断点打在哪?
相信很多朋友也很想看源码,但是不知道从何处下手。处于一种在源码里面"乱逛"的状态,一圈逛下来,收获并不大。
这一部分我想分享一下我是怎么去看源码。首先我会带着问题去源码里面寻找答案,即有针对性的看源码。
如果是这种框架类的,正如上面写的,我会先翻一翻官网(Dubbo 的官方文档其实写的挺好了),然后搭建一个简单的 Demo 项目,然后 Debug 跟进去看。Debug 的时候当然需要是设置断点的,那么这个断点如何设置呢?
第一个断点,当然毋庸置疑,是打在调用方法的地方,比如本文中,第一个断点是在这个地方:
接下里怎么办?
你当然可以从第一个断点处,一步一步的跟进去。但是在这个过程中,你发现了吗?大多数情况你都是被源码牵着鼻子走的。本来你就只带着一个问题去看源码的,有可能你Debug了十分钟,还没找到关键的代码。也有可能你Debug了十分钟,问题从一个变成了无数个
所以不要慌,我们点支烟,慢慢分析。
首先怎么避免被源码牵着四处乱逛呢?
我们得找到一个突破口,还记得我在《很开心,在使用mybatis的过程中我踩到一个坑》这篇文章中提到的逆向排查的方法吗?这次的文章,我再次展示一下该方法。
看源码之前,我们的目标要十分明确,就是想要找到 Dubbo 最小活跃数算法的具体实现类以及实现类的具体逻辑是什么。
根据我们的 provider.xml 里面的:
很明显,我们知道 loadbalance 是关键字。所以我们拿着 loadbalance 全局搜索,可以看到 Dubbo 包下面的 LoadBalance。
这是一个 SPI 接口 com.alibaba.dubbo.rpc.cluster.LoadBalance:
其实现类为:
com.alibaba.dubbo.rpc.cluster.loadbalance.AbstractLoadBalance
AbstractLoadBalance 是一个抽象类,该类里面有一个抽象方法doSelect。这个抽象方法其中的一个实现类就是我们要分析的最少活跃次数负载均衡的源码。
同时,到这里我们知道了 LoadBalance 是一个 SPI 接口,说明我们可以扩展自己的负载均衡策略。抽象方法 doSelect 有四个实现类。这个四个实现类,就是 Dubbo 官方提供的负载均衡策略(截止 2.7.7 版本之前),他们分别是:
- ConsistentHashLoadBalance 一致性哈希算法
- LeastActiveLoadBalance 最小活跃数算法
- RandomLoadBalance 加权随机算法
- RoundRobinLoadBalance 加权轮询算法
我们已经找到了 LeastActiveLoadBalance 这个类了,那么我们的第二个断点打在哪里已经很明确了。
目前看来,两个断点就可以支撑我们的分析了。
有的朋友可能想问,那我想知道 Dubbo 是怎么识别出我们想要的是最少活跃次数算法,而不是其他的算法呢?其他的算法是怎么实现的呢?从第一个断点到第二个断点直接有着怎样的调用链呢?
在没有彻底搞清楚最少活跃数算法之前,这些统统先记录在案但不予理睬。一定要明确目标,带着一个问题进来,就先把带来的问题解决了。之后再去解决在这个过程中碰到的其他问题。在这样环环相扣解决问题的过程中,你就慢慢的把握了源码的精髓。这是我个人的一点看源码的心得。供诸君参考。
模拟环境
既然叫做最小活跃数策略。那我们得让现有的三个消费者都有一些调用次数。所以我们得改造一下服务提供者和消费者。
服务提供者端的改造如下:
PS:这里以权重为 300 的服务端为例。另外的两个服务端改造点相同。
客户端的改造点如下:
一共发送 21 个请求:其中前 20 个先发到服务端让其 hold 住(因为服务端有 sleep),最后一个请求就是我们需要 Debug 跟踪的请求。
运行一下,让程序停在断点的地方,然后看看控制台的输出:
权重为300的服务端共计收到9个请求
权重为200的服务端共计收到6个请求
默认权重的服务端共计收到5个请求
我们还有一个请求在 Debug。直接进入到我们的第二个断点的位置,并 Debug 到下图所示的一行代码(可以点看查看大图):
正如上面这图所说的:weight=100 回答了一个问题,active=0 提出的一个问题。
weight=100 回答了什么问题呢?
默认权重是多少?是 100。
我们服务端的活跃数分别应该是下面这样的
- 权重为300的服务端,active=9
- 权重为200的服务端,active=6
- 默认权重(100)的服务端,active=5
但是这里为什么截图中的active会等于 0 呢?这是一个问题。
继续往下 Debug 你会发现,每一个服务端的 active 都是 0。所以相比之下没有一个 invoker 有最小 active 。于是程序走到了根据权重选择 invoker 的逻辑中。
active为什么是0?
active 为 0 说明在 Dubbo 调用的过程中 active 并没有发生变化。那 active 为什么是 0,其实就是在问 active 什么时候发生变化?
要回答这个问题我们得知道 active 是在哪里定义的,因为在其定义的地方,必有其修改的方法。
下面这图说明了active是定义在RpcStatus类里面的一个类型为AtomicInteger 的成员变量。
在 RpcStatus 类中,有三处()调用 active 值的方法,一个增加、一个减少、一个获取:
很明显,我们需要看的是第一个,在哪里增加。
所以我们找到了 beginCount(URL,String) 方法,该方法只有两个 Filter 调用。
ActiveLimitFilter,见名知意,这就是我们要找的东西。
com.alibaba.dubbo.rpc.filter.ActiveLimitFilter具体如下:
看到这里,我们就知道怎么去回答这个问题了:为什么active是0呢?因为在客户端没有配置ActiveLimitFilter。所以,ActiveLimitFilter没有生效,导致active没有发生变化。
怎么让其生效呢?已经呼之欲出了。
好了,再来试验一次:
加上Filter之后,我们通过Debug可以看到,对应权重的活跃数就和我们预期的是一致的了。
1.权重为300的活跃数为6
2.权重为200的活跃数为11
3.默认权重(100)的活跃数为3
根据活跃数我们可以分析出来,最后我们Debug住的这个请求,一定会选择默认权重的invoker去执行,因为他是当前活跃数最小的invoker。如下所示:
虽然到这里我们还没开始进行源码的分析,只是把流程梳理清楚了。但是把Demo完整的搭建了起来,而且知道了最少活跃数负载均衡算法必须配合ActiveLimitFilter使用,位于RpcStatus类的active字段才会起作用,否则,它就是一个基于权重的算法。
比起其他地方直接告诉你,要配置ActiveLimitFilter才行哦,我们自己实验得出的结论,能让我们的印象更加深刻。
我们再仔细看一下加上ActiveLimitFilter之后的各个服务的活跃数情况:
- 权重为300的活跃数为6
- 权重为200的活跃数为11
- 默认权重(100)的活跃数为3