Ribbon源码解析
首先,跟踪LoadBalancerClient的源码,它是一个接口类,继承了ServiceInstanceChooser,它的实现类为RibbonLoadBalancClient,它们之间的关系如下图:
LoadBalancerClient是一个负载均衡的客户端,有如下3种方法。其中有2个excute(方法,均用来执行请求,reconstructURI()用于 重构Url,代码如下:
ServiceInstanceChooser接口有一个方法根据serviceId获取ServiceInstance,即通过服务名来选择服务实例:
LoadBalancerClient的实现类为RibbonLoadBalancerClient
。RibbonLoadBalancerClient是一个非常重要的类,最终的负载均衡的请求处理由它来执行:
在RibbonLoadBalancerClient的源码中,choose()方法用于选择具体服务实例。该方法通过getServer()方法去获取实例,经过源码跟踪,最终交给ILoadBalancer类去选择服务实例。
ILoadBalancer在ribbon-loadbalancer的jar包下,ILoadbalancer是一个借口,该借口定义了一系列实现负载均衡的方法:
其中,addServers()方法用于添加一个Server集合,chooseServer()方法用于根据key去获取Server,markServerDown()方法用于标记某个服务下线,getReachableServers()获取可用的Server集合,getAllServers()获取所有的Server集合。
ILoadBalancer的子类为BaseLoadBalancer,BaseLoadBalancer的实现类为DynamicServerListLoadBalancer,三者之间的关系如下:
查看DynamicServerListLoadBalancer类的源码,DynamicServerListLoadBalancer需要配置IClientConfig、IRule、IPing、ServerList、ServerListFilter和ILoadBalancer
。查看BaseLoadBalancer类的源码,在默认情况下,实现了如下配置:
IClientConfig ribbonClientConfig: DefaultClientConfigImpl
IRule ribbonRule: RoundRobinRule
IPing ribbonPing: DummyPing
ServerList ribbonServerList: ConfigurationBasedServerList
ServerListFilter ribbonServerListFilter: ZonePreferenceServerListFilter
ILoadBalancer ribbonLoadBalancer: ZoneAwareLoadBalancer
IClientConfig用于配置负载均衡的客户端,IClientConfig的默认实现类为DefaultClientConfigImpl。
IRule用于配置负载均衡的策略,IRule有3个方法,其中choose()是根据key来获取server实例的,setLoadBalancer()和getLoadBalancer()是用来设置和获取ILoadBalancer的,它的源码如下:
IRule有很多默认的实现类,这些实现类根据不同的算法和逻辑来处理负载均衡的策略。IRule的默认实现类有以下7种。在大多数情况下,这些默认的实现类是可以满足需求的,如果有特殊的需求,可以自己实现。
BestAvailableRule
:选择最小请求数。ClientConfigEnabledRoundRobinRule
: 轮询。RandomRule
: 随机选择一个server。RoundRobinRule
: 轮询选择 server。RetryRule
:根据轮询的方式重试。WeightedResponse TimeRule
:根据响应时间去分配一个weight ,weight 越低,被选择的可能性就越低。ZoneAvoidanceRule
: 根据server的zone区域和可用性来轮询选择。
IPing用于向server发送"ping",来判断server是否有响应,从而判断server是否可用。它有一个isAlive()方法:
IPing的实现类有PingUrl、PingConstant、NoOpPing、DummyPing和NIWSDiscoveryPing。
PingUrl
:真实地去piing某个Url,判断其是否可用PingConstant
:固定返回某服务是可用,默认返回true,即可用NoOpPing
:不去ping,直接返回true,即可用DummyPing
:直接返回true,并实现了initWithNiwsConfig方法NIWSDiscoveryPing
:根据DIscoveryEnabledServer的InstanceInfo的InstanceStatus去判断,如果为InstanceStatus.UP,则可用,否则不可用
ServerList是定义获取所有server的注册列表信息的接口:
ServerListFilter接口定义了可根据配置去过滤或者特性动态地获取符合条件的server列表的方法:
阅读DynamicServerListLoadBalancer的源码,DynamicServerListLoadBalancer的构造函数中有一个initWithNiwsConfig()方法。在该方法中经过一系列的初始化配置,最终执行了restOfInit()方法。DynamicServerListLoadBalance的部分源码如下:
在restOfInit()方法中,有一个updateListOfServers()的方法,该方法是用来获取所有的ServerList的。
void restOfInit(IClientConfig clientConfig){
.....
updateListOfServers();
......
}
进一步跟踪updateListOfServers()方法的源码,最终由serverListImpl.getUpdateListOfServers()获取所有的服务列表:
而serverListImpl是ServerList 接口的具体实现类。跟踪源码,ServerList 的实现类为DiscoveryEnabledNIWSServerList
,这个类在ribbon-eureka.jar 的com.netlix.niws.loadbalancer包下。
其中,DiscoveryEnabledNIWSServerList 有getlnitiallistOfServers()和getUpdatedListOfServers()方法,具体代码如下:
继续跟踪源码,obtainServersViaDiscovery() 方法是根据eurekaClientProvider.get()方法来获取EurekaClient的,再根据EurekaClient来获取服务注册列表信息,代码如下:
其中,eurekaClientProvider 的实现类是LegacyEurekaClientProvider
,LegacyEurekaClientProvider是一个获取eurekaClient实例的类,其代码如下:
由此可见,负载均衡器是从Eureka Client获取服务列表信息的,并根据IRule的策略去路由,根据IPing去判断服务的可用性。
那么现在还有一个问题,负载均衡器每隔多长时间从EurekaClient获取注册信息呢?
在BaseLoadBalancer类的源码中,在BaseLoadBalancer 的构造方法开启了一个PingTask任务,代码如下:
public BaseLoadBalancer(String name, IRule rule, LoadBalancerStats stats, IPing ping, IPingStrategy pingStrategy){
........
setupPingTask();
........
}
在setupPingTask()的具体代码逻辑里,开启了ShutdownEnabledTimer的PingTask任务,在默认情况下,变量pingIntervalSeconds的值为10, 即每10秒向EurekaClient发送一次心跳‘ping”
。
查看PingTask的源码,PingTask创建了一个Pinger对象,并执行了runPinger()方法。
查看Pinger的runPinger()方法,最终根据pingerStrategy.pingServers(ping, allServers)
来获取服务的可用性:
- 如果该返回结果与之前相同,则不向EurekaClient获取注册列表;
- 如果不同,则通知ServerStatusChangeListener服务注册列表信息发生了改变,进行更新或者重新拉取;
由此可见,LoadBalancerClient是在初始化时向Eureka获取服务注册列表信息,并且每10秒向EurekaClient发送“ping”,来判断服务的可用性。如果服务的可用性发生了改变或者服务数量和之前的不一致,则更新或者重新拉取。LoadBalancerClient 有了这些服务注册列表信息,就可以根据具体的IRule的策略来进行负载均衡。
最后,回到问题的本身,为什么在RestTemplate类的Bean上加一个@LoadBalance 注解就可以使用Ribbon的负载均衡呢?
全局搜索查看有哪些类用到了@LoadBalanced注解。通过搜索,可以发现LoadBalancerAutoConfiguration类(LoadBalancer 自动配置类)使用到了该注解,LoadBalancerAutoConfiguration 类的代码如下:
综上所述,Ribbon的负载均衡主要是通过LoadBalancerClient来实现的,而LoadBalancerClient具体交给了ILoadBalancer 来处理,ILoadBalancer 通过配置IRule、IPing 等,向EurekaClient获取注册列表的信息,默认每10秒向EurekaClient发送一次“ping”,进而检查是否需要更新服务的注册列表信息。最后,在得到服务注册列表信息后,ILoadBalancer根据IRule的策略进行负载均衡。
而RestTemplate加上@LoadBalance注解后,在远程调度时能够负载均衡,主要是维护了一个被@LoadBalance注解的RestTemplate 列表,并给该列表中的RestTemplate 对象添加了拦截器。在拦截器的方法中,将远程调度方法交给了Ribbon 的负载均衡器LoadBalancerClient去处理,从而达到了负载均衡的目的。