Ribbon是什么
Spring Cloud Ribbon是一个基于HTTP和TCP的客户端负载均衡工具,它基于Netflix Ribbon实现。通过Spring Cloud的封装,可以让我们轻松地将面对服务的REST模块请求自动转换成客户端负载均衡的服务调用。
Ribbon整合流程
从上图就可以清晰知道Ribbon的作用就是从eureka注册中心中拿到服务列表serverList,然后根据当前的负载均衡算法,选择一个服务server进行http远程调用将结果返回就完事了。那么这个负载均衡是怎么做到的呢,继续往下看看...
负载均衡执行架构
- (1)负载均衡器在初始化的时候,会从Eureka获取一次实例列表;
- (2)负载均衡器在初始化的时候,会启动一个ServerListUpdater定时器,默认每10秒从Eureka同步一次实例列表;
- (3)在获取到实例列表以后,如果负载均衡器带有服务列表过滤器(ServerListFilter),要将实例列表通过服务列表过滤器(ServerListFilter)过滤一遍,获取到最终的实例列表;
- (4)负载均衡器在初始化的时候,会启动一个PingTask定时器,默认每30秒校验一下实例列表中的所有实例是否可用;
- (5)完成上述步骤后,我们会获取到一个可用的实例列表,当要进行接口调用的时候,负载均衡器会通过负载均衡规则(IRule)筛选出其中一个实例,以供调用。
既然上面都说了那么牛叉,那么底层代码到底是怎样的,不看看怎么对得起自己?
源码分析
按照上图去翻ribbon的源码,你就能清楚其原理。我说说其中的几个重要步骤。
如何获取实例列表
一个调用请求过来的话,会首先被拦截器拦截,执行拦截器的 interrupt 方法,获取到 url 并从 url 中解析出来ServiceName ,交由 RibbonLoadBalancerClient 进行执行,调用的是 execute 方法。
首先会通过服务名获取到服务的上下文对象,通过 IloadBalancer.class 获取到对应的 ILoadBalancer 接口,但这个接口的话,会在 RibbonClientConfiguration 类中,通过 @Bean 进行初始化,所以 ILoadBalancer 对应的实现类是 ZoneAwareLoadBalancer 实例。
在构建 ZoneAwareLoadBalancer 的时候,会调用 restOfInit 方法,这个方法中调用的updateListOfServers方法就是获取注册表的方法,这个方法主要是靠 ServerImpl 去执行而Server是在创建 ZoneAwareLoadBalancer 实例的时候从构造方法中传入的,我们可以从EurekaRibbonClientConfiguration 类中发现会对 ServerList 进行构造,会创建 DiscoveryEnabledNIWSServerList 实例,并将其交由DomainExtractingServerList ,也就是说,最后 ServerList 接口的实现类就是 DomainExtractingServerList 这个类。
那么通过 ServerImpl 去进行调用,也就是相当于调用 DomainExtractingServerList 的getInitialListOfServers 方法,由于在创建的时候,其实是将 DiscoveryEnabledNIWSServerList 交给它管理,所以最后的实现方法是 DiscoveryEnabledNIWSServerList.xx.inServersViaDiscovery 。
这里的主要逻辑就是获取到 EurekaClient 实例,通过服务名获取到对应的服务实例,将服务实例的地址,放到一个服务实例集合中,当然中间会干一些比较琐碎的事情,但大体流程的话就是这样:构建 ZoneAwareLoadBalance 的时候通过已经初始化好的 ServerLIist 的实现DiscoveryEnabledNIWSServerList 来负责和 eureka cient 进行交互,通过服务名获取到对应的服务信息,将服务实例地址加入到集合中,供后面使用。
那么在我们的实际使用中,eureka 的信息是会改变的,因此会在调用 restOfInit 方法的时候,调用enableAndInitLearnNewServersFeature 它内部的话,主要还是调用上面的那个获取注册表的方法,同时初始化了一个延迟一秒每三十秒调用一次的调度任务,每隔三十秒更新一次注册表信息。
负载均衡器干了啥
通过 getServer() 方法去调用 BaseLoadBalancer.chooseServer 的方法,通过 IRule 实例,进行规则的选择,默认实例是 PredicateBasedRule ,核心执行的话是在 AbstractServerPredicate 类中,调用的 incermentAndGetModulo 方法,内部算法也比较简单,主要就是用当前值 1 和 服务数量进行取模,并且将当前值修改为计算出来的值
Ping机制是个啥
原生的 Ribbon 的话有一个 ping 机制,就是有一个 IPing 的组件,会时不时的 ping 一下服务器,看看服务器是否存活,这样我们就可以只对存活的进行访问。
默认的话,在创建 ZoneAwareLoadBalancer 的时候,会传入一个 IPing 组件,它是在 org.springframework.cloud.netflix.ribbon.eureka 包下的EurekaRibbonClientConfiguration 类中进行的初始化,主要就是创建了一个 NIWSDiscoveryPing 对象。
NIWSDiscoveryPing 这个 Ping 进行校验的方法很简单,就是获取到每个服务对应的 InstanceInfo 实例对象,判断这个实例对象的状态是不是 up 状态。
在这检查的方法调用的话,主要是在创建 ZoneAwareLoadBalancer 的时候,通过调用父类 BaseLoadBalancer 的 initWithConfig 方法,初始化了一个 PingTask 的调度任务,会每隔30秒调用一次,主要就是获取到服务实例的集合,遍历这个集合,都调用一下NIWSDiscoveryPing的isAlive方法,判断是否存活(也就是判断当前服务实例对应的状态是不是 up)
自定义负载均衡算法
代码语言:javascript复制public class MyIRule extends AbstractLoadBalancerRule {
Random rand = new Random();
public RandomRule() {
}
@SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
public Server choose(ILoadBalancer lb, Object key) {
......
......
}
public Server choose(Object key) {
return this.choose(this.getLoadBalancer(), key);
}
public void initWithNiwsConfig(IClientConfig clientConfig) {
}
}
@SpringBootApplication
@EnableEurekaClient
//在启动该微服务的时候就能去加载我们的自定义Ribbon配置类,从而使配置生效
@RibbonClient(name="cloud-provider",configuration=MySelfRuleConfig.class)
public class CloudRibbon{
public static void main(String[] args)
{
SpringApplication.run(CloudRibbon.class, args);
}
}
使自定义负载均衡算法起作用,不能像使用Ribbon中默认负载均衡算法一样直接注入容器,需要注意以下几点:
- 自定义的负载均衡算法,不能在SpringBoot启动时扫描到,即自定义的负载均衡类,不能放在启动类的子包或启动类所在包中。
- 定义配置类将自定义的负载均衡算法注入Spring容器中。(配置类也不能被启动类扫描到)
- 启动类上添加注解@RibbonClient(name="微服务名", configuration="装载自定义负载均衡算法的配置类")。
参考文章
https://zhuanlan.zhihu.com/p/262660637
https://www.cnblogs.com/roytian/p/12176321.html
https://juejin.cn/post/6904886607661236238
https://www.dazhuanlan.com/2020/03/27/5e7cfff5d58a1/
https://github.com/Netflix/ribbon/wiki
https://www.bilibili.com/video/BV1Vo4y1Z7jo