文章目录
- 一、前言
- 二、从getForObject()是如何进入LoadBalanceInterceptor.intercept()方法的
- 三、获得服务列表,间隔更新服务列表,将服务列表存储在内存变量中
- 3.1 过渡,从LoadBalanceInterceptor.intercept()出发...
- 3.2 获得服务列表,间隔更新服务列表
- 3.2.1 restOfInit()方法
- 3.2.2 enableAndInitLearnNewServersFeature()方法
- 3.3 将服务列表存储在内存变量中
- 3.4 心跳机制(ping测试服务列表中的服务是否存活)
- 四、IRule接口之源码中各种各样的负载均衡策略
- 4.1 简单负载均衡策略
- 4.2 权重负载均衡策略(根据后端的响应时间合理规划负载均衡的权重)
- 4.3 自己动手实现一种负载均衡算法
- 五、尾声
一、前言
ribbon是一个顶级渣男,也是一个顶级时间管理大师,他有100个女朋友,但是这100个女朋友相互之间并不知道,因为ribbon每次都邀请一个女朋友约会,坚决不要各个女朋友认识。
ribbon手里有各个女朋友的电话号码、微信号、家庭住址和家庭成员信息,永远不会搞错,ribbon每晚上约会一个女朋友,他是怎样选择的呢?他有各种方案,可以给女朋友编号,从1号到100号,这样的话,连续三个月都不换重的,爽乖乖;可以每天晚上翻牌子,随便抽取一个,好有古代皇帝的感觉;也可以尽量邀请那些约会不要让他等他久女孩子,毕竟渣男是时间宝贵。
本文,手把手教你ribbon如何轻松应对100个女朋友的约会的情场秘籍。
二、从getForObject()是如何进入LoadBalanceInterceptor.intercept()方法的
先看 ClientHttpRequest request = createRequest(url, method);
点进去,到了HttpAccessor类中,
好了,我们先看getInterceptors()方法是如何得到拦截器的
其实,上一篇博客,早在LoadBalancerAutoConfiguration类中,讲解三个bean的初始化的时候,我们就看到interceptors的赋值,这里来看一下,它就在:
好了,getInterceptor()方法得到拦截器之后,新建一个了InterceptingClientHttpRequestFactory工厂并返回,这样InterceptingHttpAccessor类就看完了,我们回到调用方HttpAccessor类的createRequest()方法,
附加一句:
好了,看完了,现在返回HttpAccessor类的createRequest方法,如下:
回到RestTemplate类的doExecute()方法,如下:
所以,不断执行后,最后返回给RestTemplate类的doExecute()方法,如下:
三、获得服务列表,间隔更新服务列表,将服务列表存储在内存变量中
3.1 过渡,从LoadBalanceInterceptor.intercept()出发…
对于LoadBalanceInterceptor类的intercept()方法,执行了loadbalancer.execute()方法,参数一共四个,分别是服务名serviceName,实参request,实参body,实参execution,如下图:
点进去,就从LoadBalancerInterceptor类导流RibbonLoadBalancerClient类,这里有三个execute()方法,如下图:
其中,第二个,就是两个参数的execute()方法,就是上一篇博客介绍过的,先获得负载均衡器,然后获得服务节点,我们再次跟到服务节点中去:
看起来,ZoneAwareLoadBalancer类中的chooseServer()方法的逻辑完成了,但是这里补充一句,chooseServer()方法中如果满足 getLoadBalancerStats().getAvailableZones().size() <= 1
或者最后执行 zoneLoadBalancer.chooseServer(key);
得到的server为null,就是执行父类的chooseServer()方法,就是BaseLoadBalancer类的chooseServer()方法。
规则默认是轮询,可以设置
3.2 获得服务列表,间隔更新服务列表
3.2.1 restOfInit()方法
我们看到,在ZoneAwareLoadBalancer的父类,DynamicServerListLoadBalancer的构造方法中,调用过一个名为restOfInit()的方法,如下:
看名称像是一个初始化方法,而且还在构造函数中被调用,应该是一个启动设置相关的方法,那么到底设置什么呢?其实,就是获取application.properties本地配置文件中配置中的地址列表,并把它设置到程序中的两个list中。
进入restOfInit()方法看看具体逻辑吧,如下:
3.2.2 enableAndInitLearnNewServersFeature()方法
先看enableAndInitLearnNewServersFeature()
方法,如下:
这里包含两个东西,一个是start()方法,一个是updateAction,我们先看看这个start()方法,点进去,如下:
问题:任务启动以后延期1秒执行,每次刷新的间隔时间是30秒的源码依据在哪里?
回答:
问题2:第二个构造函数,那个getRefreshIntervalMs(clientConfig)方法,点进去看一下呗?
回答2:
我们看到,start()方法中在按照“任务启动以后延期1秒执行,每次刷新的间隔时间是30秒”执行着任务,执行的这个任务是什么?就是start()方法传入的实参——updateAction,看一看这个updateAction:
所以,这个updateAction就是定时更新服务地址列表,进入updateListOfServers()方法,如下:
这里调用了一个serverListImpl.getUpdatedListOfServers()
才能获得servers,看来秘密就在这里面,点进去看一看,
这个key值是什么?点进去看看
回到ConfigurationBasedServerList类的getUpdatedListOfServers()方法,原来这个方法就是将配置文件中的地址列表出来。所以,调用方DynamicServerListLoadBalancer类中的updateListOfServers()方法中就是得到了本地配置文件中的地址列表存放到servers变量中。
其实,获取地址列表有两种,一是本地配置文件中的地址列表,还有一种是Eureka注册中心的地址列表,因为没有导入eureka依赖,所以serverListImpl.getUpdatedListOfServers()
这里ctrl+alt+左键没有弹出来,所以略去。
3.3 将服务列表存储在内存变量中
在BaseLoadBalancer类中,有两个list类型的类变量allServerLIst和upServerList,这两个list是服务端地址列表在客户端内存中的存储,如下:
那么我们的问题,这两个list在哪里被初始化,在哪里被更新?这是本节的问题。
上面,在DynamicServerListLoadBalancer类中的updateListOfServers()方法,最后一句调用了updateAllServerList()方法,点进去看看:
再进入setServersList()方法,如下:
点击super.setServersList()方法,进入BaseLoadBalancer类中的setServersList()方法,如下:
好了,返回回去看一看吧!
至此,这个restOfInit()方法完成了“获得服务列表,间隔更新服务列表,将服务列表存储在内存变量中”。
3.4 心跳机制(ping测试服务列表中的服务是否存活)
Ribbon有一种类似心跳的ping机制,如果服务列表中的服务宕机了,即注册中心(Eureka)不通知,Ribbon也可以知道,当服务列表中某一个服务宕机后,Ribbon可以用自己的ping机制知道,将无法访问的节点剔除掉,从而改变内存中两个list的存储,这有点向Eureka服务注册中应该实现的东西。那么,源码中是怎么实现的呢?一起来看看吧。
找到BaseLoadBalancer类中的setupPingTask()方法,
点进去new PingTask(),看一看这个PingTask类的具体任务:
在代码行 new Pinger(pingStrategy).runPinger()
中,来看看runPinger()方法的具体逻辑,
其实,IPing有不同的实现,比如PingUrl类,如下:
isAlive()也很容易就看懂了
我们现在搞清楚了Ribbon的心跳机制和两个list区别,我们再看看这个ping是在哪里定义的?ping是怎么和loadBalancer联系的(即心跳机制怎么帮助负载均衡)?
好了,那么第二个问题,ping是怎么和loadBalancer联系的(即心跳机制怎么帮助负载均衡)?回到RibbonClientConfiguration类,如下:
四、IRule接口之源码中各种各样的负载均衡策略
4.1 简单负载均衡策略
找到IRule接口,
我们先来看看比较简单的轮询负载均衡、随机负载均衡、重试负载均衡。
轮询负载均衡策略
随机负载均衡策略
重试负载均衡策略
小结:无论是轮询、随机、重试,这些负载均衡策略虽然简单,但是很难真正起到负载均衡的作用,要想真正起到负载均衡的作用,要需要更好的负载均衡规则rule,且看下面的权重负载均衡策略,根据后端的响应时间合理规划负载均衡的权重。
4.2 权重负载均衡策略(根据后端的响应时间合理规划负载均衡的权重)
找到WeightedResponseTimeRule类,这个权重类是轮询类的子类,所以这个权重算法以轮询为基础的,它的本质是在轮询的基础上对各个服务节点加上权重,从而使自己轮询的时候的比重更多。
对于核心代码的解释
代码如下:
double randomWeight = random.nextDouble() * maxTotalWeight;
// pick the server index based on the randomIndex
int n = 0;
for (Double d : 140>=) {
if (d >= randomWeight) {
serverIndex = n;
break;
} else {
n++;
}
}
server = allList.get(serverIndex);
解释:比如,服务节点A的响应时间是10毫秒,服务节点B的响应时间是20毫秒,服务节点C的响应时间是40毫秒,三种服务节点中,A的响应时间最快,C的响应时间最慢。
如果要求每一个服务节点,总的时间时间为150毫秒
A=[0,(150-10)]=[0,140]
B=(140,140+(150-20)]=(140,270]
C=(270,270+(150-40)]=(270,380]
区间表示这个权重,然后随机生成一个数字,看这个数字落在哪个服务节点负责的区间,就选择哪个服务节点。
如果是randomWeight是40,那么开始循环遍历 for (Double d : currentWeights)
这三个节点,对于A节点,满足 140>=40 ,所以执行 serverIndex = n;
然后break,最后执行 server = allList.get(serverIndex);
选出A节点;
如果是randomWeight是240,那么开始循环遍历 for (Double d : currentWeights)
这三个节点,对于A节点,不满足 if (d >= randomWeight)
,走else分支,n++
操作之后 n为1,对于B节点,满足 270>240,所以执行 serverIndex = n;
然后break,最后执行 server = allList.get(serverIndex);
选出B节点;
如果是randomWeight是340,那么开始循环遍历 for (Double d : currentWeights)
这三个节点,对于A节点和B节点,都不满足 if (d >= randomWeight)
,走else分支,两次 n++
操作之后 n为1,对于C节点,满足 380>240,所以执行 serverIndex = n;
然后break,最后执行 server = allList.get(serverIndex);
选出C节点;
4.3 自己动手实现一种负载均衡算法
自己动手实现一种负载均衡算法不是很难,我们可以实现一个自己的负载均衡算法,如下:
五、尾声
看顶级渣男如何邀约100个女朋友(二),完成了。
天天打码,天天进步!!