【深入理解SpringCloud微服务】Ribbon源码解析
- Ribbon的原理
- RestTemplate中的拦截器链
- Ribbon的拦截器
- 如何将拦截器放入到RestTemplate中
- Ribbon中的核心类
- LoadBalancerAutoConfiguration
- LoadBalancerInterceptor
- LoadBalancerClient
- ILoadBalancer
- ServerList
- IRule
- IPing
- Ribbon核心源码解析
- LoadBalancerAutoConfiguration
- LoadBalancerInterceptor
- RibbonLoadBalancerClient
- ILoadBalancer
- request.apply(serviceInstance)
Ribbon的原理
RestTemplate中的拦截器链
在深入理解Ribbon之前,我们先要研究一下RestTemplate。RestTemplate是Spring提供的一个用于发送http请求的工具类。而在RestTemplate的父类InterceptingHttpAccessor里面有这么个东西:
public abstract class InterceptingHttpAccessor extends HttpAccessor {private final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();...
}
interceptors是RestTemplate里面的拦截器,拦截器类型是ClientHttpRequestInterceptor。当我们使用RestTemplate时,在RestTemplate真正发出http请求之前,请求会经过interceptors这个拦截器链处理。于是,我们可以通过给RestTemplate添加拦截器,对RestTemplate即将发送的请求做各种拦截处理。
Ribbon的拦截器
比如Ribbon的拦截器做的处理就是url重构,url中的域名部分原先是某微服务的服务名,重构后替换成真正的ip端口。
当然完整的流程应该是先通过负载均衡策略选出一个服务实例,然后再根据该实例的ip和port重构url。
如何将拦截器放入到RestTemplate中
在RestTemplate的父类InterceptingHttpAccessor中有一个getInterceptors() 方法:
public List<ClientHttpRequestInterceptor> getInterceptors() {return this.interceptors;}
可以直接获取到它的拦截器链,于是我们可以调用restTemplate.getInterceptors()获取到拦截器链,然后调用list.add(interceptor)把我们的拦截器interceptor添加到RestTemplate的拦截器链中。
经过上面这一套动作,就可以把我们的拦截器添加到RestTemplate,但是这一套需要有一个时机以及地点去触发它。而Ribbon就利用了Spring提供的SmartInitializingSingleton这个扩展点去触发。
public interface SmartInitializingSingleton {void afterSingletonsInstantiated();}
Spring会在完成了所有非懒加载单例bean的初始化之后调用注册到Spring容器中的所有SmartInitializingSingleton的afterSingletonsInstantiated()方法。于是Ribbon实现了一个SmartInitializingSingleton并注册到Spring中,在afterSingletonsInstantiated()方法中把Ribbon自己的拦截器设置到RestTemplate中。
Ribbon中的核心类
Ribbon中的核心类一共就以下那么几个:
LoadBalancerAutoConfiguration
LoadBalancerAutoConfiguration是Ribbon的自动配置类,会被SpringBoot的自动装配机制加载。LoadBalancerAutoConfiguration利用Spring的扩展点SmartInitializingSingleton设置Ribbon自己的拦截器LoadBalancerInterceptor到RestTemplate的拦截器链中。
LoadBalancerInterceptor
LoadBalancerInterceptor是Ribbon的拦截器,会被添加到RestTemplate的拦截器链中。当调用RestTemplate发起http请求时,请求会经过该拦截器进行负载均衡以及url重写的处理。
LoadBalancerClient
LoadBalancerClient是Ribbon的负载均衡客户端,会被LoadBalancerInterceptor调用,负载均衡以及url重写的工作其实是由LoadBalancerClient完成的。
当LoadBalancerInterceptor拿到原始url时,会从url中取出服务名serviceName(也就是域名部分),然后把serviceName传给LoadBalancerClient。LoadBalancerClient根据serviceName取得对应的ILoadBalancer,调用ILoadBalancer进行负载均衡处理,ILoadBalancer返回负载均衡选出的一个服务实例并返回。LoadBalancerClient拿到返回的服务实例,会把url中的域名(也就是服务名serviceName)改写为该实例的ip和port。
ILoadBalancer
ILoadBalancer是Ribbon的负载均衡器,ILoadBalancer是与服务名一一对应的,也就是每一个服务名对应一个ILoadBalancer,不同服务间的ILoadBalancer互不相干。LoadBalancerClient会通过服务名作为key从loadBalancerMap中获取对应的ILoadBalancer负载均衡器,通过ILoadBalancer的负载均衡选出一个服务实例。
在ILoadBalancer的实现类BaseLoadBalancer有四个比较重要的字段:
- List<Server> allServerList:所有的服务实例
- List<Server> upServerList:在线的服务实例
- IRule rule:负载均衡策略
- IPing ping:用于ping一个服务实例以检查是否存活的工具
ILoadBalancer使用IRule负载均衡策略从upServerList选出一个服务实例Server。然后ILoadBalancer内部有个定时任务通过IPing去ping在allServerList中的每个服务实例,得出所有在线的Server,然后更新upServerList。
然后在BaseLoadBalancer的子类DynamicServerListLoadBalancer中还有一个重要的字段:
- ServerList<T> serverListImpl:向注册中心拉取服务实例列表的工具
DynamicServerListLoadBalancer有一个定时任务从调用serverListImpl拉取最新的服务实例列表,更新到allServerList中。
于是这ILoadBalancer中的这五者就是以下这样的关系:
ServerList
ServerList是一个用于从注册中心拉取服务实例列表的接口,不同的注册中心客户端会有不同的ServerList实现,比如nacos的NacosServerList就是通过NamingService去拉取。
IRule
IRule是Ribbon的负载均衡规则接口,不同的负载均衡策略有不同的IRule实现。
- RandomRule:随机选择一个Server
- RoundRobinRule:轮询选择
- WeightedResponseTimeRule:根据响应时间加权的轮询策略,响应时间越长,权重越小
- BestAvailableRule:最小并发优先策略,在众多Server中选取并发数最小的
- ZoneAvoidanceRule:默认的负载均衡策略,在有区域的环境下,会复合判断Server所在区域的性能和Server的可用性选择Server,在没有区域的环境下,就是轮询,因此默认的负载均衡策略就是轮询
IPing
Iping接口就是用于判断一个Server是否可用的接口,有一个boolean isAlive(Server server)方法需要实现,判断当前这个server是否可用。
Ribbon核心源码解析
LoadBalancerAutoConfiguration
@Configuration(...)
...
public class LoadBalancerAutoConfiguration {// 把被@LoadBalanced注解修饰的RestTemplate通过Spring自动注入的方式收集到这里@LoadBalanced@Autowired(required = false)private List<RestTemplate> restTemplates = Collections.emptyList();...// 通过Spring提供的SmartInitializingSingleton扩展点,// 调用RestTemplateCustomizer把LoadBalancerInterceptor放入RestTemplate的拦截器链中@Beanpublic SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {return () -> restTemplateCustomizers.ifAvailable(customizers -> {for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {for (RestTemplateCustomizer customizer : customizers) {customizer.customize(restTemplate);}}});}...// Ribbon的负载均衡拦截器,会被放入到RestTemplate的拦截器链中@Beanpublic LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient,LoadBalancerRequestFactory requestFactory) {return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);}// Ribbon通过RestTemplateCustomizer// 把LoadBalancerInterceptor放入到RestTemplate的拦截器链中@Bean@ConditionalOnMissingBeanpublic RestTemplateCustomizer restTemplateCustomizer(final LoadBalancerInterceptor loadBalancerInterceptor) {return restTemplate -> {List<ClientHttpRequestInterceptor> list = new ArrayList<>(restTemplate.getInterceptors());list.add(loadBalancerInterceptor);restTemplate.setInterceptors(list);};}...}
LoadBalancerAutoConfiguration中配置了许多bean,但是重点就是以上三个。LoadBalancerAutoConfiguration通过Spring的扩展点SmartInitializingSingleton调用RestTemplateCustomizer,通过RestTemplateCustomizer把LoadBalancerInterceptor放入到RestTemplate的拦截器链中。
LoadBalancerInterceptor
RestTemplate的拦截器链中有了LoadBalancerInterceptor之后,当我们使用RestTemplate发起http请求时,就会经过LoadBalancerInterceptor的拦截处理。
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {// 负载均衡客户端private LoadBalancerClient loadBalancer;// Request对象工厂 private LoadBalancerRequestFactory requestFactory;...// RestTemplate发起http请求,会经过LoadBalancerInterceptor的intercept方法拦截处理@Overridepublic ClientHttpResponse intercept(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) throws IOException {final URI originalUri = request.getURI();String serviceName = originalUri.getHost();// LoadBalancerInterceptor的intercept方法,调用LoadBalancerClient的execute方法。// 传入了服务名serviceName,requestFactory创建的Request对象return this.loadBalancer.execute(serviceName,this.requestFactory.createRequest(request, body, execution));}}
RestTemplate发起的http请求会被LoadBalancerInterceptor的intercept方法拦截,LoadBalancerInterceptor的intercept方法从url中取出域名部分作为服务名serviceName,然后调用LoadBalancerClient的execute方法。
RibbonLoadBalancerClient
LoadBalancerClient接口的实现类是RibbonLoadBalancerClient。
public class RibbonLoadBalancerClient implements LoadBalancerClient {...public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint)throws IOException {// 根据服务名(serviceId就是服务名),取得对应的ILoadBalancerILoadBalancer loadBalancer = getLoadBalancer(serviceId);// getServer方法里面通过ILoadBalancer选出一个服务实例ServerServer server = getServer(loadBalancer, hint);...// Server包装为RibbonServerRibbonServer ribbonServer = new RibbonServer(serviceId, server, ...);// 调用execute方法(重写url,发起请求)return execute(serviceId, ribbonServer, request);}...@Overridepublic <T> T execute(String serviceId, ServiceInstance serviceInstance,LoadBalancerRequest<T> request) throws IOException {...// 调用LoadBalancerRequest的apply方法(重写url,发起请求)T returnVal = request.apply(serviceInstance);...}...protected Server getServer(ILoadBalancer loadBalancer, Object hint) {// 调用ILoadBalancer的chooseServer方法选取一个服务实例return loadBalancer.chooseServer(hint != null ? hint : "default");}...}
RibbonLoadBalancerClient首先调用getLoadBalancer(serviceId),根据服务名(serviceId就是服务名),取得对应的ILoadBalancer。然后通过ILoadBalancer的chooseServer方法选取一个服务实例Server。最后选出的服务实例作为参数,调用LoadBalancerRequest的apply方法进行下一步操作(重写url,发起请求)
ILoadBalancer
我们看看ILoadBalancer中的chooseServer方法是如何选出实例的。默认的ILoadBalancer就是ZoneAwareLoadBalancer,由于我们一般不会配置zone(区域),所以最后还是会进入父类BaseLoadBalancer的chooseServer方法。
public Server chooseServer(Object key) {...return rule.choose(key);...}
就是调用IRule的choose方法,默认就是轮询策略。
request.apply(serviceInstance)
我们再看看上面request.apply(serviceInstance)这行代码里面的逻辑。
这里的request是LoadBalancerRequestFactory的createRequest创建的。
public LoadBalancerRequest<ClientHttpResponse> createRequest(final HttpRequest request, final byte[] body,final ClientHttpRequestExecution execution) {return instance -> {// 以选出的服务实例对象instance作为参数创建了一个ServiceRequestWrapper对象HttpRequest serviceRequest = new ServiceRequestWrapper(request, instance,this.loadBalancer);...return execution.execute(serviceRequest, body);};}
可以看到LoadBalancerRequestFactory返回的request对象的apply方法里面,以选出的服务实例对象instance作为参数创建了一个ServiceRequestWrapper对象。然后调用execution.execute(serviceRequest, body)方法。
public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {...// request.getURI()调用到ServiceRequestWrapper的getURI()方法,里面会重写url,然后返回一个重写了url的URI对象// 然后这里的requestFactory(ClientHttpRequestFactory类型)拿着重写后的URI对象,创建一个新的ClientHttpRequest对象ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), method);...// 调用ClientHttpRequest的execute()方法真正发起请求,返回ClientHttpResponse(响应结果)return delegate.execute();}
我们看一下ServiceRequestWrapper的getURI()方法。
@Overridepublic URI getURI() {// 调用LoadBalancerClient的reconstructURI方法(这里的loadBalancer是LoadBalancerClient类型)URI uri = this.loadBalancer.reconstructURI(this.instance, getRequest().getURI());return uri;}
所以总结起来request.apply(serviceInstance)里面就是调用LoadBalancerClient的reconstructURI方法重写url,然后调用ClientHttpRequestFactory的createRequest方法重新创建一个Request对象,然后调用该Request对象的execute()方法真正发起http请求。