SpringCloud Ribbon的分析

发布日期:2019-07-09

  Spring Cloud Ribbon主要用于客户端的负载均衡。最基本的用法便是使用RestTemplate进行动态的负载均衡。我们只需要加入如下的配置便能完成客户端的负载均衡。

@Configurationpublic class RestConfiguration { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); }}

/** * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient * @author Spencer Gibb */@Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@Qualifierpublic @interface LoadBalanced {}

 

这里的@LoadBalanced使得RestTemplate可以使用LoadBalancerClient,LoadBalancerClient在这个路劲下,还存在着LoadBalancerAutoConfiguration这个配置类只要用于对LoadBalancerClient做出配置。我们就以此为入口,开始分析ribbon

 

LoadBalancer的自动化配置


@Configuration@ConditionalOnClass(RestTemplate.class)@ConditionalOnBean(LoadBalancerClient.class)@EnableConfigurationProperties(LoadBalancerRetryProperties.class)public class LoadBalancerAutoConfiguration { @LoadBalanced @Autowired(required = false) private List<RestTemplate> restTemplates = Collections.emptyList(); @Bean public SmartInitializingSingleton loadBalancedRestTemplateInitializer( final List<RestTemplateCustomizer> customizers) { return new SmartInitializingSingleton() { @Override public void afterSingletonsInstantiated() { for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) { for (RestTemplateCustomizer customizer : customizers) { customizer.customize(restTemplate); } } } }; } @Autowired(required = false) private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList(); @Bean @ConditionalOnMissingBean public LoadBalancerRequestFactory loadBalancerRequestFactory( LoadBalancerClient loadBalancerClient) { return new LoadBalancerRequestFactory(loadBalancerClient, transformers); } @Configuration @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate") static class LoadBalancerInterceptorConfig { @Bean public LoadBalancerInterceptor ribbonInterceptor( LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) { return new LoadBalancerInterceptor(loadBalancerClient, requestFactory); } @Bean @ConditionalOnMissingBean public RestTemplateCustomizer restTemplateCustomizer( final LoadBalancerInterceptor loadBalancerInterceptor) { return new RestTemplateCustomizer() { @Override public void customize(RestTemplate restTemplate) { List<ClientHttpRequestInterceptor> list = new ArrayList<>( restTemplate.getInterceptors()); list.add(loadBalancerInterceptor); restTemplate.setInterceptors(list); } }; } }}

 这个配置类主要做了以下几件事

1.创建了一个LoadBalancerInterceptor的bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。

2.创建了一个RestTemplateCustomizer的bean,用于给RestTemplate增加LoadBalancerInterceptor拦截器。

3.维护一个@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行维护,通过调用RestTemplateCustomizer的实例来给需要的客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。

 

现在我们看下 LoadBalancerInterceptor 的拦截,我们在这里打上断点,查看一个 RestTemplate 请求是怎么被拦截的。

我们在 org.springframework.http.client.InterceptingClientHttpRequest.InterceptingRequestExecution#execute 发现了如下代码

if (this.iterator.hasNext()) { ClientHttpRequestInterceptor nextInterceptor = this.iterator.next(); return nextInterceptor.intercept(request, body, this); }

 这里和mybatis比较类似,都是通过责任链模式,一层层的拦截。最终就会到LoadBalancerInterceptor。现在再看LoadBalancerInterceptor的intercept方法

前两步是分别获取这个request的请求地址和ServiceName,这里截图供参考

最开始我们说过,

@LoadBalanced使得RestTemplate可以使用LoadBalancerClient,就是在这里使用LoadBalancerClient的executor方法,做出具体的负载均衡。由于这里的LoadBalancerClient是一个接口,他具体的实现类是RibbonLoadBalancerClient,我们在这里分析具体的execute方法

public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException { ILoadBalancer loadBalancer = getLoadBalancer(serviceId); Server server = getServer(loadBalancer); if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server, serviceId), serverIntrospector(serviceId).getMetadata(server)); return execute(serviceId, ribbonServer, request); }

 

第一步获取 loadBalancer,loadBalancer是定义软件负载均衡器操作的接口,有以下方法。默认配置的是使用 ZoneAwareLoadBalancer 。

public interface ILoadBalancer { //向负载均衡器中维护的实例列表增加服务实例 public void addServers(List<Server> newServers); //从负载均衡器中挑选出一个具体的服务实例 public Server chooseServer(Object key); //用来通知和标记负载均衡器中的某个具体实例已经停止服务,不然负载均衡器在下一次获取服务实例清单前都会认为服务实例均是正常服务的。 public void markServerDown(Server server); //获取当前正常服务的实例列表 public List<Server> getReachableServers(); //获取所有已知的服务实例列表,包括正常服务和停止服务实例。 public List<Server> getAllServers();}

 

 

第二步是根据一定的算法去获取server,这一步也是整个ribbon的核心,关于具体的算法逻辑,我们后面分析

protected Server getServer(ILoadBalancer loadBalancer) { if (loadBalancer == null) { return null; } return loadBalancer.chooseServer("default"); // TODO: better handling of key }

 

第三步 在获取到Server后包装成一个 RibbonServer

 

一个具体的server被选出来后,便可以接着请求,这时会将剩下的拦截器走完

public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException { Server server = null; if(serviceInstance instanceof RibbonServer) { server = ((RibbonServer)serviceInstance).getServer(); } if (server == null) { throw new IllegalStateException("No instances available for " + serviceId); } RibbonLoadBalancerContext context = this.clientFactory .getLoadBalancerContext(serviceId); RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);    try { T returnVal = request.apply(serviceInstance); statsRecorder.recordStats(returnVal); return returnVal; }return null; }

 

最终,创建出一个具体的请求并执行。

public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException { if (this.iterator.hasNext()) { ClientHttpRequestInterceptor nextInterceptor = this.iterator.next(); return nextInterceptor.intercept(request, body, this); } else { ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod()); for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) { List<String> values = entry.getValue(); for (String value : values) { delegate.getHeaders().add(entry.getKey(), value); } } if (body.length > 0) { StreamUtils.copy(body, delegate.getBody()); } return delegate.execute(); } }

 

以上就是对ribbon大致流程的分析