负载均衡器:Ribbon和LoadBalance

一、负载均衡介绍

1、什么是负载均衡?

阿里云对负载均衡的定义:

负载均衡是一种对流量进行按需分发的服务,通过将流量分发到不同的后端服务器来扩展应

用系统的吞吐能力,并且可以消除系统中的单点故障,提升应用系统的可用性。

2、负载均衡的2种方式

负载均衡一般分:服务端负载均衡 和 客户端负载均衡

2.1、服务端负载均衡

传统的方式前端发送请求会到我们的的nginx上去,nginx作为反向代理,然后路由给后端的

服务器,由于负载均衡算法是nginx提供的,而nginx是部署到服务器端的,所以这种方式又

被称为服务器端负载均衡。如下图所示:

2.2、客户端负载均衡

假如现在有三个用户中心实例,订单中心可以通过discoveryClient 获取到用户中心的实例信

息,如果我们在订单中心写一个负载均衡 的规则计算请求那个实例,交给restTemplate进行

请求,这样也可以实现负载均衡,这个算法里面,负载均衡是由订单中心提供的,而订单中

心相对于用户中心是一个客户端,所以这种方式又称为客户端负负载均衡。如下图所示:

在订单中心(服务调用方)编写负载均衡方法如下:

java 复制代码
 /**
     * DiscoveryClient 是 nacos 提供的类,通过该类可以获取Nacos注册中心的服务列表
     */
    @Autowired
    private DiscoveryClient discoveryClient;

    @GetMapping("/order/create")
    public String createOrder(Integer productId,Integer userId){

        /**
         * 准备:向nacos 注册多个(这里启动3个)名称为 shop-stock 的服务
         *
         * todo : 2种负载均衡的方式
         *    1)通过类 DiscoveryClient 获取Nacos 服务注册中心种服务名称为 shop-stock 的服务列表,
         *          然后通过随机数或轮询的方式 获取服务
         *
         */
        List<ServiceInstance> instances =  discoveryClient.getInstances("shop-stock");
        //获取目标服务地址
        List<String> targetUrl = instances.stream().map(instance -> instance.getUri().toString()+"/stock/reduce/").collect(Collectors.toList());
        //随机获取服务
        int index = ThreadLocalRandom.current().nextInt(targetUrl.size());
        String target = targetUrl.get(index);
        //调用目标服务
        String result2 = restTemplate.getForObject(target + productId, String.class);
         


        return "sucess";
    }

二、Ribbon

1、Ribbon是什么?

Ribbon是Netflix开源的客户端侧负载均衡器;更加直观说就是ribbon就是简化上边方法createOrder中的这段代码的小组件,不过他比我们的代码要强大一些,他给他们提供了丰富

的负载均衡算法;如下图所示:

2、Ribbon 重要接口及内置的负载均衡策略

2.1、Ribbon 重要的接口

接口 作用 默认值
IClientConfig 读取配置 DefaultclientConfigImpl
IRule 负载均衡规则,选择实例 ZoneAvoidanceRule
IPing 筛选掉ping不通的实例 默认采用DummyPing实现,该检查策略是一个特殊的实现,<br />实际上它并不会检查实例是否可用,而是始终返回true,默认认为所<br />有服务实例都是可用的.
ServerList<Server> 交给Ribbon的实例列表 Ribbon : ConfigurationBasedServerList<br /> Spring Cloud Alibaba: NacosServerList
ServerListFilter 过滤掉不符合条件的实例 ZonePreferenceServerListFilter
ILoadBalancer Ribbon的入口 ZoneAwareLoadBalancer
ServerListUpdater 更新交给Ribbon的List的策略 PollingServerListUpdater

2.2、Ribbon内置的负载均衡策略

规则名称 特点
RandomRule 随机选择一个Server
RetryRule 对选定的负责均衡策略机上充值机制,在一个配置时间段内当选择Server不成功,则一直尝试使用subRule的方式选择一个可用的Server
RoundRobinRule 轮询选择,轮询index,选择index对应位置Server
WeightedResponseTimeRule 根据相应时间加权,相应时间越长,权重越小,被选中的可能性越低
ZoneAvoidanceRule (默认是这个)该策略能够在多区域环境下选出最佳区域的实例进行访问。在没有Zone的环境下,类似于轮询(RoundRobinRule)

3、Ribbon 使用步骤

前期准备:启动3个shop-stock 服务和一个shop-order服务,在shop-order 中引入Ribbon

远程调用shop-stock

3.1、引入 netflix ribbon 依赖

注意:若你的项目中已经引入了 alibaba nacos discovery ,就没必要再引入 netflix ribbon

,因为 alibaba nacos discovery 中包含了 netflix ribbon;如下图所示:

netflix ribbon 依赖如下所示:

XML 复制代码
<!--spring-cloud-starter-netflix-ribbon -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
    <version>2.2.10.RELEASE</version>
</dependency>

2、配置Ribbon负载均衡策略(重点)

Ribbon 负载均衡策略的配置方式有2种,分别是:类配置 和 配置文件配置

注意:这一步不是必须项,若没有配置Ribbon的负载均衡策略,Ribbon 则使用默认的负载均

衡策略。

java 类配置的优先级要高于配置文件配置

2.1、使用配置类的方式配置

使用配置类的方式配置Ribbon 负载均衡策略分为2步,分别是:编写注入Ribbon负载均衡

配置类 和 为Ribbon指定负载均衡策略,下边详细看下这2个步骤;

2.1.1、编写注入Ribbon负载均衡

编写配置类RibbonConfig 来注入 Ribbon 负载均衡策略;

有一点要注意,即类 RibbonConfig上并没有添加注解 @Configuration,主要原因有2个,

即:

1)若 RibbonConfig 上加了注解 @Configuration,这种方式配置全局的负载均衡策略

有bug,不支持使用;推荐使用注解@RibbonClients 来配置全局的负载均衡策略

2)若 RibbonConfig 上加了注解 @Configuration,该负载均衡策略就成了全局负载均

衡策略,粒度太大。

RibbonConfig 示例代码如下:

java 复制代码
/****************************************************
 * Ribbon 配置类,用于配置Ribbon 相关信息,如:负载均衡策略
 *
 * todo 注意:
 *    Ribbon 配置类 上并没有添加注解 @Configuration,因为我想只在远程调用 shop-stock 模块时使用RibbonConfig
 *    中配置的负载均衡策略;
 *    若 RibbonConfig 上加了注解 @Configuration,该类中的负载均衡策略就变成了全局的负载均衡策略,
 *     shop-order调用任何模块都会使用该类中的负载均衡策略。
 *     注意:这种方式配置全局的负载均衡策略有bug,不支持使用;推荐使用注解@RibbonClients 来配置全局的负载均衡策略
 *
 *
 *
 * @author 
 * @date 2025/6/13 9:15
 ****************************************************/
public class RibbonConfig {

    /**
     * 采用类配置Ribbon负载均衡策略
     * 向spring 种注入指定的 Ribbon 负载均衡实例,哪个Ribbon负载均衡策略被实例化了,RPC远程调用时就用
     * 哪个负载均衡策略;即通过配置类手动指定Ribbon的负载均衡策略
     *
     */
    @Bean
    public IRule ribbonRule(){
        //指定Ribbon的负载均衡策略为随机
        return new RandomRule();
    }
}

2.1.2、为Ribbon指定负载均衡策略

Ribbon 提供了注解 @RibbonClient和@RibbonClients 来选择负载均衡策略;

@RibbonClient 可以用来为某个服务指定Ribbon的负载均衡策略(如:shop-stock),

粒度比较细,@RibbonClients 是用来为当前客户端段服务配置全局的Ribbon负载均衡

策略,@RibbonClient和@RibbonClients 需要标注在 @Configuration 配置类上

示例代码如下:

java 复制代码
/****************************************************
 * 配置类,不编写什么内容,用于指定调用目标服务(如:shop-stack)时所采用的负载均衡策略
 *
 * @author 
 * @date 2025/6/13 9:55
 ****************************************************/

/**
 * 注解 @RibbonClient 用于指定当前服务(如:shop-order)以哪个负载均衡策略调用目标服务(如:shop-stack)
 */
//@RibbonClient(name = "shop-stack",configuration = RibbonConfig.class)
/**
 * 注解 @RibbonClients 用于全局的配置Ribbon负载均衡策略,通过 RestTemplate 远程调用每个服务
 * 时,负载均衡策略都是 RibbonConfig 中配置的策略
 *
 */
@RibbonClients(defaultConfiguration = RibbonConfig.class)
@Configuration
public class StockRibbonConfig {
}

2.2、使用配置文件的方式配置Ribbon负载均衡策略

配置文件内容如下:

3、开启远程调用的负载均衡

在实例化远程调用工具(如:RestTemplate)的Bean 上加上注解 @LoadBalanced 来开启

远程调用的负载均衡(即整合 Ribbon),示例代码如下:

java 复制代码
@LoadBalanced
    @Bean
    public RestTemplate restTemplate(){

        RestTemplate restTemplate = new RestTemplate();

        return restTemplate;
    }

4、在通过 RestTemplate 远程调用 shop-stock 服务时,就可以实现Ribbon的负载均衡了;

示例代码如下:

java 复制代码
@GetMapping("/order/create")
    public String createOrder(Integer productId,Integer userId){

        /**
         * 测试 RestTemplate 的负载均衡
         * 准备:向nacos 注册多个名称为 shop-stock 的服务
         *
         * todo : 2种负载均衡的方式
         *    1)通过类 DiscoveryClient 获取Nacos 服务注册中心种服务名称为 shop-stock 的服务列表,
         *          然后通过随机数或轮询的方式 获取服务
         *    2)在配置类种注入 RestTemplate 时,加上注解 @LoadBalanced,即整合Ribbon,使用Ribbon的负载均衡来实现
         *          远程调用
         */

        /**
        //方式1:
        List<ServiceInstance> instances =  discoveryClient.getInstances("shop-stock");
        //获取目标服务地址
        List<String> targetUrl = instances.stream().map(instance -> instance.getUri().toString()+"/stock/reduce/").collect(Collectors.toList());
        //随机获取服务
        int index = ThreadLocalRandom.current().nextInt(targetUrl.size());
        String target = targetUrl.get(index);
        //调用目标服务
        String result2 = restTemplate.getForObject(target + productId, String.class);
         */

        //方式2:
        String result = restTemplate.getForObject("http://shop-stock/stock/reduce/" + productId, String.class);


        return "sucess";
    }

三、LoadBalancer

1、LoadBalancer 介绍

Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器,抽象和实现,

用来替代Ribbon(已经停更)

2、LoadBalancer 与Ribbon 的对比

LoadBalancer 与Ribbon 的对比列表如下:

组件 组件提供的负载策略 支持负载的客户端
Ribbon 随机 RandomRule 轮询 RoundRobinRule 重试 RetryRule 最低并发 BestAvailableRule 可用过滤 AvailabilityFilteringRule 响应时间加权重 ResponseTimeWeightedRule 区域权重 ZoneAvoidanceRule Feign或openfeign、RestTemplate
Spring Cloud Loadbalancer RandomLoadBalancer 随机(高版本有)RoundRobinLoadBalancer 轮询(默认) Feign或openfeign、RestTemplate、WebClient(WebFlux 异步)

3、整合 LoadBalancer

3.1、升级 Spring Cloud

若你的Spring Cloud 是Hoxton之前的版本,最好升级到 Hoxton 之后的版本;

因为如果是Hoxton之前的版本,默认负载均衡器为Ribbon,需要移除Ribbon引用和

增加配置spring.cloud.loadbalancer.ribbon.enabled: false(不启用ribbon)

3.2、在 nacos discovery 依赖中排除 Ribbon 的依赖,并引入 LoadBalancer 依赖,如下图:

3.3、开启远程调用负载均衡

在实例化远程调用工具(如:RestTemplate)的Bean 上加上注解 @LoadBalanced 来

开启远程调用的负载均衡(即整合 LoadBalancer);

可选配置:

跟Ribbon 一样,LoadBalancer也提供了注解 @LoadBalancerClient 和

@LoadBalancerClients 用于显式的指定 LoadBalancer的负载均衡策略;

@LoadBalancerClient 和 @LoadBalancerClients 需要标注在 @Configuration

配置类上,

如下所示:

java 复制代码
/****************************************************
 * 配置类,注入 RestTemplate
 * @author lbf
 * @date 2025/6/13 15:30
 ****************************************************/

/**
 * 配置使用 loadbalancer 负载均衡器,
 * 局部的负载均衡器,使用name指定目标服务,configuration 指定调用目标服务所使用的负载均衡器,如:shop-stock
 */
@LoadBalancerClient(name = "shop-stock",configuration = LoadBalancerClientConfiguration.class)
/**
 * 使用注解 @LoadBalancerClients 配置全局的负载均衡器,表示在当前服务中远程调用任何目标服务,所使用的
 * 负载均衡器都是 CustomRandomLoadBalancerClient
 */
//@LoadBalancerClients(defaultConfiguration = LoadBalancerClientConfiguration.class)
@Configuration
public class RestTemplateConfig {


    /**
     *
     * 注解 @LoadBalanced 开启远程调用的负载均衡
     */
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

3.4、在通过 RestTemplate 远程调用 shop-stock 服务时,就可以实现LoadBalancer的负载均衡了

如下图所示:

4、自定义 LoadBalancer 负载均衡策略

LoadBalancer 中 自定义负载均衡策略需要通过注解 @LoadBalancerClient 或

@LoadBalancerClients 来显式的指定才能生效。

示例代码如下:

java 复制代码
/****************************************************
 * 自定义随机选择的负载均衡器
 * 自定义负载均衡器用法:
 *     1)实现接口 ReactorServiceInstanceLoadBalancer 自定义负载均衡器
 *     2)在@Configuration  配置类上使用注解
 *
 * @author 
 * @date 2025/6/13 15:49
 ****************************************************/
public class CustomRandomLoadBalancerClient implements ReactorServiceInstanceLoadBalancer {
    // 服务列表
    private ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider;

    public CustomRandomLoadBalancerClient(ObjectProvider<ServiceInstanceListSupplier> serviceInstanceListSupplierProvider) {
        this.serviceInstanceListSupplierProvider = serviceInstanceListSupplierProvider;
    }

    @Override
    public Mono<Response<ServiceInstance>> choose(Request request) {
        ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider.getIfAvailable();
        return supplier.get().next().map(this::getInstanceResponse);
    }

    /**
     * 使用随机数获取服务
     *
     * @param instances  服务列表
     * @return
     */
    private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
        System.out.println("进来了");
        if (instances.isEmpty()) {
            return new EmptyResponse();
        }

        System.out.println("进行随机选取服务");
        // 随机算法
        int size = instances.size();
        Random random = new Random();
        ServiceInstance instance = instances.get(random.nextInt(size));

        return new DefaultResponse(instance);
    }
}



/****************************************************
 * 配置类,注入 RestTemplate
 * @author 
 * @date 2025/6/13 15:30
 ****************************************************/

/**
 * 配置使用 loadbalancer 负载均衡器,
 * 局部的负载均衡器,使用name指定目标服务,configuration 指定调用目标服务所使用的负载均衡器,如:shop-stock
 */
@LoadBalancerClient(name = "shop-stock",configuration = CustomRandomLoadBalancerClient.class)
/**
 * 使用注解 @LoadBalancerClients 配置全局的负载均衡器,表示在当前服务中远程调用任何目标服务,所使用的
 * 负载均衡器都是 CustomRandomLoadBalancerClient
 */
//@LoadBalancerClients(defaultConfiguration = CustomRandomLoadBalancerClient.class)
/**
 * 表示在当前服务中,远程调用服务 shop-stock 使用的负载均衡器是 CustomRandomLoadBalancerClient,
 * 调用其他服务使用的负载均衡器是 LoadBalancerClientConfiguration
 */
/**
@LoadBalancerClients(
        value = {@LoadBalancerClient(name = "shop-stock",configuration = CustomRandomLoadBalancerClient.class)},
        defaultConfiguration = LoadBalancerClientConfiguration.class)
 */
@Configuration
public class RestTemplateConfig {


    /**
     *
     * 注解 @LoadBalanced 开启远程调用的负载均衡
     */
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

5、LoadBalancer 的重试机制配置

具体配置如下:

java 复制代码
spring:
  cloud:
    loadbalancer:
      #以下配置为LoadBalancerProperties 配置类
      clients:
        #default 表示去全局配置,如要针对某个服务,则填写毒地应的服务名称即可
        default:
          retry:
            enbled: true
            #是否所有的的请求都重试,false表示只有GET请求才重试
            retryOnAllOperation: true
            #同一个实例的重试次数,不包括第一次调用:比如第填写3 ,实际会调用4次
            maxRetriesOnSameServiceInstance: 3
            #其他实例的重试次数,多节点情况下使用
            maxRetriesOnNextServiceInstance: 0
相关推荐
JavaAlpha几秒前
Jenkins 配置信息导出 的详细说明(中英对照)
运维·servlet·jenkins
Blossom.1181 小时前
基于区块链的去中心化身份验证系统:原理、实现与应用
运维·服务器·网络·人工智能·机器学习·去中心化·区块链
老实巴交的麻匪1 小时前
可观测性 | Grafana Loki 日志聚合方案快速体验
运维·云原生·容器
flypig哗啦啦1 小时前
ubuntu桌面x11异常修复
linux·运维·ubuntu
will_net1 小时前
Linux入门(十六)shell&shell变量&设置环境变量&位置参数变量&预定义变量
linux·运维·chrome
hie988942 小时前
[nginx]反向代理grpc
运维·nginx·iphone
星释2 小时前
DevOps软件开发流程规范
运维·devops
LetsonH2 小时前
Ubuntu 实现 sudo 免密码关键步骤
linux·运维·ubuntu
Detachym2 小时前
CentOS下的运维监控Grafana部署
运维·centos·grafana
顧棟2 小时前
CentOS 7.3环境中部署Kerberos集群
linux·运维·centos