Spring Cloud Ribbon 负载均衡使用策略示例详解

Java
435
0
0
2023-04-12
标签   SpringCloud
目录
  • 一、前言
  • 二、什么是 Ribbon
  • 2.1 ribbon简介
  • 2.1.1  ribbon在负载均衡中的角色
  • 2.2 客户端负载均衡
  • 2.3 服务端负载均衡
  • 2.4 常用负载均衡算法
  • 2.4.1 随机算法
  • 2.4.2 轮询算法
  • 2.4.3 加权轮询算法
  • 2.4.4 IP地址hash
  • 2.4.5 最小链接数
  • 三、Ribbon中负载均衡策略总探究
  • 3.1 nacos中使用ribbon过程
  • 3.1.1 添加配置类
  • 3.1.2 接口层调用
  • 3.2 Ribbon中负载均衡配置策略
  • 3.2.1 IRule
  • 3.2.2 AbstractLoadBalancerRule
  • 3.2.3 RandomRule
  • 3.2.4  RoundRobinRule
  • 3.2.5  RetryRule
  • 3.2.6 WeightedResponseTimeRule
  • 3.2.7 ClientConfigEnabledRoundRobinRule
  • 3.2.8 BestAvailableRule
  • 3.2.9 ZoneAvoidanceRule
  • 3.2.10 AvailabilityFilteringRule
  • 四、Ribbon负载均衡策略修改
  • 4.1 通过配置类方式修改
  • 4.1.1 增加一个负载均衡策略配置类
  • 4.1.2 启动类指定负载均衡配置类
  • 4.1.3 测试负载均衡策略是否生效
  • 4.2 通过配置文件方式修改
  • 4.2.1 修改启动类
  • 4.2.2 配置文件指定具体的负载均衡策略
  • 4.2.3 修改stock-service 的权重配置
  •  4.3 自定义负载均衡策略
  • 4.3.1 自定义负载均衡类
  • 4.3.2 配置文件中使用自定义配置类
  • 4.3.3 启动工程并测试
  • 4.3.4 ribbon负载均衡懒加载模式
  • 五、Spring Cloud LoadBalancer
  • 5.1 概述
  • 5.1.1 RestTemplate
  • 5.1.2 Webclient
  • 5.2 RestTemplate 整合LoadBalancer
  • 5.2.1 创建一个新的order模块,引入下面的依赖
  • 5.2.2 添加如下配置文件
  • 5.2.3 启动类
  • 5.2.4 模拟测试
  • 六、写在文末

一、前言

在上一篇,通过springcloud整合nacos,了解了基于nacos作为服务注册中心时实现微服务之间的快捷调用,其中,为了实现服务A对服务B的调用,在RestConfig配置类中配置RestTemplate时候,引入了一个叫做LoadBalanced的注解,于是不仅有小伙伴要问,这个注解是干嘛用的呢?本篇将详细的探讨这个问题。

二、什么是 Ribbon

2.1 ribbon简介

Spring Cloud Ribbon 是基于Netflix Ribbon 实现的一套客户端负载均衡工具,Ribbon客户端组件提供了一系列的完善的配置,如超时,重试等。

通过Load Balancer获取到服务提供的所有机器实例,Ribbon会自动基于某种规则(轮询,随机)去调用这些服务。Ribbon也可以实现我们自己的负载均衡算法。

2.1.1  ribbon在负载均衡中的角色

负载均衡这个概念大家都不陌生,几乎可以说是互联网公司标配,目前主流负载方案主要分为以下两种:

集中式负载均衡,在消费者和服务提供方中间使用独立的代理方式进行负载,有硬件的(比如 F5),也有软件的(比如 Nginx);客户端根据自身请求情况做负载均衡,Ribbon 就属于客户端自己做负载均衡,在dubbo中,客户端也可以配置负载均衡策略,也是类似的思想;

2.2 客户端负载均衡

顾名思义,就是由客户端根据自身的情况做负载均衡策略的选择或配置,比如spring cloud中的ribbon,简单来说,当服务提供者将自身的服务注册到服务注册中心之后,客户端相应的会维护一个服务器地址列表,在发送请求前,先通过负载均衡算法选择一个服务器,然后进行访问,这就是客户端负载均衡,即在客户端就进行负载均衡算法分配。

如下,为ribbon作为客户端的负载均衡的调用流程图。

2.3 服务端负载均衡

即集中式的管控负载均衡策略的组件,比如Nginx,即所有的请求过来之后,先通过Nginx进行负载均衡,先发送请求到具体的微服务,然后通过nginx中配置的负载均衡算法,在多个服务器之间选择一个进行访问,即在服务器端再进行负载均衡算法分配。

2.4 常用负载均衡算法

下面列举日常开发中经常用到的一些负载均衡算法,可以说很多组件的负载均衡的底层算法思想都是大同小异

2.4.1 随机算法

通过随机选择集群中的某个可用的服务进行请求执行,在集群的各服务器配置差不多的情况下可以考虑使用,一般这种方式使用较少

2.4.2 轮询算法

负载均衡默认的实现方式,请求过来之后,依次轮流从可用服务中选择进行处理

2.4.3 加权轮询算法

通过对服务器性能的分型,给高配置,低负载的服务器分配更高的权重,均衡各个服务器的压力;

2.4.4 IP地址hash

根据请求的地址进行Hash,通过客户端请求的地址的HASH值取模映射进行服务器调度, ip --->hash,这样相同请求的IP将会被打到相同的服务器处理;

2.4.5 最小链接数

即使请求均衡了,压力也不一定会均衡,最小连接数法就是根据服务器的情况,比如请求积压数等参数,将请求分配到当前压力最小的服务器上,最小连接数也叫最小活跃数。

三、Ribbon中负载均衡策略总探究

3.1 nacos中使用ribbon过程

在上一篇,springcloud-alibaba整合nacos中,客户端通过nacos进行服务调用默认使用的就是Ribbon负载均衡,只需下面两步

3.1.1 添加配置类

添加一个RestTemplate 的配置bean,使用LoadBalanced注解标注

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

3.1.2 接口层调用

具体调用的时候,仍然使用restTemplate调用即可,如果服务端有多个实例,将会自动走默认负载均衡策略;

@RestController
@RequestMapping("/order")
public class OrderController {
 
    @Autowired
    RestTemplate restTemplate;
 
    @Value("${service-url.nacos-user-service}")
    private String serverURL;
 
    //localhost:8083/order/add
    @GetMapping("/add")
    public String add(){
        System.out.println("下单成功");
        //String forObject = restTemplate.getForObject("http://localhost:8082/stock/reduct", String.class);
        String forObject = restTemplate.getForObject(serverURL + "/stock/reduct", String.class);
        System.out.println(forObject);
        return "add order  :" + forObject;
    }
 
}

3.2 Ribbon中负载均衡配置策略

通过完整的类的依赖关系图,可以看到Ribbon提供了很多种负载均衡策略,下面就Ribbon中常用的负载均衡策略做详细的解读。

3.2.1 IRule

这是所有负载均衡策略的父接口,里边的核心方法就是choose方法,用来选择一个服务实例

3.2.2 AbstractLoadBalancerRule

一个抽象类,里边主要定义了一个ILoadBalancer,这里定义它的目的主要是辅助负责均衡策略选取合适的服务端实例。

3.2.3 RandomRule

这种负载均衡策略就是随机从多个服务实例中选择一个服务实例

通过源码发现,在RandomRule的无参构造方法中初始化了一个Random对象,然后在它重写的choose方法又调用了choose(ILoadBalancer lb, Object key)这个重载的choose方法,在这个重载的choose方法中,每次利用random对象生成一个不大于服务实例总数的随机数,并将该数作为下标所以获取一个服务实例;

可以参阅源码

public Server choose(ILoadBalancer lb, Object key) {
        if (lb == null) {
            return null;
        } else {
            Server server = null;
 
            while(server == null) {
                if (Thread.interrupted()) {
                    return null;
                }
 
                List<Server> upList = lb.getReachableServers();
                List<Server> allList = lb.getAllServers();
                int serverCount = allList.size();
                if (serverCount == 0) {
                    return null;
                }
 
                int index = this.chooseRandomInt(serverCount);
                server = (Server)upList.get(index);
                if (server == null) {
                    Thread.yield();
                } else {
                    if (server.isAlive()) {
                        return server;
                    }
 
                    server = null;
                    Thread.yield();
                }
            }
 
            return server;
        }
    }

3.2.4  RoundRobinRule

RoundRobinRule 这种负载均衡策略也叫线性轮询负载均衡策略

这个类的choose(ILoadBalancer lb, Object key)方法整体逻辑是这样的:

  • 开启一个计数器count,在while循环中遍历务清单,获取清单之前先通过incrementAndGetModulo方法获取一个下标,这个下标是一个不断自增长的数先加1然后和服务清单总数取模之后获取到的(所以这个下标从来不会越界);
  • 然后拿着下标再去服务清单列表中取服务,每次循环计数器都会加1,如果连续10次都没有取到服务,则会报一个警告No available alive servers after 10 tries from load balancer: XXXX;

3.2.5  RetryRule

在轮询基础上重试,即这种负载均衡策略带有重试功能

它整体工作流程是这样:

  • 首先RetryRule中定义了一个subRule,它的实现类是RoundRobinRule;
  • 然后在RetryRule的choose(ILoadBalancer lb, Object key)方法中,每次还是采用RoundRobinRule中的choose规则来选择一个服务实例;
  • 如果选到的实例正常就返回,如果选择的服务实例为null或者已经失效,则在失效时间deadline之前不断的进行重试(重试时获取服务的策略还是RoundRobinRule中定义的策略);
  • 如果超过了deadline还是没取到则会返回一个null;

3.2.6 WeightedResponseTimeRule

顾名思义,带有权重功能,权重 — nacos的NacosRule ,Nacos还扩展了一个自己的基于配置的权重扩展 

大致工作原理如下:

  • WeightedResponseTimeRule是RoundRobinRule的一个子类,在WeightedResponseTimeRule中对RoundRobinRule的功能进行了扩展;
  • WeightedResponseTimeRule中会根据每一个实例的运行情况来给计算出该实例的一个权重,然后在挑选实例的时候则根据权重进行挑选,这样能够实现更优的实例调用;
  • WeightedResponseTimeRule中有一个名叫DynamicServerWeightTask的定时任务,默认情况下每隔30秒会计算一次各个服务实例的权重;
  • 权重计算规则很简单,如果一个服务的平均响应时间越短则权重越大,那么该服务实例被选中执行任务的概率也就越大;

3.2.7 ClientConfigEnabledRoundRobinRule

这种策略实现很简单,内部定义了RoundRobinRule,choose方法还是采用了RoundRobinRule的 choose方法,所以它的选择策略和RoundRobinRule的选择策略一致,就不再过多不赘述。

3.2.8 BestAvailableRule

  • BestAvailableRule继承自ClientConfigEnabledRoundRobinRule;
  • 它在ClientConfigEnabledRoundRobinRule的基础上主要增加了根据loadBalancerStats中保存的服务实例的状态信息来过滤掉失效的服务实例的功能,然后顺便找出并发请求最小的服务实例来使用;
  • 然而loadBalancerStats有可能为null,如果loadBalancerStats为null,则BestAvailableRule将采用它的父类即ClientConfigEnabledRoundRobinRule的服务选取策略(线性轮询);

3.2.9 ZoneAvoidanceRule

默认规则 ,复合判断server所在区域的性能和server的可用性选择服务器
  • ZoneAvoidanceRule是PredicateBasedRule的一个实现类,只不过这里多一个过滤条件;
  • ZoneAvoidanceRule中的过滤条件是以ZoneAvoidancePredicate为主过滤条件和以AvailabilityPredicate为次过滤条件组成的一个叫做CompositePredicate的组合过滤条件;
  • 过滤成功之后,继续采用线性轮询(RoundRobinRule)的方式从过滤结果中选择一个出来;

3.2.10 AvailabilityFilteringRule

先过滤掉故障实例,再选择并发较小的实例,具体来说,过滤掉一直连接失败的被标记为circuit tripped的后端Server,并过滤掉那些高并发的后端Server或者使用一个AvailabilityPredicate来 包含过滤server的逻辑,其实就是检查status里记录的各个Server的运行状态。

四、Ribbon负载均衡策略修改

上面详细了解了Ribbon中的常用的一些负载均衡配置策略,接下来通过实际操作演示下如何修改Ribbon的负载均衡配置策略。

4.1 通过配置类方式修改

这是一种比较灵活的修改配置方式,只需要添加一个配置类,并配置指定的负载均衡bean即可

4.1.1 增加一个负载均衡策略配置类

注意这里有个坑,自定义的配置类不能写在@SpringbootApplication注解的@CompentScan扫描得到的地方,否则自定义的配置类就会被所有的 RibbonClients共享。

因此实际开发中不建议这么使用,推荐yml方式

工程的目录结构如下

RibbonRandomRuleConfig 配置类

@Configuration
public class RibbonRandomRuleConfig {
 
    @Bean
    public IRule iRule(){
        return new RandomRule();
    }
}

4.1.2 启动类指定负载均衡配置类

@SpringBootApplication
@RibbonClients(value = {
        @RibbonClient(name = "stock-service",configuration = RibbonRandomRuleConfig.class)
})
public class RibbonOrderApp {
 
    public static void main(String[] args) {
        SpringApplication.run(RibbonOrderApp.class,args);
    }
 
}

4.1.3 测试负载均衡策略是否生效

分别启动stock-service的两个工程,再启动order工程,stock-service可以在idea中通过区分端口的方式

启动完成之后,检查nacos,可以看到stock-service有两个服务实例,这正好是我们希望的

调用order模块的接口:http://localhost:8030/order/add,由于是客户端负载均衡,而且我们配置的是随机的策略,预计在随机调用该接口之后,会呈现出不同的接口

 多调用几次,可以看到8021与8023对应的服务不断交替出现,并且没什么规律

4.2 通过配置文件方式修改

4.2.1 修改启动类

去掉启动类中的注解信息

@SpringBootApplication
/*@RibbonClients(value = {
        @RibbonClient(name = "stock-service",configuration = RibbonRandomRuleConfig.class)
})*/
public class RibbonOrderApp {
 
    public static void main(String[] args) {
        SpringApplication.run(RibbonOrderApp.class,args);
    }
 
}

4.2.2 配置文件指定具体的负载均衡策略

在配置文件下方增加如下配置,这里是哦也能够的是nacos提供的基于权重的策略,也可以选择其他的;

stock-service:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule

4.2.3 修改stock-service 的权重配置

在nacos服务列表那一栏,进入到如下所示 stock-servide具体的详情页面

分别编辑两个实例的权重大小,比如这里给8023实例权重为10,另一个保持默认为1

设置完成后按照预期猜想,设置权重更高的那个将会优先得到访问的机会,启动order服务,再次按照上面的方式调用多次观察效果

粗略统计发现,在10多次的请求中,权重高的那个被访问的次数更多,这也就符合我们的预期效果了;

 4.3 自定义负载均衡策略

 从上面的ribbon类图可以发现,通过实现 IRule 接口可以自定义负载策略,主要的选择服务逻辑在 choose 方法中完成,最简单的就是继承AbstractLoadBalancerRule这个抽象类;

4.3.1 自定义负载均衡类

只需要继承AbstractLoadBalancerRule抽象类,并重写里面的choose方式,在该方式中,使用了类似的随机算法的策略,也可以根据自身的需求,定制特定的负载均衡算法;

public class CustomRule extends AbstractLoadBalancerRule {
 
    private static Logger logger = LoggerFactory.getLogger(CustomRule.class);
 
 
    @Override
    public Server choose(Object o) {
 
        ILoadBalancer loadBalancer = this.getLoadBalancer();
        List<Server> reachableServers = loadBalancer.getReachableServers();
        int i = ThreadLocalRandom.current().nextInt(reachableServers.size());
        Server server = reachableServers.get(i);
        return server;
    }
 
    @Override
    public void initWithNiwsConfig(IClientConfig iClientConfig) {
 
    }
}

4.3.2 配置文件中使用自定义配置类

stock-service:
  ribbon:
    #NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule
    #使用自定义的负载均衡策略
    NFLoadBalancerRuleClassName: com.ribbon.CustomRule

4.3.3 启动工程并测试

将上一步的nacos中stock-service的两个服务实例的权重都调整为1,然后再次访问接口,可以看到呈现出随机的效果;

4.3.4 ribbon负载均衡懒加载模式

Ribbon的负载均衡策略默认是懒加载,意味着只有在发起调用的时候才会创建客户端,这带来的问题是,如果网络不好,第一次调用的时候可能会出现调用超时,我们通过控制台日志也能看出来;

如何解决这个问题呢?可以手动开启饥饿加载,来解决第一次调用慢的问题,只需要在配置文件中添加如下配置即可;

ribbon:
  eager-load:
    # 开启ribbon饥饿加载
    enabled: true
    # 配置stock-service使用ribbon饥饿加载,多个使用逗号分隔
    clients: stock-service

再次重启order服务,通过控制台输出日志可以看到,在启动的时候负载均衡策略就被加载了;

五、Spring Cloud LoadBalancer

5.1 概述

Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器, 用来替代
Ribbon

Spring 官方提供了两种负载均衡客户端

5.1.1 RestTemplate

RestTemplate是 Spring提供的用于访问Rest服务的客户端,RestTemplate提供了多种便捷访问远程Http服务的方法,能够大大提高客户端的编写效率。默认情况下,RestTemplate默认依赖jdk的HTTP连接工具

5.1.2 Webclient

WebClient是从Spring WebFlux 5.0版本开始提供的一个非阻塞的基于响应式编程的进行Http请求的客户端工具。它的响应式编程的基于Reactor的。WebClient中提供了标准Http请求方式对应的get、post、put、delete等方法,可以用来发起相应的请求

5.2 RestTemplate 整合LoadBalancer

5.2.1 创建一个新的order模块,引入下面的依赖

 <dependencies>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <!--nacos-config 配置中心-自带动态刷新-->
        <!--<dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>-->
 
        <!--nacos-discovery 注册中心-服务发现与注册-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.cloud</groupId>
                    <artifactId>spring‐cloud‐starter‐netflix‐ribbon</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring‐cloud‐starter‐loadbalancer</artifactId>
        </dependency>
 
    </dependencies>

5.2.2 添加如下配置文件

这里主要是将默认的ribbon的loadbalancer负载均衡策略禁用掉

server:
  port: 8033
 
spring:
  application:
    name: order-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #服务注册中心地址
      #config:
        #server-addr: localhost:8848 #配置中心地址
    loadbalancer:
      ribbon:
        enabled: false
 
service-url:
  nacos-user-service: http://stock-service

5.2.3 启动类

@SpringBootApplication
//@EnableDiscoveryClient
public class BalancerOrderApp {
    public static void main(String[] args) {
        SpringApplication.run(BalancerOrderApp.class,args);
    }
 
}

5.2.4 模拟测试

启动stock-service和当前order模块,启动完成后,界面调用:localhost:8033/order/add

loadbalancer默认走的是轮询策略,通过反复调用上面的接口,可以看到请求的结果端口显示看,是stock-service两个服务轮着来的;

六、写在文末

在微服务治理过程中,尽管来说Ribbon现在使用的并不算太多,但Ribbon在springcloud的生态体系下的出现,可以说具有重要的意义,真正将客户端负载均衡的思想进行了落地和解决,而且提供了较为完备的负载均衡策略,给予了开发者相当的扩展性,这也为其他的微服务治理框架提供了很好的思路。