【一起学源码-微服务】Ribbon 源码四:进一步探究Ribbon的IRule和IPing
前言
前情回顾
上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是DiscoveryEnableNIWSServerList,同时在DynamicServerListLoadBalancer中会调用PollingServerListUpdater 进行定时更新Eureka注册表信息到BaseLoadBalancer中,默认30s调度一次。
本讲目录
我们知道Ribbon主要是由3个组件组成的:
- ILoadBalancer
- IRule
- IPing
其中ILoadBalancer前面我们已经分析过了,接下来我们一起看看IRule和IPing中的具体实现。
目录如下:
- 负载均衡默认Server选择逻辑
- Ribbon实际执行http请求逻辑
- Ribbon中ping机制原理
- Ribbon中其他IRule负载算法初探
说明
原创不易,如若转载 请标明来源!
博客地址:一枝花算不算浪漫
微信公众号:壹枝花算不算浪漫
源码分析
负载均衡默认Server选择逻辑
还记得我们上一讲说过,在Ribbon初始化过程中,默认的IRule为ZoneAvoidanceRule,这里我们可以通过debug看看,从RibbonLoadBalancerClient.getServer() 一路往下跟,这里直接看debug结果:

然后我们继续跟ZoneAvoidanceRule.choose() 方法:
public abstract class PredicateBasedRule extends ClientConfigEnabledRoundRobinRule {
/**
* Method that provides an instance of {@link AbstractServerPredicate} to be used by this class.
*
*/
public abstract AbstractServerPredicate getPredicate();
/**
* Get a server by calling {@link AbstractServerPredicate#chooseRandomlyAfterFiltering(java.util.List, Object)}.
* The performance for this method is O(n) where n is number of servers to be filtered.
*/
@Override
public Server choose(Object key) {
ILoadBalancer lb = getLoadBalancer();
Optional<Server> server = getPredicate().chooseRoundRobinAfterFiltering(lb.getAllServers(), key);
if (server.isPresent()) {
return server.get();
} else {
return null;
}
}
}
这里是调用的ZoneAvoidanceRule的父类中的choose()方法,首先是拿到对应的ILoadBalancer,然后拿到对应的serverList数据,接着调用chooseRoundRobinAfterFiltering()方法,继续往后跟:
public abstract class AbstractServerPredicate implements Predicate<PredicateKey> {
public Optional<Server> chooseRoundRobinAfterFiltering(List<Server> servers, Object loadBalancerKey) {
List<Server> eligible = getEligibleServers(servers, loadBalancerKey);
if (eligible.size() == 0) {
return Optional.absent();
}
return Optional.of(eligible.get(incrementAndGetModulo(eligible.size())));
}
private int incrementAndGetModulo(int modulo) {
for (;;) {
int current = nextIndex.get();
int next = (current + 1) % modulo;
if (nextIndex.compareAndSet(current, next) && current < modulo)
return current;
}
}
}
到了这里可以看到incrementAndGetModulo()方法就是处理serverList轮询的算法,这个和RoudRobinRule中采用的是一样的算法,这个算法大家可以品一品,我这里也会画个图来说明下:

看了图=中的例子估计大家都会明白了,这个算法就是依次轮询。这个算法写的很精简。
Ribbon实际执行http请求逻辑
我们上面知道,我们按照轮询的方式从serverList取到一个server后,那么怎么把之前原有的类似于:http://ServerA/sayHello/wangmeng中的ServerA给替换成请求的ip数据呢?
接着我们继续看RibbonLoadBalancerClient.execute()方法,这个里面request.apply()会做一个serverName的替换逻辑。
最后可以一步步跟到RibbonLoadBalancerClient.reconstructURI(),这个方法是把请求自带的getURI方法给替换了,我们最后查看context.reconstructURIWithServer() 方法,debug结果如图,这个里面会一步步把对应的请求url给拼接起来:

Ribbon中ping机制原理
我们知道 Ribbon还有一个重要的组件就是ping机制,通过上一讲Ribbon的初始化我们知道,默认的IPing实现类为:NIWSDiscoveryPing,我们可以查看其中的isAlive()方法:
public class NIWSDiscoveryPing extends AbstractLoadBalancerPing {
BaseLoadBalancer lb = null;
public NIWSDiscoveryPing() {
}
public BaseLoadBalancer getLb() {
return lb;
}
/**
* Non IPing interface method - only set this if you care about the "newServers Feature"
* @param lb
*/
public void setLb(BaseLoadBalancer lb) {
this.lb = lb;
}
public boolean isAlive(Server server) {
boolean isAlive = true;
if (server!=null && server instanceof DiscoveryEnabledServer){
DiscoveryEnabledServer dServer = (DiscoveryEnabledServer)server;
InstanceInfo instanceInfo = dServer.getInstanceInfo();
if (instanceInfo!=null){
InstanceStatus status = instanceInfo.getStatus();
if (status!=null){
isAlive = status.equals(InstanceStatus.UP);
}
}
}
return isAlive;
}
@Override
public void initWithNiwsConfig(
IClientConfig clientConfig) {
}
}
这里就是获取到DiscoveryEnabledServer对应的注册信息是否为UP状态。那么 既然有个ping的方法,肯定会有方法进行调度的。
我们可以查看isAlive()调用即可以找到调度的地方。
在BaseLoadBalancer构造函数中会调用setupPingTask()方法,进行调度:
protected int pingIntervalSeconds = 10;
void setupPingTask() {
if (canSkipPing()) {
return;
}
if (lbTimer != null) {
lbTimer.cancel();
}
lbTimer = new ShutdownEnabledTimer("NFLoadBalancer-PingTimer-" + name,
true);
lbTimer.schedule(new PingTask(), 0, pingIntervalSeconds * 1000);
forceQuickPing();
}
这里pingIntervalSeconds在BaseLoadBalancer中定义的是10s,但是在initWithConfig()方法中,通过传入的时间覆盖了原本的10s,这里实际的默认时间是30s。如下代码:

我们也可以通过debug来看看:

可能大家好奇为什么要单独截图来说这个事,其实是因为网上好多博客讲解都是错的,都写的是ping默认调度时间为10s,想必他们都是没有真正debug过吧。

还是那句话,源码出真知。
Ribbon中其他IRule负载算法初探
RoundRobinRule:系统内置的默认负载均衡规范,直接round robin轮询,从一堆server list中,不断的轮询选择出来一个server,每个server平摊到的这个请求,基本上是平均的
这个算法,说白了是轮询,就是一台接着一台去请求,是按照顺序来的
AvailabilityFilteringRule:这个rule就是会考察服务器的可用性
如果3次连接失败,就会等待30秒后再次访问;如果不断失败,那么等待时间会不断变长
如果某个服务器的并发请求太高了,那么会绕过去,不再访问这里先用round robin算法,轮询依次选择一台server,如果判断这个server是存活的可用的,如果这台server是不可以访问的,那么就用round robin算法再次选择下一台server,依次循环往复,10次。
WeightedResponseTimeRule:带着权重的,每个服务器可以有权重,权重越高优先访问,如果某个服务器响应时间比较长,那么权重就会降低,减少访问
ZoneAvoidanceRule:根据机房和服务器来进行负载均衡,说白了,就是机房的意思,看了源码就是知道了,这个就是所谓的spring cloud ribbon环境中的默认的Rule
BestAvailableRule:忽略那些请求失败的服务器,然后尽量找并发比较低的服务器来请求
总结
到了这里 Ribbon相关的就结束了,对于Ribbon注册表拉取及更新逻辑这里也梳理下,这里如果Ribbon保存的注册表信息有宕机的情况,这里最多4分钟才能感知到,所以spring cloud还有一个服务熔断的机制,这个后面也会讲到。

申明
本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!
感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫

【一起学源码-微服务】Ribbon 源码四:进一步探究Ribbon的IRule和IPing的更多相关文章
- 【一起学源码-微服务】Ribbon源码五:Ribbon源码解读汇总篇~
前言 想说的话 [一起学源码-微服务-Ribbon]专栏到这里就已经全部结束了,共更新四篇文章. Ribbon比较小巧,这里是直接 读的spring cloud 内嵌封装的版本,里面的各种config ...
- 【一起学源码-微服务】Eureka+Ribbon+Feign阶段性总结
前言 想说的话 这里已经梳理完Eureka.Ribbon.Feign三大组件的基本原理了,今天做一个总结,里面会有一个比较详细的调用关系流程图. 说明 原创不易,如若转载 请标明来源! 博客地址:一枝 ...
- 【一起学源码-微服务】Nexflix Eureka 源码十:服务下线及实例摘除,一个client下线到底多久才会被其他实例感知?
前言 前情回顾 上一讲我们讲了 client端向server端发送心跳检查,也是默认每30钟发送一次,server端接收后会更新注册表的一个时间戳属性,然后一次心跳(续约)也就完成了. 本讲目录 这一 ...
- 【一起学源码-微服务】Nexflix Eureka 源码十三:Eureka源码解读完结撒花篇~!
前言 想说的话 [一起学源码-微服务-Netflix Eureka]专栏到这里就已经全部结束了. 实话实说,从最开始Eureka Server和Eureka Client初始化的流程还是一脸闷逼,到现 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(四)——一步一步教你如何撸Dapr之订阅发布
之前的章节我们介绍了如何通过dapr发起一个服务调用,相信看过前几章的小伙伴已经对dapr有一个基本的了解了,今天我们来聊一聊dapr的另外一个功能--订阅发布 目录:一.通过Dapr实现一个简单的基 ...
- 【一起学源码-微服务】Ribbon 源码一:Ribbon概念理解及Demo调试
前言 前情回顾 前面文章已经梳理清楚了Eureka相关的概念及源码,接下来开始研究下Ribbon的实现原理. 我们都知道Ribbon在spring cloud中担当负载均衡的角色, 当两个Eureka ...
- 【一起学源码-微服务】Ribbon 源码三:Ribbon与Eureka整合原理分析
前言 前情回顾 上一篇讲了Ribbon的初始化过程,从LoadBalancerAutoConfiguration 到RibbonAutoConfiguration 再到RibbonClientConf ...
- 【一起学源码-微服务】Feign 源码一:源码初探,通过Demo Debug Feign源码
前言 前情回顾 上一讲深入的讲解了Ribbon的初始化过程及Ribbon与Eureka的整合代码,与Eureka整合的类就是DiscoveryEnableNIWSServerList,同时在Dynam ...
- 【一起学源码-微服务】Nexflix Eureka 源码二:EurekaServer启动之配置文件加载以及面向接口的配置项读取
前言 上篇文章已经介绍了 为何要读netflix eureka源码了,这里就不再概述,下面开始正式源码解读的内容. 如若转载 请标明来源:一枝花算不算浪漫 代码总览 还记得上文中,我们通过web.xm ...
随机推荐
- 关于XIFF开发IM的一些想法
因为发现网上XIFF的资料真的不多,何况现在gg又被和谐,查询资料很是不便.我写这篇文章的目的除了跟大家分享一些开发心得,也希望有类似经验的朋友看到文章后有机会多多交流. 之前我没有怎么接触过flas ...
- 洛谷P4860 Roy&October之取石子II 题解 博弈论
题目链接:https://www.luogu.org/problem/P4860 和<P4018 Roy&October之取石子>一样的推导思路,去找循环节. 可以发现:只要不能被 ...
- Java JDBC学习实战(三): 事务管理
一. 数据库的事务特性 事务是一步或多步组成操作序列组成的逻辑执行单元,这个序列要么全部执行,要么则全部放弃执行. 事务的四个特性:原子性(Atomicity).一致性(Consistency).隔离 ...
- pip安装指定版本的应用
可以在pip后使用 == 运算符指定版本号 pip install applicationName==version
- Python--day31--TCP的长链接
- C# 序列类为 xml 可以使用的特性大全
本文告诉大家如何使用序列类,以及序列时可以用到的特性,特性的作用和一些容易被问的问题 最近我在把项目文件修改为 VisualStudio 2017 的格式,请看从以前的项目格式迁移到 VS2017 新 ...
- 154th LeetCode Weekly Contest
A B D均比较简单.先说C题 K-Concatenation Maximum Sum Given an integer array arr and an integer k, modify the ...
- mpvue的坑,持续更新-.-
mpvue... 坑 怎么说呢,去github看一下,发现还是有很多问题没有解决... 不支持filter 亲,到现在还没有支持filter哦.只能用替代方法了,用computed或者渲染前先处理数据 ...
- PHP PDO扩展整理,包括环境配置\基本增删改查\事务\预处理
相关文章:PHP的mysql扩展整理,操作数据库的实现过程分析 PHPmysqli扩展整理,包括面向过程和面向对象的比较\事务控制\批量执行\预处理 介绍 PDO是一种PHP程序连接数据库的接口 ...
- I/O 端口和 I/O 内存
每个外设都是通过读写它的寄存器来控制. 大部分时间一个设备有几个寄存器, 并且在连 续地址存取它们, 或者在内存地址空间或者在 I/O 地址空间. 在硬件级别上, 内存区和 I/O 区域没有概念上的区 ...