1 负载均衡概述

1.1 什么是负载均衡?

  • 在搭建网站的时候,如果单节点的web服务性能和可靠性都无法达到要求;或者是在使用外网服务的时候,经常担心被人攻破,一不小心就会有打开外网端口的请求,通常这个时候加入负载均衡就能有效解决服务问题。
  • 负载均衡是一种基础的网络服务,其原理是通过运行在前面的负载均衡服务,按照指定的负载均衡算法,将流量分配到后端服务集群上,从而为系统提供并行扩展的能力。
  • 负载均衡的应用场景包括流量包、转发规则以及后端服务,由于该服务有内外网个例、健康检查等功能,能够有效提供系统的安全性和可用性。

什么是负载均衡 (来自DESKTOP-63B3E9H的冲突副本 2020-09-24_18.54.43).png

1.2 客户端负载均衡和服务端负载均衡

1.2.1 客户端负载均衡

  • 客户端会有一个服务器地址列表,在发送请求前通过负载均衡算法选择一个服务器,进行访问;即在客户端进行负载均衡算法分配。
  • 典型应用:Ribbon是客户端负载均衡。

1.2.2 服务端负载均衡

  • 先发送请求到负载均衡服务器或软件,然后通过负载均衡算法,在多个服务器之间选择一个进行访问;即在服务器端进行负载均衡算法分配。
  • 典型应用:
  • 负载均衡Ribbon高级(此版本不过时,后面的版本推荐使用Spring Cloud Loadbalancer) - 图2硬件:F5等。
  • 负载均衡Ribbon高级(此版本不过时,后面的版本推荐使用Spring Cloud Loadbalancer) - 图3软件:Nginx等。

2 基于Ribbon实现负载均衡

2.1 搭建环境

  • 准备两个商品微服务(端口分别是9001和9011)让其注册到Eureka集群中。
  • 商品微服务9001的application.yml:
  1. server:
  2. port: 9001 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-product # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. # 配置 eureka
  17. eureka:
  18. instance:
  19. # 主机名称:服务名称修改,其实就是向eureka server中注册的实例id
  20. instance-id: service-product:9001
  21. # 显示IP信息
  22. prefer-ip-address: true
  23. client:
  24. service-url: # 此处修改为 Eureka Server的集群地址
  25. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  26. # 微服务info内容详细信息
  27. info:
  28. app.name: xxx
  29. company.name: xxx
  30. build.artifactId: $project.artifactId$
  31. build.version: $project.version$
  • 商品微服务9011的application.yml:
  1. server:
  2. port: 9011 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-product # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. # 配置 eureka
  17. eureka:
  18. instance:
  19. # 主机名称:服务名称修改,其实就是向eureka server中注册的实例id
  20. instance-id: service-product:9011
  21. # 显示IP信息
  22. prefer-ip-address: true
  23. client:
  24. service-url: # 此处修改为 Eureka Server的集群地址
  25. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/,http://eureka7003.com:7003/eureka/
  26. # 微服务info内容详细信息
  27. info:
  28. app.name: xxx
  29. company.name: xxx
  30. build.artifactId: $project.artifactId$
  31. build.version: $project.version$
  • 商品微服务9001和9011的ProductController.java
  1. package com.sunxiaping.product.controller;
  2. import com.sunxiaping.product.domain.Product;
  3. import com.sunxiaping.product.service.ProductService;
  4. import org.springframework.beans.factory.annotation.Autowired;
  5. import org.springframework.beans.factory.annotation.Value;
  6. import org.springframework.web.bind.annotation.*;
  7. @RestController
  8. @RequestMapping(value = "/product")
  9. public class ProductController {
  10. @Autowired
  11. private ProductService productService;
  12. @Value("${server.port}")
  13. private String port;
  14. @Value("${spring.cloud.client.ip-address}")
  15. private String ip;
  16. @PostMapping(value = "/save")
  17. public String save(@RequestBody Product product) {
  18. productService.save(product);
  19. return "新增成功";
  20. }
  21. @GetMapping(value = "/findById/{id}")
  22. public Product findById(@PathVariable(value = "id") Long id) {
  23. Product product = productService.findById(id);
  24. product.setProductName("访问的地址是:" + ip + ":" + port);
  25. return product;
  26. }
  27. }
  • 订单微服务的OrderController.java
  1. package com.sunxiaping.order.controller;
  2. import com.sunxiaping.order.domain.Product;
  3. import org.springframework.beans.factory.annotation.Autowired;
  4. import org.springframework.cloud.client.discovery.DiscoveryClient;
  5. import org.springframework.web.bind.annotation.GetMapping;
  6. import org.springframework.web.bind.annotation.PathVariable;
  7. import org.springframework.web.bind.annotation.RequestMapping;
  8. import org.springframework.web.bind.annotation.RestController;
  9. import org.springframework.web.client.RestTemplate;
  10. @RestController
  11. @RequestMapping(value = "/order")
  12. public class OrderController {
  13. @Autowired
  14. private RestTemplate restTemplate;
  15. /**
  16. * SpringCloud提供的获取元数据的工具类
  17. * 调用方法获取服务的元数据
  18. */
  19. @Autowired
  20. private DiscoveryClient discoveryClient;
  21. /**
  22. * 基于Ribbon的形式调用远程的微服务
  23. *
  24. * @param id
  25. * @return
  26. */
  27. @GetMapping(value = "/buy/{id}")
  28. public Product buy(@PathVariable(value = "id") Long id) {
  29. Product product = restTemplate.getForObject("http://service-product/product/findById/" + id, Product.class);
  30. return product;
  31. }
  32. }

基于Ribbon实现客户端的负载均衡.gif

2.2 Ribbon内置的负载均衡策略

2.2.1 Ribbon内置的负载均衡策略的概述

  • Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶层接口为com.netflix.loadbalancer.IRule,实现方式如下:

com.netflix.loadbalancer.IRule的实现.png

  • com.netflix.loadbalancer.RoundRobinRule:以轮询的方式进行负载均衡。
  • com.netflix.loadbalancer.RandomRule:随机策略。
  • com.netflix.loadbalancer.RetryRule:重试策略。
  • com.netflix.loadbalancer.WeightedResponseTimeRule:权重策略。会计算每个服务的权重,权重越高的被调用的可能性会越大。
  • com.netflix.loadbalancer.BestAvailableRule:最佳策略。遍历所有的服务实例,过滤掉故障实例,并将请求数量最小的实例返回。
  • com.netflix.loadbalancer.AvailabilityFilteringRule:可用过滤策略。过滤掉故障和请求数超过阈值的服务实例,再从剩下的实例中轮询调用。

2.2.2 Ribbon内置的负载均衡策略的使用一

  • 在服务消费者的application.yml中修改负载均衡策略:
  1. # 修改ribbon的负载均衡策略 服务名 - ribbon - NFLoadBalancerRuleClassName :负载均衡策略
  2. service-product:
  3. ribbon:
  4. NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的负载均衡策略为权重策略
  • 消息者完整的application.yml:
  1. server:
  2. port: 9002 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-order # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. # 配置Eureka
  17. eureka:
  18. instance:
  19. # 实例的名称
  20. instance-id: service-order:9002
  21. # 显示IP信息
  22. prefer-ip-address: true
  23. lease-renewal-interval-in-seconds: 5 # 发送心跳续约间隔(默认30秒)
  24. lease-expiration-duration-in-seconds: 10 # Eureka Client发送心跳给Eureka Server端后,续约到期时间(默认90秒)
  25. client:
  26. healthcheck:
  27. enabled: true
  28. service-url: # Eureka Server的地址
  29. # defaultZone: http://localhost:9000/eureka/
  30. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  31. # 修改ribbon的负载均衡策略 服务名 - ribbon - NFLoadBalancerRuleClassName :负载均衡策略
  32. service-product:
  33. ribbon:
  34. NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的负载均衡策略为权重策略
  35. # 微服务info内容详细信息
  36. info:
  37. app.name: xxx
  38. company.name: xxx
  39. build.artifactId: $project.artifactId$
  40. build.version: $project.version$

修改Ribbon的负载均衡策略为随机策略.gif

2.2.3 Ribbon内置的负载均衡策略的使用二

Ribbon内置的负载均衡策略的使用.png

  • 在启动类扫描不到的地方新建一个自定义的Rule配置类(比如启动类的包名是com.sunxiaping.order,而自定义Rule配置类的包名是com.sunxiaping.ribbon.rule)。
  1. package com.sunxiaping.ribbon.rule;
  2. import com.netflix.loadbalancer.IRule;
  3. import com.netflix.loadbalancer.RandomRule;
  4. import org.springframework.context.annotation.Bean;
  5. import org.springframework.context.annotation.Configuration;
  6. @Configuration
  7. public class SelfRule {
  8. /**
  9. * 替换Ribbon内置的负载均衡策略
  10. *
  11. * @return
  12. */
  13. @Bean
  14. public IRule iRule() {
  15. return new RandomRule();
  16. }
  17. }
  • 在启动类上标注@RibbonClient注解:
  1. package com.sunxiaping.order;
  2. import com.sunxiaping.ribbon.rule.SelfRule;
  3. import org.springframework.boot.SpringApplication;
  4. import org.springframework.boot.autoconfigure.SpringBootApplication;
  5. import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
  6. import org.springframework.cloud.netflix.ribbon.RibbonClient;
  7. @SpringBootApplication
  8. @EnableEurekaClient
  9. //在启动类上标注@RibbonClient注解,这样该微服务启动的时候就能去加载自定义的Ribbon配置类,从而使得配置生效
  10. @RibbonClient(name = "service-product",configuration = SelfRule.class)
  11. public class OrderApplication {
  12. public static void main(String[] args) {
  13. SpringApplication.run(OrderApplication.class, args);
  14. }
  15. }

2.2.4 Ribbon的策略选择

  • 如果每个机器配置一样,建议不修改策略(推荐)。
  • 如果部分机器配置强,则可以改为WeightedResponseTimeRule。

2.3 请求重试机制

  • 在实际生产环境中,Ribbon做客户端负载均衡的时候,Ribbon默认的负载均衡算法是轮询,一旦访问到的那台微服务提供者突然宕机了,此时就会出现404的情况,这时可以使用Ribbon的请求重试机制,Ribbon的请求重试机制基于Spring的retry(Spring的重试框架)。
  • 使用:
  • 负载均衡Ribbon高级(此版本不过时,后面的版本推荐使用Spring Cloud Loadbalancer) - 图8在微服务消费者导入spring-retry的Maven坐标:
  1. <dependency>
  2. <groupId>org.springframework.retry</groupId>
  3. <artifactId>spring-retry</artifactId>
  4. </dependency>
  • 负载均衡Ribbon高级(此版本不过时,后面的版本推荐使用Spring Cloud Loadbalancer) - 图9修改微服务消费者的application.yml:
  • 修改部分:
  1. # Ribbon的重试机制
  2. service-product:
  3. ribbon:
  4. # 修改ribbon的负载均衡策略 服务名 - ribbon - NFLoadBalancerRuleClassName :负载均衡策略
  5. # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的负载均衡策略为权重策略
  6. # Ribbon的重试机制参数
  7. ConnectTimeout: 250 # Ribbon的连接超时时间
  8. ReadTimeout: 1000 # Ribbon的数据读取超时时间
  9. OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
  10. MaxAutoRetriesNextServer: 2 # 切换实例的重试次数
  11. MaxAutoRetries: 1 # 对当前实例的重试次数
  • 完整部分:
  1. server:
  2. port: 9002 # 微服务的端口号
  3. spring:
  4. application:
  5. name: service-order # 微服务的名称
  6. datasource:
  7. url: jdbc:mysql://192.168.1.57:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true
  8. driver-class-name: com.mysql.cj.jdbc.Driver
  9. username: root
  10. password: 123456
  11. jpa:
  12. generate-ddl: true
  13. show-sql: true
  14. open-in-view: true
  15. database: mysql
  16. # 配置Eureka
  17. eureka:
  18. instance:
  19. # 实例的名称
  20. instance-id: service-order:9002
  21. # 显示IP信息
  22. prefer-ip-address: true
  23. lease-renewal-interval-in-seconds: 5 # 发送心跳续约间隔(默认30秒)
  24. lease-expiration-duration-in-seconds: 10 # Eureka Client发送心跳给Eureka Server端后,续约到期时间(默认90秒)
  25. client:
  26. healthcheck:
  27. enabled: true
  28. service-url: # Eureka Server的地址
  29. # defaultZone: http://localhost:9000/eureka/
  30. defaultZone: http://eureka7001.com:7001/eureka/,http://eureka7002.com:7002/eureka/
  31. # Ribbon的重试机制
  32. service-product:
  33. ribbon:
  34. # 修改ribbon的负载均衡策略 服务名 - ribbon - NFLoadBalancerRuleClassName :负载均衡策略
  35. # NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 修改ribbon的负载均衡策略为权重策略
  36. # Ribbon的重试机制参数
  37. ConnectTimeout: 250 # Ribbon的连接超时时间
  38. ReadTimeout: 1000 # Ribbon的数据读取超时时间
  39. OkToRetryOnAllOperations: true # 是否对所有操作都进行重试
  40. MaxAutoRetriesNextServer: 2 # 切换实例的重试次数
  41. MaxAutoRetries: 1 # 对当前实例的重试次数
  42. # 微服务info内容详细信息
  43. info:
  44. app.name: xxx
  45. company.name: xxx
  46. build.artifactId: $project.artifactId$
  47. build.version: $project.version$
  48. # 开启日志debug
  49. logging:
  50. level:
  51. root: debug

3 Ribbon中负载均衡的源码分析

3.1 Ribbon中的关键组件

Ribbon中的关键组件.jpg

  • ServerList:可以响应客户端的特定服务的服务器列表。
  • ServerListFilter:可以动态获取具有所需特征的候选服务器列表的过滤器。
  • ServetListUpdater:用于执行动态服务器的列表更新。
  • Rule:负载均衡策略,用于确定从服务器列表返回哪个服务器。
  • Ping:客户端用于快速检查服务器当时是否处于活动状态。
  • LoaderBalancer:负载均衡器,负载负载均衡调度的管理。

3.2 @LoadBalanced注解

  • 使用Ribbon完成客户端负载均衡往往是从一个注解开始的:
  1. package com.sunxiaping.order.config;
  2. import org.springframework.cloud.client.loadbalancer.LoadBalanced;
  3. import org.springframework.context.annotation.Bean;
  4. import org.springframework.context.annotation.Configuration;
  5. import org.springframework.web.client.RestTemplate;
  6. @Configuration
  7. public class SpringConfig {
  8. @Bean
  9. @LoadBalanced
  10. public RestTemplate restTemplate() {
  11. return new RestTemplate();
  12. }
  13. }
  • @LoadBalanced注解的源码如下:
  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做标记,方便我们对RestTemplate添加一个LoadBalancerClient,以实现客户端负载均衡。

3.3 自动装配

  • 根据SpringBoot中的自动装配规则可以在spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar中找到spring.factories,内容如下:
  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration
  • RibbonAutoConfiguration的部分源码如下:
  1. @Configuration
  2. @Conditional({RibbonAutoConfiguration.RibbonClassesConditions.class})
  3. @RibbonClients
  4. @AutoConfigureAfter(
  5. name = {"org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration"}
  6. )
  7. @AutoConfigureBefore({LoadBalancerAutoConfiguration.class, AsyncLoadBalancerAutoConfiguration.class})
  8. @EnableConfigurationProperties({RibbonEagerLoadProperties.class, ServerIntrospectorProperties.class})
  9. public class RibbonAutoConfiguration {
  10. @Autowired(
  11. required = false
  12. )
  13. private List<RibbonClientSpecification> configurations = new ArrayList();
  14. @Autowired
  15. private RibbonEagerLoadProperties ribbonEagerLoadProperties;
  16. public RibbonAutoConfiguration() {
  17. }
  18. //其他略
  19. }
  • 可以知道RibbonAutoConfiguration引入了LoadBalancerAutoConfiguration配置类。

3.4 负载均衡调用

  • LoadBalancerAutoConfiguration的源码如下:
  1. @Configuration
  2. @ConditionalOnClass(RestTemplate.class)
  3. @ConditionalOnBean(LoadBalancerClient.class)
  4. @EnableConfigurationProperties(LoadBalancerRetryProperties.class)
  5. public class LoadBalancerAutoConfiguration {
  6. @LoadBalanced
  7. @Autowired(required = false)
  8. private List<RestTemplate> restTemplates = Collections.emptyList();
  9. @Bean
  10. public SmartInitializingSingleton loadBalancedRestTemplateInitializerDeprecated(
  11. final ObjectProvider<List<RestTemplateCustomizer>> restTemplateCustomizers) {
  12. return () -> restTemplateCustomizers.ifAvailable(customizers -> {
  13. for (RestTemplate restTemplate : LoadBalancerAutoConfiguration.this.restTemplates) {
  14. for (RestTemplateCustomizer customizer : customizers) {
  15. customizer.customize(restTemplate);
  16. }
  17. }
  18. });
  19. }
  20. @Autowired(required = false)
  21. private List<LoadBalancerRequestTransformer> transformers = Collections.emptyList();
  22. @Bean
  23. @ConditionalOnMissingBean
  24. public LoadBalancerRequestFactory loadBalancerRequestFactory(
  25. LoadBalancerClient loadBalancerClient) {
  26. return new LoadBalancerRequestFactory(loadBalancerClient, transformers);
  27. }
  28. @Configuration
  29. @ConditionalOnMissingClass("org.springframework.retry.support.RetryTemplate")
  30. static class LoadBalancerInterceptorConfig {
  31. //①创建LoadBalancerInterceptor的Bean,用于实现对客户端请求进行拦截,以实现客户端负载均衡
  32. @Bean
  33. public LoadBalancerInterceptor ribbonInterceptor(
  34. LoadBalancerClient loadBalancerClient,
  35. LoadBalancerRequestFactory requestFactory) {
  36. return new LoadBalancerInterceptor(loadBalancerClient, requestFactory);
  37. }
  38. //②创建RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadBalancerInterceptor烂机器。
  39. @Bean
  40. @ConditionalOnMissingBean
  41. public RestTemplateCustomizer restTemplateCustomizer(
  42. final LoadBalancerInterceptor loadBalancerInterceptor) {
  43. return restTemplate -> {
  44. //③维护了一个被@LoadBalanced注解修饰的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器
  45. List<ClientHttpRequestInterceptor> list = new ArrayList<>(
  46. restTemplate.getInterceptors());
  47. list.add(loadBalancerInterceptor);
  48. restTemplate.setInterceptors(list);
  49. };
  50. }
  51. }
  52. @Configuration
  53. @ConditionalOnClass(RetryTemplate.class)
  54. public static class RetryAutoConfiguration {
  55. @Bean
  56. @ConditionalOnMissingBean
  57. public LoadBalancedRetryFactory loadBalancedRetryFactory() {
  58. return new LoadBalancedRetryFactory() {};
  59. }
  60. }
  61. @Configuration
  62. @ConditionalOnClass(RetryTemplate.class)
  63. public static class RetryInterceptorAutoConfiguration {
  64. @Bean
  65. @ConditionalOnMissingBean
  66. public RetryLoadBalancerInterceptor ribbonInterceptor(
  67. LoadBalancerClient loadBalancerClient, LoadBalancerRetryProperties properties,
  68. LoadBalancerRequestFactory requestFactory,
  69. LoadBalancedRetryFactory loadBalancedRetryFactory) {
  70. return new RetryLoadBalancerInterceptor(loadBalancerClient, properties,
  71. requestFactory, loadBalancedRetryFactory);
  72. }
  73. @Bean
  74. @ConditionalOnMissingBean
  75. public RestTemplateCustomizer restTemplateCustomizer(
  76. final RetryLoadBalancerInterceptor loadBalancerInterceptor) {
  77. return restTemplate -> {
  78. List<ClientHttpRequestInterceptor> list = new ArrayList<>(
  79. restTemplate.getInterceptors());
  80. list.add(loadBalancerInterceptor);
  81. restTemplate.setInterceptors(list);
  82. };
  83. }
  84. }
  85. }
  • 在该自动配置类中,主要做如下的三件事:
  • 负载均衡Ribbon高级(此版本不过时,后面的版本推荐使用Spring Cloud Loadbalancer) - 图11创建一个LoadBalancerInterceptor的Bean,用于实现对客户端发起请求时进行拦截,以实现客户端负载均衡。
  • 负载均衡Ribbon高级(此版本不过时,后面的版本推荐使用Spring Cloud Loadbalancer) - 图12创建了一个RestTemplateCustomizer的Bean,用于给RestTemplate增加LoadBalancerInterceptor拦截器。
  • 负载均衡Ribbon高级(此版本不过时,后面的版本推荐使用Spring Cloud Loadbalancer) - 图13维护了一个被@LoadBalanced注解修改的RestTemplate对象列表,并在这里进行初始化,通过调用RestTemplateCustomizer的实例来给需要客户端负载均衡的RestTemplate增加LoadBalancerInterceptor拦截器。
  • LoadBalancerInterceptor的源码如下:
  1. public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
  2. private LoadBalancerClient loadBalancer;
  3. private LoadBalancerRequestFactory requestFactory;
  4. public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) {
  5. this.loadBalancer = loadBalancer;
  6. this.requestFactory = requestFactory;
  7. }
  8. public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) {
  9. // for backwards compatibility
  10. this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer));
  11. }
  12. @Override
  13. public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
  14. final ClientHttpRequestExecution execution) throws IOException {
  15. final URI originalUri = request.getURI();
  16. String serviceName = originalUri.getHost();
  17. Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
  18. return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request, body, execution));
  19. }
  20. }
  • 通过源码和之前的自动化配置类,我们可以看到在拦截器中注入了LoadBalancerClient的实现。当一个被@LoadBalanced注解修饰的RestTemplate对象向外发起HTTP请求时,会被LoadBalancerInterceptor类的intercept方法拦截。
  • LoadBalancerClient是一个抽象的负载均衡接口,其实现类是RibbonLoadBalancerClient。
  1. public class RibbonLoadBalancerClient implements LoadBalancerClient {
  2. private SpringClientFactory clientFactory;
  3. public RibbonLoadBalancerClient(SpringClientFactory clientFactory) {
  4. this.clientFactory = clientFactory;
  5. }
  6. public URI reconstructURI(ServiceInstance instance, URI original) {
  7. Assert.notNull(instance, "instance can not be null");
  8. String serviceId = instance.getServiceId();
  9. RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
  10. URI uri;
  11. Server server;
  12. if (instance instanceof RibbonLoadBalancerClient.RibbonServer) {
  13. RibbonLoadBalancerClient.RibbonServer ribbonServer = (RibbonLoadBalancerClient.RibbonServer)instance;
  14. server = ribbonServer.getServer();
  15. uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, ribbonServer);
  16. } else {
  17. server = new Server(instance.getScheme(), instance.getHost(), instance.getPort());
  18. IClientConfig clientConfig = this.clientFactory.getClientConfig(serviceId);
  19. ServerIntrospector serverIntrospector = this.serverIntrospector(serviceId);
  20. uri = RibbonUtils.updateToSecureConnectionIfNeeded(original, clientConfig, serverIntrospector, server);
  21. }
  22. return context.reconstructURIWithServer(server, uri);
  23. }
  24. //根据传入的服务id,从负载均衡中为指定的服务选择一个服务实例
  25. public ServiceInstance choose(String serviceId) {
  26. return this.choose(serviceId, (Object)null);
  27. }
  28. public ServiceInstance choose(String serviceId, Object hint) {
  29. Server server = this.getServer(this.getLoadBalancer(serviceId), hint);
  30. return server == null ? null : new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
  31. }
  32. //根据传入的服务id,指定的负责均衡器中的服务实例的执行请求
  33. public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
  34. return this.execute(serviceId, (LoadBalancerRequest)request, (Object)null);
  35. }
  36. //根据传入的服务实例,执行请求
  37. public <T> T execute(String serviceId, LoadBalancerRequest<T> request, Object hint) throws IOException {
  38. ILoadBalancer loadBalancer = this.getLoadBalancer(serviceId);
  39. Server server = this.getServer(loadBalancer, hint);
  40. if (server == null) {
  41. throw new IllegalStateException("No instances available for " + serviceId);
  42. } else {
  43. RibbonLoadBalancerClient.RibbonServer ribbonServer = new RibbonLoadBalancerClient.RibbonServer(serviceId, server, this.isSecure(server, serviceId), this.serverIntrospector(serviceId).getMetadata(server));
  44. return this.execute(serviceId, (ServiceInstance)ribbonServer, (LoadBalancerRequest)request);
  45. }
  46. }
  47. //根据传入的服务实例,执行请求
  48. public <T> T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException {
  49. Server server = null;
  50. if (serviceInstance instanceof RibbonLoadBalancerClient.RibbonServer) {
  51. server = ((RibbonLoadBalancerClient.RibbonServer)serviceInstance).getServer();
  52. }
  53. if (server == null) {
  54. throw new IllegalStateException("No instances available for " + serviceId);
  55. } else {
  56. RibbonLoadBalancerContext context = this.clientFactory.getLoadBalancerContext(serviceId);
  57. RibbonStatsRecorder statsRecorder = new RibbonStatsRecorder(context, server);
  58. try {
  59. T returnVal = request.apply(serviceInstance);
  60. statsRecorder.recordStats(returnVal);
  61. return returnVal;
  62. } catch (IOException var8) {
  63. statsRecorder.recordStats(var8);
  64. throw var8;
  65. } catch (Exception var9) {
  66. statsRecorder.recordStats(var9);
  67. ReflectionUtils.rethrowRuntimeException(var9);
  68. return null;
  69. }
  70. }
  71. }
  72. //略
  73. }
  • 从RibbonLoadBalancerClient代码可以看出,实际负载均衡是通过ILoadBalancer来实现的。