今天在学习微服务中负载均衡特性的时候,注意到了@LoadBalanced注解,这个注解用法如下:

@Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

通过增加@LoadBalanced注解,使RestTemplate调用远程服务时,具有负载均衡的特性。

同时使用RestTemplate对象也会有相应的变化,如果不增加@LoadBalanced注解,则远程调用的方式如下:

//根据微服务名称获取服务实例列表   
    List<ServiceInstance> instances = discoveryClient.getInstances("service-product");
    //随机选取其中一个实例,手动负载均衡
    int index = new Random().nextInt(instances.size());
    ServiceInstance serviceInstance = instances.get(index);
    
    //组装服务URL
    String url = serviceInstance.getHost() + ":" + serviceInstance.getPort();
        
    //使用组装的服务URL调用远程服务
    Product product = restTemplate.getForObject(
                "http://" + url + "/product/" + id, Product.class);

增加了@LoadBalanced注解后,上述代码就会出问题:

2022-11-24 23:46:14.629 ERROR 130888 --- [nio-8091-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.IllegalStateException: No instances available for 169.254.145.20] with root cause
java.lang.IllegalStateException: No instances available for 169.254.145.20
     at org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient.execute(RibbonLoadBalancerClient.java:105) ~[spring-cloud-netflix-ribbon-2.1.0.RELEASE.jar:2.1.0.RELEASE]

后台提示“169.254.145.20”没有有效的实例。

实际当中,增加@LoadBalanced注解后,远程调用的方法也需要进行相应的调整,代码如下:

//直接使用微服务名
    String url = "service-product";
    
    //直接通过服务名调用远程服务
    Product product = restTemplate.getForObject(
        "http://" + url + "/product/" + pid, Product.class);

代码简洁了很多!那如果上述代码如果在没有@LoadBalanced注解的情况下,会发生什么呢?我们来看一下:

2022-11-24 23:54:35.161 ERROR 117980 --- [nio-8091-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://service-product/product/2": service-product; nested exception is java.net.UnknownHostException: service-product] with root cause
java.net.UnknownHostException: service-product
     at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184) ~[na:1.8.0_161]

后台提示:service-product 为未知主机。

从上述现象看,我们可以推测@LoadBalanced注解大概做了什么事,即:@LoadBalanced注解按照一定的负载均衡算法,自动将服务名称转换为某一个节点的地址,最后进行服务的调用。

以下是可选的负载均衡策略:

策略名

策略描述

实现说明

BestAvailableRule

选择一个最小的并发 请求的server

逐个考察Server,如果Server被 tripped了,则忽略,在选择其中 ActiveRequestsCount最小的server

AvailabilityFilteringRule

过滤掉那些因为一直 连接失败的被标记为 circuit tripped的后 端server,并过滤掉 那些高并发的的后端 server(active connections 超过配 置的阈值)

使用一个AvailabilityPredicate来包含 过滤server的逻辑,其实就就是检查 status里记录的各个server的运行状 态

WeightedResponseTimeRule

根据相应时间分配一 个weight,相应时 间越长,weight越 小,被选中的可能性 越低。

一个后台线程定期的从status里面读 取评价响应时间,为每个server计算 一个weight。Weight的计算也比较简 单responsetime 减去每个server自己 平均的responsetime是server的权 重。当刚开始运行,没有形成statas 时,使用roubine策略选择server。

RetryRule

对选定的负载均衡策 略机上重试机制。

在一个配置时间段内当选择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。

那么如何设置负载均衡的策略呢?进行如下配置即可:

service-product: # 调用的提供者的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule