Ribbon工作原理
前面我们使用Ribbon实现负载均衡时,基本用法是注入一个RestTemplate,并使用@LoadBalanced注解标注RestTemplate,从而使RestTemplate具备负载均衡的能力。
当Spring容器启动时,使用@LoadBalanced注解修饰的RestTemplate会被添加拦截器,拦截器中使用了LoadBalancerClient处理请求,从而达到负载均衡的目的。那么LoadBalancerClient内部是如何做到的呢?下面我们通过源码分析的方式来剖析Ribbon负载均衡的工作原理。
LoadBalancerClient是Spring Cloud提供的一个非常重要的接口,它继承自ServiceInstanceChooser接口,该接口的实现类是RibbonLoadBalanceClient,它们之间的关系如图1所示。
图1 LoadBalancerClient的父接口和实现类
为了大家更好地理解图3-8所示接口及其实现类的实现细节,我们先查看LoadBalancerClient的部分源码,具体如下:
1 public interface LoadBalancerClient extends ServiceInstanceChooser {
2 <T> T execute(String serviceId, LoadBalancerRequest<T> request)
3 throws IOException;
4 <T> T execute(String serviceId, ServiceInstance serviceInstance,
5 LoadBalancerRequest<T> request) throws IOException;
6 URI reconstructURI(ServiceInstance instance, URI original);
7 }
上述源码中,LoadBalancerClient提供的两个execute()方法用于执行请求, reconstructURI()方法用于重构URL。
继续查看LoadBalancerClient继承的ServiceInstanceChooser接口源码,具体如下:
public interface ServiceInstanceChooser {
ServiceInstance choose(String serviceId);
}
上述源码中,ServiceInstanceChooser接口定义一个choose()方法,该方法用于根据serviceId选择一个服务实例,即通过服务名选择服务实例。
RibbonLoadBalanceClient是LoadBalancerClient的实现类,它用来执行最终的负载均衡请求。其中,RibbonLoadBalanceClient的一个choose()方法用于选择具体的服务实例,其内部是通过getServer()方法交给ILoadBalancer完成的。
ILoadBalancer是一个接口,该接口定义了一系列实现负载均衡的方法。ILoadBalancer接口的实现类结果如图2所示。
图2 ILoadBalancer接口实现类结构
查看BaseLoadBalancer和DynamicServerListLoadBalancer源码,默认情况下实现了以下配置:
(1)IClientConfig clientConfig:用于配置负载均衡客户端,默认实现类是DefaultClientConfigImpl。
(2)IRule rule:用于配置负载均衡的策略,默认使用的是RoundRobinRule策略,也就是轮询策略。
(3)IPing ping:用于检查当前服务是否有响应,从而判断当前服务是否可用,默认实现类是DummyPing,该实现类的isAlive()方法返回值是true,默认所有服务实例都是可用的。
(4)ServerList serverList: 用于获取所有Server注册列表信息。通过跟踪源码会发现,ServerList的实现类是DiscoveryEnabledNIWSServerList,该类定义的obtainServersViaDiscovery()方法是根据eurekaClientProvider.get()方法获取EurekaClient,再根据EurekaClient获取服务注册列表信息。EurekaClient的实现类是DiscoveryClient,DiscoveryClient具有服务注册、获取服务注册列表等功能。
(5)ServerListFilter filter:定义了根据配置过滤或者动态获取符合条件的服务列表,默认实现类是ZonePreferenceServerListFilter,该策略能够优先过滤出与请求调用方处于同区域的服务实例。
综上所述,使用RibbonLoadBalanceClient实现负载均衡时,会从EurekaClient获取服务列表信息,然后根据IPing判断服务是否可用。如果服务可用,则会根据IRule选择负载均衡策略,否则会重新获取服务清单。
了解了LoadBalancerClient负载均衡功能后,那么RestTemplate添加@LoadBalanced注解后,为什么会被拦截呢?这是因为LoadBalancerAutoConfiguration类维护了一个被@LoadBalanced修饰的RestTemplate列表,在初始化过程中,通过调用customizer.customize(restTemplate)方法为RestTemplate添加了LoadBalancerInterceptor拦截器,该拦截器中的方法将远程服务调用的方法交给了LoadBalancerClient去处理,从而达到了负载均衡的目的。