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

  1. @Configuration
  2. public class RestConfiguration {
  3. @Bean
  4. @LoadBalanced
  5. public RestTemplate restTemplate() {
  6. return new RestTemplate();
  7. }
  8. }
  1. /**
  2. * Annotation to mark a RestTemplate bean to be configured to use a LoadBalancerClient
  3. * @author Spencer Gibb
  4. */
  5. @Target({ ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD })
  6. @Retention(RetentionPolicy.RUNTIME)
  7. @Documented
  8. @Inherited
  9. @Qualifier
  10. public @interface LoadBalanced {
  11. }

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

LoadBalancer的自动化配置


  1. @Configuration
  2. @ConditionalOnClass(RestTemplate.class)
  3. @ConditionalOnBean(LoadBalancerClient.class)
  4. @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
  5. public class LoadBalancerAutoConfiguration {
  6.  
  7. @LoadBalanced
  8. @Autowired(required = false)
  9. private List<RestTemplate> restTemplates = Collections.emptyList();
  10.  
  11. @Bean
  12. public SmartInitializingSingleton loadBalancedRestTemplateInitializer(
  13. final List<RestTemplateCustomizer> customizers) {
  14. return new SmartInitializingSingleton() {
  15. @Override
  16. public void afterSingletonsInstantiated() {
  17. for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
  18. for (RestTemplateCustomizer customizer : customizers) {
  19. customizer.customize(restTemplate);
  20. }
  21. }
  22. }
  23. };
  24. }
  25.  
  26. @Autowired(required = false)
  27. private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
  28.  
  29. @Bean
  30. @ConditionalOnMissingBean
  31. public LoadBalancerRequestFactory loadBalancerRequestFactory(
  32. LoadBalancerClient loadBalancerClient) {
  33. return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
  34. }
  35.  
  36. @Configuration
  37. @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  38. static class LoadBalancerInterceptorConfig {
  39. @Bean
  40. public LoadBalancerInterceptor ribbonInterceptor(
  41. LoadBalancerClient loadBalancerClient,
  42. LoadBalancerRequestFactory requestFactory) {
  43. return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
  44. }
  45.  
  46. @Bean
  47. @ConditionalOnMissingBean
  48. public RestTemplateCustomizer restTemplateCustomizer(
  49. final LoadBalancerInterceptor loadBalancerInterceptor) {
  50. return new RestTemplateCustomizer() {
  51. @Override
  52. public void customize(RestTemplate restTemplate) {
  53. List<ClientHttpRequestInterceptor> list = new ArrayList<>(
  54. restTemplate.getInterceptors());
  55. list.add(loadBalancerInterceptor);
  56. restTemplate.setInterceptors(list);
  57. }
  58. };
  59. }
  60. }
  61. }

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

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

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

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

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

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

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

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

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

最开始我们说过,

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

  1. public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
  2. ILoadBalancer loadBalancer = getLoadBalancer(serviceId);
  3. Server server = getServer(loadBalancer);
  4. if (server == null) {
  5. throw new IllegalStateException("No instances available for " + serviceId);
  6. }
  7. RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(server,
  8. serviceId), serverIntrospector(serviceId).getMetadata(server));
  9.  
  10. return execute(serviceId, ribbonServer, request);
  11. }

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

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

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

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

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

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

  1. public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
  2. Server server = null;
  3. if(serviceInstance instanceof RibbonServer) {
  4. server = ((RibbonServer)serviceInstance).getServer();
  5. }
  6. if (server == null) {
  7. throw new IllegalStateException("No instances available for " + serviceId);
  8. }
  9. RibbonLoadBalancerContext context = this.clientFactory
  10. .getLoadBalancerContext(serviceId);
  11. RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
  12.     try {
  13. T returnVal = request.apply(serviceInstance);
  14. statsRecorder.recordStats(returnVal);
  15. return returnVal;
  16. }return null;
  17. }

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

  1. public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException {
  2. if (this.iterator.hasNext()) {
  3. ClientHttpRequestInterceptor nextInterceptor = this.iterator.next();
  4. return nextInterceptor.intercept(request, body, this);
  5. }
  6. else {
  7. ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod());
  8. for (Map.Entry<String, List<String>> entry : request.getHeaders().entrySet()) {
  9. List<String> values = entry.getValue();
  10. for (String value : values) {
  11. delegate.getHeaders().add(entry.getKey(), value);
  12. }
  13. }
  14. if (body.length > 0) {
  15. StreamUtils.copy(body, delegate.getBody());
  16. }
  17. return delegate.execute();
  18. }
  19. }

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

SpringCloud Ribbon的分析的更多相关文章

  1. SpringCloud Ribbon的分析(二)

    上文我们分析到 loadBalancer 根据具体的算法选择相应的server. protected Server getServer(ILoadBalancer loadBalancer) { if ...

  2. SpringCloud Feign的分析

    Feign是一个声明式的Web Service客户端,它使得编写Web Serivce客户端变得更加简单.我们只需要使用Feign来创建一个接口并用注解来配置它既可完成. @FeignClient(v ...

  3. Spring Cloud之负载均衡组件Ribbon原理分析

    目录 前言 一个问题引发的思考 Ribbon的简单使用 Ribbon 原理分析 @LoadBalanced 注解 @Qualifier注解 LoadBalancerAutoConfiguration ...

  4. springcloud Ribbon学习笔记二

    之前介绍了如何搭建eureka服务并开发了一个用户服务成功注册到了eureka中,接下来介绍如何通过ribbon来从eureka中获取用户服务: springcloud ribbon提供客户端的负载均 ...

  5. 客户端实现负载均衡:springCloud Ribbon的使用

    Netfilx发布的负载均衡器,是一个基于http.tcp的客户端负载均衡工具,具有控制http.tcp客户端的行为,为ribbon配置服务提供者的地址后,ribbon就 可以经过springClou ...

  6. springcloud ribbon的 @LoadBalanced注解

    在使用springcloud ribbon客户端负载均衡的时候,可以给RestTemplate bean 加一个@LoadBalanced注解,就能让这个RestTemplate在请求时拥有客户端负载 ...

  7. SpringCloud Ribbon 负载均衡 通过服务器名无法连接的神坑一个

    一,问题 采取eureka集群.客户端通过Ribbon调用服务,Ribbon端报下列异常 java.net.UnknownHostException: SERVICE-HI java.lang.Ill ...

  8. springcloud Ribbon学习笔记一

    上篇已经介绍了如何开发eureka服务并让多个服务进行相互注册,接下来记录如何开发一个服务然后注册到eureka中并能通过ribbon成功被调用 开发一个用户服务并注册到eureka中,用户服务负责访 ...

  9. springcloud源码分析(一)之采用redis实现注册中心

    注册中心 在分布式架构中注册中心起到了管理各种服务功能包括服务的注册.发现.熔断.负载.降级等功能,在分布式架构中起到了不可替代的作用.常见的注册中心有eureka,zookeeper等等,在spri ...

随机推荐

  1. 20181117-python第二章学习小结-part2

    浮点型补充: 有限小数与无限循环小数,不包括无理数! 小数点后面的数据运算太复杂,精确度不及整数! 尽量使用科学计数表示小数 列表学习(语法) 创建:[] list = []  #创建空表 list ...

  2. C++进阶:新人易入的那些坑 --1.常量、常指针和指针常量

    声明:以下内容B站/Youtube学习笔记,https://www.youtube.com/user/BoQianTheProgrammer/ Advanced C++. /* why use con ...

  3. springboot增删改查

    改https://blog.csdn.net/weixin_42338186/article/details/81561592 添加https://blog.csdn.net/weixin_42338 ...

  4. Tomcat手动部署Web项目详细步骤

    阅读须知:文章基于Tomcat8,其它版本若有差异,请自行辨别.本文为博主原创文章,转载请附原文链接. 不借助任何IDE,这里介绍在Tomcat中手动部署web项目的三种方式: 1.部署解包的weba ...

  5. windows下输入git用户名和密码错误,重新输入用户名和密码

    git clone https://YOUR_USERNAME@gitee.com.xxx.git将YOUR_USERNAME替换为该代码线的用户名,会弹出提示重新输入密码 每次windows提示输入 ...

  6. 最好的前端API备忘单整理

    注:这份表引自The best front-end hacking cheatsheets - all in one place Javascript ES2015 Cheatsheet JavaSc ...

  7. 2.postman安装及使用

    一.postman说明 postman是研发和测试进行接口调试的工具.可以用来很方便的模拟get或者post或者其他方式的请求来调试接口. 二.postman安装 ①作为谷歌浏览器插件安装 参考资料: ...

  8. ASP.NET C# 实现实时用户在线

    public static class UserOnline { /// <summary> /// 获取或设置在线列表 /// </summary> public stati ...

  9. 旧项目Makefile 迁移CMake的一种方法:include Makefile

    有些c++旧项目用Makefile,要迁移CMake的时候非常痛苦,有些像static pattern的语法和make自带命令 cmake要重写一套非常的麻烦. 因此这里用trick的方法实现了一种i ...

  10. 4.SSM整合_多表_多对多的增删改查

    多对多关系,课程和学生 接口 public interface CourseMapper { /** * 获取所有课程 * @return * @throws Exception */ public ...