Ribbon作为客户端负载均衡组件,从注册中心例如Nacos中获取服务实例列表,将请求均摊到服务的多个实例,使得服务可以进行水平扩展,提高了服务的吞吐量和处理能力。
Feign组件默认集成了Ribbon组件,所以我们直接可以基于Feign调用就可以使用Ribbon负载均衡功能。
4.1 Ribbon的使用
4.1.1 基本使用
我们对device服务新增一个/device/hello接口,输出服务实例端口号,便于观察负载均衡调用。
@RestController
@RequestMapping("/device")
public class DeviceController {
@GetMapping("/hello")
public String hello(@RequestParam("param") String param, HttpServletRequest request) {
return "hello: " + param + " 服务实例端口:" + request.getServerPort();// 当前实例端口号
}
业务服务新增一个hello接口,用于调用device服务的hello接口
@RestController
@RequestMapping("/deviceData")
@Slf4j
public class DeviceDataController {
@Resource
private FeignDeviceService feignDeviceService;
@GetMapping("/hello")
public String hello(@RequestParam("param") String param) {
return feignDeviceService.hello(param);
}
}
在业务服务的Feign接口中添加对device服务的/device/hello接口的调用
@FeignClient("smart-device")
public interface FeignDeviceService {
@GetMapping("/device/hello")
String hello(@RequestParam("param") String param);
}
观察business业务服务调用device服务的2个实例,默认是轮询调用。
4.1.2 Ribbon配置
1.负载均衡策略
Ribbon支持的负载均衡策略:
- 随机
- 轮询
- 最小并发请求
- 响应时间策略
- 其他等等。
我们可以基于配置文件的方式修改Ribbon负载均衡策略。当然还可以基于配置类来修改,但是修改配置文件的方式最简单便捷,推荐此方式。
smart-device:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule # 响应时间策略
上面指定为响应时间策略。这个响应时间策略指的是选择响应时间最短的服务实例优先调用。
2.超时与重试
在基于Ribbon调用目标服务时,在极端情况下会出现超时,那么我们在需要配置超时时间以及超时之后的重试次数。
smart-device:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.WeightedResponseTimeRule
# 连接超时、读取超时
ConnectTimeout: 5000
ReadTimeout: 5000
MaxAutoRetries: 1 # 第1次请求的重试次数
MaxAutoRetriesNextServer: 1 # 下一个服务实例的重试次数
OkToRetryOnAllOperations: true
3.饥饿加载
Ribbon负载均衡机制默认没有在应用启动的时候加载配置上下文,这样会导致在调用目标服务的时候才去加载配置项上下文从而引起超时。所以,我们在调用目标device服务的business业务服务中配置目标服务饥饿加载,在应用启动的时候就把Ribbon配置项上下文加载好。
ribbon:
eager-load: # 开启饥饿加载
enabled: true
clients: smart-device
4.2 Ribbon的基本原理
我们看到RestTemplate这个bean配置@LoadBalanced注解,就可以实现Ribbon负载均衡。
@LoadBalanced注解标记RestTemplate使用LoadBalancerClient,LoadBalancerClient扩展至ServiceInstanceChooser接口。
public interface LoadBalancerClient extends ServiceInstanceChooser {
<T>T execute(string serviceId,LoadBalancerRequest<T> request) throws IOException;
// 执行请求
<T>T execute(String serviceId, ServiceInstance serviceInstance, LoadBalancerRequest<T> request) throws IOException;
URI reconstructURl(ServiceInstance instance, URl original);
}
public interface ServiceInstanceChooser {
// 根据服务id,调用负载均衡器LoadBalancer选择服务实例
ServiceInstance choose(String serviceId);
}
4.2.1 配置类初始化
@Configuration
@ConditionalOnClass(RestTemplate.class)
@ConditionalOnBean(LoadBalancerClient.class)
@EnableConfiqurationProperties(LoadBalancerRetryProperties.class)
public class LoadBalancerAutoConfiquration {
@Bean
@ConditionalOnMissingBean
public LoadBalancerRequestFactory loadBalancerRequestFactory(LoadBalancerClient loadBalancerClient){
// 创建LoadBalancerRequest请求,供拦截器使用
return new LoadBalancerRequestFactory(loadBalancerClient,transformers);
}
@Confiquration
@ConditionalOnMissingclass("org.springframework.retry.support .RetryTemplate")
static class loadBalancerInterceptorConfig {
@Bean
public LoadBalancerInterceptor ribbonInterceptor(LoadBalancerClient loadBalancerClient, LoadBalancerRequestFactory requestFactory) {
// 拦截器,用于拦截http请求,供loadBalancerClient负载均衡器使用
return new LoadBalancerInterceptor(loadBalancerClient,requestFactory);
}
@Bean
@ConditionalonMissingBean
public RestTemplateCustomizer restTemplateCustomizer (final LoadBalancerInterceptor loadBalancerInterceptor){
// 为restTemplate添加拦截器
return restTemplate-> {
List<ClientHttpRequestInterceptor>list = new ArrayList<>(restTemplate.getInterceptors());
list.add(loadBalancerInterceptor);
restTemplate.setInterceptors(list)
}
}
}
LoadBalancerAutoConfiquration负载均衡配置类初始化请求、拦截器、负载均衡器:
1.创建LoadBalancerRequest请求,供LoadBalancerInterceptor拦截器使用
2.LoadBalancerInterceptor拦截器,用于拦截http请求,供loadBalancerClient负载均衡器使用
3.为restTemplate添加拦截器
4.2.2 LoadBalancerInterceptor拦截器拦截请求
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor
private LoadBalancerClientloadBalancer;
private LoadBalancerRequestFactory requestFactory;
@Override
public ClientHttpResponse intercept(final HttpRequest reguest, final byte[] body,final ClientHttpRequestExecution execution) throws IOException {
final URI originalUri=request.getURI();
String serviceName=originalUri.getHost();
return this.loadBalancer.execute(serviceName, requestFactory.createRequest(request,body,execution));
}
}
LoadBalancerInterceptor拦截器拦截请求,交给负载均衡器去处理
4.2.3 负载均衡器LoadBalancer选择服务实例
@Override
public <T>T execute(String serviceId, loadBalancerRequest<T> request) throws IOException {
// 获取loadBalancer负载均衡器
ILoadBalancer loadBalancer=getLoadBalancer(serviceld);
// loadBalancer负载均衡器选择服务实例
Server server=getServer(loadBalancer);
if(server ==nu11) throw new IllegalStateException("No instances available for "+ serviceld);
RibbonServer ribbonServer = new RibbonServer(serviceId, server, isSecure(serverserviceId), serverIntrospector(serviceId).getMetadata(server));
return execute(serviceld,ribbonServer,request);
}
protected Server getServer(ILoadBalancer loadBalancer) {
if(1oadBalancer==n11){
return null;
}
// loadBalancer负载均衡器选择服务实例
return loadBalancer.chooseServer("default");
}
4.2.4 调用负载均衡策略rule选择服务实例
public chooseServer(Obiect key) {
...
try {
// 负载均衡器调用负载均衡策略rule选择服务实例
return rule.choose(key);
} catch(Exceptione){
logger.warn("LoadBalancer{}]:Error choosing server for key{}"
name,key,e);
return null;
}
}