一、 了解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
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.4.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>cn.hdj</groupId><artifactId>ribbon-customer-service</artifactId><version>0.0.1-SNAPSHOT</version><name>RibbonCustomerService</name><description>Demo project for Spring Boot</description><properties><java.version>1.8</java.version><spring-cloud.version>Greenwich.SR1</spring-cloud.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId></dependency><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-netflix-eureka-client</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.12</version><scope>compile</scope></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-dependencies</artifactId><version>${spring-cloud.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
2.3 配置文件
- application.yml
server:port: 8004
- bootstrap.yml
spring:application:name: ribbon-customer-service #应用服务名称
2.4 配置RestTemplate 作为LoadBalancer客户端
@EnableDiscoveryClient@SpringBootApplicationpublic class RibbonCustomerServiceApplication {public static void main(String[] args) {SpringApplication.run(RibbonCustomerServiceApplication.class, args);}/*** 配置请求线程池** @return*/@Beanpublic HttpComponentsClientHttpRequestFactory requestFactory() {PoolingHttpClientConnectionManager connectionManager =new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);connectionManager.setMaxTotal(200);connectionManager.setDefaultMaxPerRoute(20);CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(connectionManager).evictIdleConnections(30, TimeUnit.SECONDS).disableAutomaticRetries()// 有 Keep-Alive 认里面的值,没有的话永久有效//.setKeepAliveStrategy(DefaultConnectionKeepAliveStrategy.INSTANCE)// 换成自定义的.setKeepAliveStrategy(new ConnectionKeepAliveStrategy() {private final long DEFAULT_SECONDS = 30;@Overridepublic long getKeepAliveDuration(HttpResponse response, HttpContext httpContext) {return Arrays.asList(response.getHeaders(HTTP.CONN_KEEP_ALIVE)).stream().filter(h -> StringUtils.equalsIgnoreCase(h.getName(), "timeout")&& StringUtils.isNumeric(h.getValue())).findFirst().map(h -> NumberUtils.toLong(h.getValue(), DEFAULT_SECONDS)).orElse(DEFAULT_SECONDS) * 1000;}}).build();HttpComponentsClientHttpRequestFactory requestFactory =new HttpComponentsClientHttpRequestFactory(httpClient);return requestFactory;}/*** RestTemplate 客户端** @param builder* @return** @LoadBalanced 标识使用RestTemplate 作为LoadBalancerClient*/@LoadBalanced@Beanpublic RestTemplate restTemplate(RestTemplateBuilder builder) {return builder.setConnectTimeout(Duration.ofMillis(100)).setReadTimeout(Duration.ofMillis(500)).requestFactory(this::requestFactory).build();}}
2.5 请求远程服务
@Slf4j@RestControllerpublic class RibbonController {@Autowiredprivate RestTemplate restTemplate;@Autowiredprivate DiscoveryClient discoveryClient;/*** 使用RestTemplate 读取远程服务菜单信息** @return*/@GetMapping("/getMenu")public Map<String, Object> readRemoteMenu() {Map<String, Object> map = new HashMap<>(16);//通过服务名称获取服务的请求地址log.info("DiscoveryClient: {}", discoveryClient.getClass().getName());//"waiter-service" 是 spring.application.name中定义的名称List<String> list = discoveryClient.getInstances("waiter-service").stream().map(s -> {log.info("Host: {}, Port: {}", s.getHost(), s.getPort());return String.format("Host: %s, Port: %d", s.getHost(), s.getPort());}).collect(Collectors.toList());map.put("remote address", list);//请求远程服务ParameterizedTypeReference<List<String>> ptr =new ParameterizedTypeReference<List<String>>() {};ResponseEntity<List<String>> responseEntity = restTemplate.exchange("http://waiter-service/coffee/", HttpMethod.GET, HttpEntity.EMPTY, ptr);responseEntity.getBody().forEach(c -> log.info("Coffee: {}", c));map.put("response==>", responseEntity.getBody());return map;}}
注意:
获取服务地址的方式
- EurekaClient#getNextServerFromEureka()
- 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调用
@RestControllerpublic class WaiterController {@GetMapping(value = "/coffee",produces = "application/json")public List<String> coffeeMenu() {return Arrays.asList("instance3 ESPRESSO COFFEES $12", "HANDMADE COFFEES $15", "SOOTHING HOT ALTERNATIVES $20", "COLD ALTERNATIVES $8");}}
启动
eureka-server -> euraka-waiter-service -> ribbon-customer-service
- 访问
http://localhost:8761/
http://localhost:8004/getMenu
{"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
