一、 了解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
@SpringBootApplication
public class RibbonCustomerServiceApplication {
public static void main(String[] args) {
SpringApplication.run(RibbonCustomerServiceApplication.class, args);
}
/**
* 配置请求线程池
*
* @return
*/
@Bean
public 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;
@Override
public 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
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofMillis(100))
.setReadTimeout(Duration.ofMillis(500))
.requestFactory(this::requestFactory)
.build();
}
}
2.5 请求远程服务
@Slf4j
@RestController
public class RibbonController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private 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调用
@RestController
public 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