一、 了解LoadBalancer

1.1 介绍

SpringCloud 提供了自己的客户端负载均衡器抽象和实现。对于负载平衡机制,增加了ReactiveLoadBalancer接口,并提供了Round-Robin-based的实现。为了从响应式服务中选择实例,使用InstanceListSupplier。目前,我们支持基于服务发现的ServiceInstanceListSupplier实现,该实现使用类路径中可用的发现客户端从服务发现中检索可用实例。

1.2 Load Balancer客户端

使用@LoadBalanced注解标识负载均衡客户端

  • 非阻塞式负载均衡客户端 WebClient
  • 阻塞是负载均衡客户端 RestTemplate

实际上都是通过ClientHttpRequestInterceptor请求拦截器实现的。 具体实现细节可以看LoadBalancerInterceptor

二、 案例实践

2.1 创建SpringBoot 项目 (ribbon-customer-service)

2.2 pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.1.4.RELEASE</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>cn.hdj</groupId>
  12. <artifactId>ribbon-customer-service</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>RibbonCustomerService</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
  19. </properties>
  20. <dependencies>
  21. <dependency>
  22. <groupId>org.springframework.boot</groupId>
  23. <artifactId>spring-boot-starter-web</artifactId>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.springframework.boot</groupId>
  27. <artifactId>spring-boot-starter-actuator</artifactId>
  28. </dependency>
  29. <dependency>
  30. <groupId>org.springframework.cloud</groupId>
  31. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  32. </dependency>
  33. <dependency>
  34. <groupId>org.projectlombok</groupId>
  35. <artifactId>lombok</artifactId>
  36. <optional>true</optional>
  37. </dependency>
  38. <dependency>
  39. <groupId>org.springframework.boot</groupId>
  40. <artifactId>spring-boot-starter-test</artifactId>
  41. <scope>test</scope>
  42. </dependency>
  43. <dependency>
  44. <groupId>org.apache.httpcomponents</groupId>
  45. <artifactId>httpclient</artifactId>
  46. <version>4.5.12</version>
  47. <scope>compile</scope>
  48. </dependency>
  49. </dependencies>
  50. <dependencyManagement>
  51. <dependencies>
  52. <dependency>
  53. <groupId>org.springframework.cloud</groupId>
  54. <artifactId>spring-cloud-dependencies</artifactId>
  55. <version>${spring-cloud.version}</version>
  56. <type>pom</type>
  57. <scope>import</scope>
  58. </dependency>
  59. </dependencies>
  60. </dependencyManagement>
  61. <build>
  62. <plugins>
  63. <plugin>
  64. <groupId>org.springframework.boot</groupId>
  65. <artifactId>spring-boot-maven-plugin</artifactId>
  66. </plugin>
  67. </plugins>
  68. </build>
  69. </project>

2.3 配置文件

  • application.yml
  1. server:
  2. port: 8004
  • bootstrap.yml
  1. spring:
  2. application:
  3. name: ribbon-customer-service #应用服务名称

2.4 配置RestTemplate 作为LoadBalancer客户端

  1. @EnableDiscoveryClient
  2. @SpringBootApplication
  3. public class RibbonCustomerServiceApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(RibbonCustomerServiceApplication.class, args);
  6. }
  7. /**
  8. * 配置请求线程池
  9. *
  10. * @return
  11. */
  12. @Bean
  13. public HttpComponentsClientHttpRequestFactory requestFactory() {
  14. PoolingHttpClientConnectionManager connectionManager =
  15. new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
  16. connectionManager.setMaxTotal(200);
  17. connectionManager.setDefaultMaxPerRoute(20);
  18. CloseableHttpClient httpClient = HttpClients.custom()
  19. .setConnectionManager(connectionManager)
  20. .evictIdleConnections(30, TimeUnit.SECONDS)
  21. .disableAutomaticRetries()
  22. // 有 Keep-Alive 认里面的值,没有的话永久有效
  23. //.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)
  24. // 换成自定义的
  25. .setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {
  26. private final long DEFAULT_SECONDS = 30;
  27. @Override
  28. public long getKeepAliveDuration(HttpResponse response, HttpContext httpContext) {
  29. return Arrays.asList(response.getHeaders(HTTP.CONN_KEEP_ALIVE))
  30. .stream()
  31. .filter(h -> StringUtils.equalsIgnoreCase(h.getName(), "timeout")
  32. && StringUtils.isNumeric(h.getValue()))
  33. .findFirst()
  34. .map(h -> NumberUtils.toLong(h.getValue(), DEFAULT_SECONDS))
  35. .orElse(DEFAULT_SECONDS) * 1000;
  36. }
  37. })
  38. .build();
  39. HttpComponentsClientHttpRequestFactory requestFactory =
  40. new HttpComponentsClientHttpRequestFactory(httpClient);
  41. return requestFactory;
  42. }
  43. /**
  44. * RestTemplate 客户端
  45. *
  46. * @param builder
  47. * @return
  48. *
  49. * @LoadBalanced 标识使用RestTemplate 作为LoadBalancerClient
  50. */
  51. @LoadBalanced
  52. @Bean
  53. public RestTemplate restTemplate(RestTemplateBuilder builder) {
  54. return builder
  55. .setConnectTimeout(Duration.ofMillis(100))
  56. .setReadTimeout(Duration.ofMillis(500))
  57. .requestFactory(this::requestFactory)
  58. .build();
  59. }
  60. }

2.5 请求远程服务

  1. @Slf4j
  2. @RestController
  3. public class RibbonController {
  4. @Autowired
  5. private RestTemplate restTemplate;
  6. @Autowired
  7. private DiscoveryClient discoveryClient;
  8. /**
  9. * 使用RestTemplate 读取远程服务菜单信息
  10. *
  11. * @return
  12. */
  13. @GetMapping("/getMenu")
  14. public Map<String, Object> readRemoteMenu() {
  15. Map<String, Object> map = new HashMap<>(16);
  16. //通过服务名称获取服务的请求地址
  17. log.info("DiscoveryClient: {}", discoveryClient.getClass().getName());
  18. //"waiter-service" 是 spring.application.name中定义的名称
  19. List<String> list = discoveryClient.getInstances("waiter-service")
  20. .stream()
  21. .map(s -> {
  22. log.info("Host: {}, Port: {}", s.getHost(), s.getPort());
  23. return String.format("Host: %s, Port: %d", s.getHost(), s.getPort());
  24. })
  25. .collect(Collectors.toList());
  26. map.put("remote address", list);
  27. //请求远程服务
  28. ParameterizedTypeReference<List<String>> ptr =
  29. new ParameterizedTypeReference<List<String>>() {
  30. };
  31. ResponseEntity<List<String>> responseEntity = restTemplate
  32. .exchange("http://waiter-service/coffee/", HttpMethod.GET, HttpEntity.EMPTY, ptr);
  33. responseEntity.getBody().forEach(c -> log.info("Coffee: {}", c));
  34. map.put("response==>", responseEntity.getBody());
  35. return map;
  36. }
  37. }

注意:

  • 获取服务地址的方式

    1. EurekaClient#getNextServerFromEureka()
    2. DiscoveryClient#getInstances() (推荐,可用于不同的服务注册中心)

      2.6 启动euraka-waiter-service,eureka-server项目

      https://www.yuque.com/h_dj/lih6if/ar33ny
  • 修改euraka-waiter-service项目,添加web api 接口给ribbon-customer-service调用

    1. @RestController
    2. public class WaiterController {
    3. @GetMapping(value = "/coffee",produces = "application/json")
    4. public List<String> coffeeMenu() {
    5. return Arrays.asList("instance3 ESPRESSO COFFEES $12", "HANDMADE COFFEES $15", "SOOTHING HOT ALTERNATIVES $20", "COLD ALTERNATIVES $8");
    6. }
    7. }
  • 启动

eureka-server -> euraka-waiter-service -> ribbon-customer-service

  • 访问

http://localhost:8761/
image.png
http://localhost:8004/getMenu

  1. {"response==>":["ESPRESSO COFFEES $12","HANDMADE COFFEES $15","SOOTHING HOT ALTERNATIVES $20","COLD ALTERNATIVES $8"],"remote address":["Host: 192.168.43.122, Port: 33699"]}

LoadBalancer访问服务 完成

项目地址
https://github.com/h-dj/SpringCloud-Learning

参考