【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现

负载均衡的实现方式

  • 客户端的负载均衡
  • 服务端的负载均衡(经常使用nginx来实现)

服务端负载均衡

  1. 接收请求
  2. 选择服务器地址
  3. 转发请求

客户端负载均衡

  1. 选择服务器地址
  2. 发请求

也可以分为集中式负载均衡进程内负载均衡

集中式负载均衡

即在服务的消费方和提供方之间使用独立的负载均衡设施(可以是硬件,如F5,可以是软件,如nginx),由该实施负责把访问请求通过某种策略转发至服务的提供方

进程内负载均衡

将负载均衡逻辑集成到消费方,消费方从服务注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个合适的服务器。
Ribbon就属于进程内负载均衡,它只是一个类库,集成于消费方进程,消费方通过它来获取服务提供方的地址

Ribbon的简单介绍

Spring Cloud Ribbon是基于Netfilx Ribbon实现一套客户端负载均衡的工具。简单的说,Ribbon是Netfilx 发布的开源项目,主要功能是提供客户端的软件负载均衡算法和服务调用。Ribbon客户端组件提供一系列完善的配置项,如:连接超时、重试等。
简单的说,就是在配置文件列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如:简单轮询、随机连接等)去连接这些机器。
我们很容易使用Ribbon实现自定义负载均衡算法。

Ribbon 注册到nacos server之后,Ribbon会从nacos server获取服务列表
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现

Ribbon 负载均衡策略的简单介绍

IRule 接口的实现类定义了一系列负载规则

IRule的类图
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现
负载策略的大致功能实现

策略名 策略声明 策略描述 实现说明
BestAvailableRule public class BestAvailableRule extends ClientConfigEnabledRoundRobinRule 选择一个最小的并发请求的server 逐个考察Server,如果Server被tripped了,则忽略,在选择其中ActiveRequestsCount最小的server
AvailabilityFilteringRule public class AvailabilityFilteringRule extends PredicateBasedRule 过滤掉那些因为一直连接失败的被标记为circuit tripped的后端server,并过滤掉那些高并发的的后端server(active connections 超过配置的阈值) 使用一个AvailabilityPredicate来包含过滤server的逻辑,其实就就是检查status里记录的各个server的运行状态
WeightedResponseTimeRule public class WeightedResponseTimeRule extends RoundRobinRule 根据响应时间分配一个weight,响应时间越长,weight越小,被选中的可能性越低。 一个后台线程定期的从status里面读取评价响应时间,为每个server计算一个weight。Weight的计算也比较简单responsetime 减去每个server自己平均的responsetime是server的权重。当刚开始运行,没有形成status时,使用roubine策略选择server。
RetryRule public class RetryRule extends AbstractLoadBalancerRule 对选定的负载均衡策略机上重试机制。 在一个配置时间段内当选择server不成功,则一直尝试使用subRule的方式选择一个可用的server
RoundRobinRule public class RoundRobinRule extends AbstractLoadBalancerRule roundRobin方式轮询选择server 轮询index,选择index对应位置的server
RandomRule public class RandomRule extends AbstractLoadBalancerRule 随机选择一个server 在index上随机,选择index对应位置的server
ZoneAvoidanceRule public class ZoneAvoidanceRule extends PredicateBasedRule 复合判断server所在区域的性能和server的可用性选择server 使用ZoneAvoidancePredicate和AvailabilityPredicate来判断是否选择某个server,前一个判断判定一个zone的运行性能是否可用,剔除不可用的zone(的所有server),AvailabilityPredicate用于过滤掉连接数过多的Server。

ResponseTimeWeightedRule已经被弃用,作用和WeightedResponseTimeRule一样。

还是以Nacos的简单介绍以及服务注册与发现功能的基本使用这篇博客为基础,我们来探究一下Ribbon负载均衡的基本使用

示例

nacos-provider(提供服务)

基本项目结构如下
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现
还是Nacos的简单介绍以及服务注册与发现功能的基本使用这篇博客的代码,可以去这篇博客的nacos-provider章节查看,我就不搬运过来了

nacos-consumer(消费服务)

基本结构如下
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现
其中feign包是使用feign调用远程服务不用管它

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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud</artifactId>
        <groupId>com.xt</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>nacos-consumer</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</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-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

</project>

application.yml配置文件

server:
  port: 8085

spring:
  application:
    name: nacos-consumer
  cloud:
    nacos:
      discovery:
        server-addr: 部署nacos server的服务器IP:8848

feign:
  hystrix:
    enabled: true

RibbonConfig

写一个ribbon配置类
IRule 接口的实现类定义了一系列负载规则
如果自己不指定,默认使用的就是轮询算法。
先使用RandomRule的随机来做测试

@Configuration
public class RibbonConfig {

    @Bean
    public IRule iRule() {
//        权重负载策略 nacos的负载均衡实现
//        return new NacosRule(); 是spring cloud alibaba 继承了netflix.loadbalancer包的AbstractLoadBalancerRule抽象类的实现

//        会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
//        return new BestAvailableRule();

//        复合判断server所在区域的性能和server的可用性选择服务器
//        return new ZoneAvoidanceRule();

//        先过滤掉故障实例,再选择并发较小的实例
//        return new AvailabilityFilteringRule();

//      对RoundRobinRule 轮询的扩展,响应速度越快的实例选择权重越多大,越容易被选择
//        return new WeightedResponseTimeRule();

//        return new ResponseTimeWeightedRule(); 已经被弃用

//        随机
        return new RandomRule();

//        先按照RoundRobinRule 轮询的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务
//        return new RetryRule();

    }
}

controller

@RestController
public class TestController {

    private final RestTemplate restTemplate;

    @Autowired
    public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}

    @RequestMapping(value = "/echo-restemplate/{str}", method = RequestMethod.GET)
    public String echo(@PathVariable String str) {
        return restTemplate.getForObject("http://nacos-provider/echo/" + str, String.class);
    }


    @Autowired
    EchoService echoService;

    @RequestMapping(value = "/echo-feign/{str}",method = RequestMethod.GET)
    public String feign(@PathVariable String str) {
        return echoService.echo(str);
    }


}

feign(远程调用服务的东西)

@Component
@FeignClient(name = "nacos-provider")
public interface EchoService {


    @RequestMapping(value = "/echo-error/{str}",method = RequestMethod.GET)
    String echo(@PathVariable("str") String str);

    //fallback实现类
    @Component
    class EchoServiceFallback implements EchoService{
        @Override
        public String echo(@PathVariable("str") String str) {
            return "接口请求失败";
        }
    }

}

MyLoadBalancer类(自定义负载均衡的实现,我这里写死了,只访问一个实例)

public class MyLoadBalancer extends AbstractLoadBalancerRule{


    public MyLoadBalancer() {
    }

    @SuppressWarnings({"RCN_REDUNDANT_NULLCHECK_OF_NULL_VALUE"})
    public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;

            while(server == null) {
                //测试当前线程是否被中断(检查中断标志),返回一个boolean并清除中断状态,第二次再调用时中断状态已经被清除,将返回一个false。
                if (Thread.interrupted()) {
                    return null;
                }
                //获取已启动且可访问的服务器。
                List<Server> upList = lb.getReachableServers();
                //获取所有已知的服务器(可访问和不可访问)
                List<Server> allList = lb.getAllServers();
                //全部服务的实例个数
                int serverCount = allList.size();

                if (serverCount == 0) {
                    return null;
                }

                //RandomRule使用ThreadLocalRandom获取随机数,我这里直接写死,我只要1实例提供服务
                int index = 1;

                server = (Server)upList.get(index);
                if (server == null) {
                    //放出CPU资源
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }

                    server = null;
                    Thread.yield();
                }
            }

            return server;
        }

    }

    protected int chooseRandomInt(int serverCount) {
        //ThreadLocalRandom,在多线程下,它为每个线程维护一个 seed 变量
        return ThreadLocalRandom.current().nextInt(serverCount);

    }

    @Override
    public Server choose(Object key) {
        return this.choose(this.getLoadBalancer(), key);
    }

    @Override
    public void initWithNiwsConfig(IClientConfig clientConfig) {
    }

}

NacosConsumerApplication启动类

使用RestTemplate来请求服务,向Spring的IOC注入一个bean: restTemplate;并通过@LoadBalanced注解表明这个restRemplate开启负载均衡的功能。

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@RibbonClient(name="nacos-provider",configuration= RibbonConfig.class)
public class NacosConsumerApplication {

//    @Bean
//    @Scope(value = "prototype")
//    public IRule loadBalanceRule() {
//        return new NacosRule();
//    }

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

    public static void main(String[] args){
        SpringApplication.run(NacosConsumerApplication.class, args);
    }

}

Nacos的简单介绍以及服务注册与发现功能的基本使用这篇博客一样,先开启三个nacos-provider服务。再开启一个nacos-consumer服务

登录服务器部署的nacos服务的控制台(默认账号和密码都是nacos)

服务器IP:8848/nacos

我们可以看到我们注册好的4个服务实例
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现

测试Ribbon自带的随机策略

配置类,设置成 RandomRule

 return new RandomRule();

如下所示,将RandomRule注入到Spring 的IOC 容器中进行管理

@Configuration
public class RibbonConfig {

    @Bean
    public IRule iRule() {
//        权重负载策略 nacos的负载均衡实现
//        return new NacosRule(); 是spring cloud alibaba 继承了netflix.loadbalancer包的AbstractLoadBalancerRule抽象类的实现

//        会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
//        return new BestAvailableRule();

//        复合判断server所在区域的性能和server的可用性选择服务器
//        return new ZoneAvoidanceRule();

//        先过滤掉故障实例,再选择并发较小的实例
//        return new AvailabilityFilteringRule();

//      对RoundRobinRule 轮询的扩展,响应速度越快的实例选择权重越多大,越容易被选择
//        return new WeightedResponseTimeRule();

//        return new ResponseTimeWeightedRule(); 已经被弃用

//        随机
        return new RandomRule();

//        先按照RoundRobinRule 轮询的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务
//        return new RetryRule();

    }
    
}

访问nacos-consumer服务,然后nacos-consumer服务访问请求nacos-provider服务,点击刷新多次
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现
1号provider服务实例
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现
2号provider服务实例
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现
3号provider服务实例
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现
可以看出,确实是随机策略

测试自定义的负载均衡策略

修改RibbonConfig,向Spring IOC容器中注入MyLoadBalancer

return new MyLoadBalancer();
@Configuration
public class RibbonConfig {

    @Bean
    public IRule iRule() {
//        权重负载策略 nacos的负载均衡实现
//        return new NacosRule(); 是spring cloud alibaba 继承了netflix.loadbalancer包的AbstractLoadBalancerRule抽象类的实现

//        会先过滤掉由于多次访问故障而处于断路器跳闸状态的服务,然后选择一个并发量最小的服务
//        return new BestAvailableRule();

//        复合判断server所在区域的性能和server的可用性选择服务器
//        return new ZoneAvoidanceRule();

//        先过滤掉故障实例,再选择并发较小的实例
//        return new AvailabilityFilteringRule();

//      对RoundRobinRule 轮询的扩展,响应速度越快的实例选择权重越多大,越容易被选择
//        return new WeightedResponseTimeRule();

//        return new ResponseTimeWeightedRule(); 已经被弃用

//        随机
//        return new RandomRule();

//        先按照RoundRobinRule 轮询的策略获取服务,如果获取服务失败则在指定时间内进行重试,获取可用的服务
//        return new RetryRule();
        
        //自己的负载均衡策略,只访问可访问服务列表的下标为1的服务实例
        return new MyLoadBalancer();
        
    }

}

重新运行nacos-consumer服务实例
然后再次使用浏览器访问nacos-consumer服务,让他去消费nacos-provider服务。
【Spring Cloud】Ribbon负载均衡策略介绍以及自定义负载均衡实现
我多次点击刷新浏览器,只有3号provider服务实例的被调用次数在增加,说明我们的修改生效了。

注意:虽然我的负载均衡策略是设置的活动服务实例列表中的下标为1的服务实例接收消费。这里虽然是3号provider实例,但是下标为1是指列表里面的下标为1

//获取已启动且可访问的服务器。
List<Server> upList = lb.getReachableServers();

References:

  • https://www.cnblogs.com/roytian/p/12176321.html
  • https://zhuanlan.zhihu.com/p/180300022
  • https://www.jianshu.com/p/861ed1960014

(写博客主要是对自己学习的归纳整理,资料大部分来源于书籍、网络资料和自己的实践,整理不易,但是难免有不足之处,如有错误,请大家评论区批评指正。同时感谢广大博主和广大作者辛苦整理出来的资源和分享的知识。)

上一篇:dotnet 使用 FormatterServices 的 GetUninitializedObject 方法在丢失 DLL 情况下能否执行


下一篇:Nacos配置管理最佳实践