通俗讲,负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。
根据负载均衡发生位置的不同,一般分为服务端负载均衡和客户端负载均衡。
服务端负载均衡指的是发生在服务提供者一方,比如:常见的nginx负载均衡;而客户端负载均衡指的是发生在服务请求的一方,也就是在发送请求之前已经选好了由哪个实例处理请求。
我们在微服务调用关系中一般会选择客户端负载均衡,也就是在服务调用的一方来决定服务由哪个提供者执行。
第1步:启动多个相同的微服务。
第2步:通过Nacos查看微服务的注册启动情况。
第3步:修改服务调用代码,实现负载均衡
@RestControllerpublic class OrderController{
@Autowried private RestTemplate restTemplate;
@Autowired private DiscoveryClient discoveryClient;
@GetMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid")Integer pid){
//从nacos中获取服务地址 //自定义规则实现随机挑选服务
List<ServiceInstance> instances = discoveryClient.getInstances("service-provider");
int index = new Random().nextInt(instances.size());
ServiceInstance serviceInstance = instances.get(index);
String url = servieInstance.getHost() + ":" + serviceInstance.getPort();
Product product = restTemplate.getForObject("http://"+url+"/product"+pid, Product.class); }}
第4步:启动多个服务提供者和一个服务消费者,多访问几次消费者测试其结果。
Ribbon是Spring Cloud的一个组件,它可以让我们使用一个注解就能轻松的实现负载均衡; 是 Netflix
公司的一个开源的负载均衡 项目;是一个基于HTTP 和 TCP的客户端/进程内
负载均衡器,**运行在消费者端**
。
第1步:在RestTemplate的生成方法上添加@LoadBalanced注解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
第2步:修改服务调用的方法
@RestControllerpublic class OrderController{
@Autowired
private RestTemplate restTemplate;
@GetMapping("/order/prod/{pid}")
public Order order(@PathVariable("pid")Integer pid){
String url = "service-product";
Product product = restTeplate.getForObject("http://"+url+"/product/"+pid,Product.class);
}
}
Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule,具体的负载策略如下:
策略名 | 策略描述 | 实现说明 |
---|---|---|
BestAvailableRule | 选择一个最小的并发请求的server | 逐个考察Server,如果Server被tripped了,则忽略,再选择其中ActiveRequestCount最小的server。 |
AvailabilityFilteingRule | 过滤掉哪些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的后端server(active connections超过配置的阈值) | 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就是检查status里纪录的各个server的运行状态 |
WeightedResponseTimeRule | 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 | 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。weight的计算也比较简单repsponse time 减去每个server自己平均的response time是server的权重。当开始运行,没有形成status时,使用roubine策略选择server。 |
RetryRule | 对选定的负载均衡策略server上执行重试机制。 | 在一个配置时间段内,当选择server失败,则一直尝试使用subRule的方式选择一个可用的server。 |
RoundRobinRule | 轮询方式选择server | 轮询index,选择index对应位置的server。 |
RandomRule | 随机选择一个server | 在index上随机,选择index对应位置的server。 |
ZoneAvoidanceRule | 复合判断server所在区域的性能和server的可用性选择server | 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的server。 |
可以通过修改配置来调整Ribbon的负载均衡策略,具体代码如下:
service-provider: #调用的服务提供者的服务名称 ribbon: NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
Spring Cloud原有的客户端负载均衡方案Ribbon已经被废弃(因为Netflix公司将Ribbon闭源),因此,Spring Cloud定义了LoadBalancer来替换Ribbon。
第1步:添加LoadBalancer依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-loadbalancerartifactId>
<version>2.2.1.RELEASEversion>
dependency>
第2步:在RestTemplate的生成方法上添加@LoadBalanced注解
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();}
或者 使用LoadBalancerClient的客户端API
//在服务消费者的业务逻辑代码中注入LoadBalancerClient 的实例
@Autowired
LoadBalancerClient lbc;
@Autowired
RestTemplate rt;
public List<String> getOders(){
ServiceInstance si = lbc.choose("order-service");
rt.getForObject(si.getUri() + "/order/list", List.class);
}
如果项目中引入了Ribbon,Spring Cloud默认优先使用Ribbon,因此使用Spring Cloud Load Balancer需要将Ribbon依赖排除掉或者关闭Ribbon,可以使用以下配置关闭Ribbon功能。
spring:
cloud:
loadbalancer:
ribbon:
enabled: false
关闭ribbon之后,Spring Cloud LoadBalancer就会加载成默认的负载均衡器(BlockingLoadBalancerClient), 默认负载均衡器是轮询选择。
可以在启动类上面使用@LoadBalancerClient注解,或者@LoadBalancerClients注解,指定服务级别的负载均衡策略。
@LoadBalancerClient(value = "demo-provider", configuration = RandomLoadbalancerConfig.class)
public class RandomLoadbalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> reactorServiceInstanceLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
loadBalancerClientFactory.getLazyProvider(name, ServiceInstanceListSupplier.class), name);
}
}
自定义灰度负载均衡策略示例:
@Slf4j
public class GrayRoundRobinLoadBalancer extends RoundRobinLoadBalancer {
private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;
private String serviceId;
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider .getIfAvailable(NoopServiceInstanceListSupplier::new);
return supplier.get(request).next().map(serviceInstances -> getInstanceResponse(serviceInstances, request));
}
Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances, Request request) { // 注册中心无可用实例 抛出异常
if (CollUtil.isEmpty(instances)) {
log.warn("No instance available {}", serviceId);
return new EmptyResponse();
}
DefaultRequestContext requestContext = (DefaultRequestContext) request.getContext();
RequestData clientRequest = (RequestData) requestContext.getClientRequest();
HttpHeaders headers = clientRequest.getHeaders();
String reqVersion = headers.getFirst(CommonConstants.VERSION);
if (StrUtil.isBlank(reqVersion)) {
return super.choose(request).block();
} // 遍历可以实例元数据,若匹配则返回此实例
for (ServiceInstance instance : instances) {
NacosServiceInstance nacosInstance = (NacosServiceInstance) instance;
Map<String, String> metadata = nacosInstance.getMetadata();
String targetVersion = MapUtil.getStr(metadata, CommonConstants.VERSION);
if (reqVersion.equalsIgnoreCase(targetVersion)) {
log.debug("gray requst match success :{} {}", reqVersion, nacosInstance);
return new DefaultResponse(nacosInstance);
}
} // 降级策略,使用轮询策略
return super.choose(request).block();
}
}
完