目录
3.4.1、Ribbon 的负载均衡 IRule 接口的实现
3、Ribbon
3.1、Ribbon 官方解释
Ribbon is a client side load balancer which gives you a lot of control over the behaviour of HTTP and TCP clients. Feign already uses Ribbon, so if you are using @FeignClient then this section also applies.
A central concept in Ribbon is that of the named client. Each load balancer is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that you give it as an application developer (for example, by using the @FeignClient annotation). On demand, Spring Cloud creates a new ensemble as an ApplicationContext for each named client by using RibbonClientConfiguration. This contains (amongst other things) an ILoadBalancer, a RestClient, and a ServerListFilter.
Ribbon 是一个客户端负载均衡器,可以让您对 HTTP 和 TCP 客户端的行为进行大量控制。 Feign 已经使用了 Ribbon,因此如果您使用的是 @FeignClient,那么本节也适用。
Ribbon 中的一个核心概念是指定客户端的概念。 每个负载均衡器都是一组组件的一部分,这些组件一起工作以按需联系远程服务器,并且该集合具有您作为应用程序开发人员提供的名称(例如,通过使用 @FeignClient 批注)。 根据需要,Spring Cloud 通过使用 RibbonClientConfiguration 为每个命名客户端创建一个新的集合作为 ApplicationContext。 这包含(除其他外)ILoadBalancer,RestClient和ServerListFilter。
3.2、负载均衡策略
- 随机(Random)
- 轮询(RoundRobin)
- 一致性哈希(ConsistentHash)
- 哈希(Hash)
- 加权(Weighted)
- 最小请求数(Available)
3.3、Spring Boot 使用 Ribbon
因为在 eureka 或 Fegin 的包都有依赖 Ribbon,所以不需要依赖 jar,下面是 RestTemplate + LoadBalance 的实现
//1、定义RestTemplate
@Bean
@LoadBalanced
public RestTemplate loadBalancedRestTemplate(){
return new RestTemplate();
}
//可以使用自定义负载策略,不定义的话会默认
@Bean
public IRule ribbonRule(){
return new RoundRobinRule();
}
//2、使用 LoadBalanced
@Autowired
@LoadBalanced
private RestTemplate abRestTemplate;
@GetMapping("/lb/invoke/{serviceName}/say")
public String invokeLoadBalanceSay(@PathVariable String serviceName, @RequestParam String message){
//调用服务端的接口
return abRestTemplate.getForObject("http://" + serviceName + "/say?message=" + message, String.class);
}
3.4、参照 Ribbon 自定义负载均衡
3.4.1、Ribbon 的负载均衡 IRule 接口的实现
- RandomRule:随机策略
- RoundRobinRule:轮询策略
- WeightedResponseTimeRule:加权策略 可以与轮询策略组合成加权轮询策略
- BestAvailableRule:请求数最少策略
- AvailabilityFilteringRule:过滤多次访问故障,以及并发超过阈值的服务,最后使用轮询
- RetryRule:对选定的负载均衡机上重试机制
- ZoneAvoidanceRule:复合判断 server 所在区域的性能和 server 的可用性来选择 server
3.4.2、自定义负载均衡代码实现
public class LoadBalanceRequestInterceptor implements ClientHttpRequestInterceptor {
private volatile Map<String,Set<String>> targetUrlsCache = new HashMap<>();
@Autowired
private DiscoveryClient discoveryClient;
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
//URI:/${app-name}/uri
URI requestURI = request.getURI();
String path = requestURI.getPath();
String [] parts = StringUtils.split(path.substring(1),"/");
String serviceName = parts[0];
String uri = parts[1];
List<String> targetUrls = new LinkedList<>(targetUrlsCache.get(serviceName));
int size = targetUrls.size();
int index = new Random().nextInt(size);//使用简单的随机策略,可以使用IRule拓展
String targetURL = targetUrls.get(index);
String actualURL = targetURL +"/"+ uri + "?" + requestURI.getQuery();
RestTemplate restTemplate = new RestTemplate();
ResponseEntity<InputStream> entity = restTemplate.getForEntity(actualURL,InputStream.class);
HttpHeaders httpHeaders = entity.getHeaders();
InputStream responseBody = entity.getBody();
return new SimpleClentHttpResponse(httpHeaders,responseBody);
}
//更新缓存中的服务地址
@Scheduled(fixedRate = 10 * 1000)
public void updateTargetUrlsCache(){
Map<String,Set<String>> newTargetUrisCache = new HashMap<>();
discoveryClient.getServices().forEach(serviceName -> {
List<ServiceInstance> serviceInstances = discoveryClient.getInstances(serviceName);
Set<String> newTargetUrls = serviceInstances
.stream()
.map(s ->
s.isSecure() ? "https://" + s.getHost() + ":" + s.getPort() :
"http://" + s.getHost() + ":" + s.getPort()
).collect(Collectors.toSet());
newTargetUrisCache.put(serviceName,newTargetUrls);
});
this.targetUrlsCache = newTargetUrisCache;
}
private class SimpleClentHttpResponse implements ClientHttpResponse {
private HttpHeaders httpHeaders;
private InputStream body;
public SimpleClentHttpResponse(HttpHeaders httpHeaders, InputStream responseBody) {
this.httpHeaders = httpHeaders;
this.body = responseBody;
}
@Override
public HttpStatus getStatusCode() throws IOException {
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
return 200;
}
@Override
public String getStatusText() throws IOException {
return "OK";
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return body;
}
@Override
public HttpHeaders getHeaders() {
return httpHeaders;
}
}
}
3.4.3、使用自定义负载均衡器
在spring boot 项目中使用 restTempate + 自定义的 loadBalance 实现负载均衡调用
//1、定义自定义的负载拦截器
@Bean
public ClientHttpRequestInterceptor interceptor(){
return new LoadBalanceRequestInterceptor();
}
@Bean
@Autowired
public RestTemplate restTemplate(ClientHttpRequestInterceptor interceptor){
RestTemplate restTemplate = new RestTemplate();
//2、将自己定义的负载拦截器添加到 restTemplate 的拦截器中
restTemplate.setInterceptors(Arrays.asList(interceptor));
return restTemplate;
}
//3、使用自定义的 RestTemplate
@Autowired
private RestTemplate restTemplate;
@GetMapping("/invoke/{serviceName}/say")
public String invokeSay(@PathVariable String serviceName, @RequestParam String message){
//TODO 存在JsonParseException 的异常
return restTemplate.getForObject("/" + serviceName + "/say?message=" + message, String.class);
}