章娅萝 发表于 2025-9-14 23:27:55

[高可用/负载均衡] Ribbon LoadBalancer: 开源的客户端式负载均衡框架

0 序言


[*]某项目上,原先为自建的数据库集群提供了负载均衡IP服务器(简称: ELB IP Server),客户端的数据库请求URL都统一走ELB IP。但随着业务量的增长,识别到一个严峻的现实:


[*]其一,考虑到未来的业务增长情况,云厂商提供的 ELB IP Server 云服务的入网带宽必将完全无法满足本项目的诉求。
[*]其二,云厂商提供的 ELB IP Server 的费用较为昂贵,实在是不划算。
除了入网带宽的使用量较高外,云厂商ELB 服务提供的其他方面的资源指标,使用量均极低(有浪费钱的嫌疑)。


[*]为此开始尝试:取消服务端式负载均衡器,自行实现客户端式的负载均衡器。
经过一番研究,开源的、支持Java、与spring生态框架独立/解耦的、负载均衡器 Ribbon,成为个人的首选。
即:笔者此时的诉求之一是,不需要引入spring框架,与其解耦。
1 概述:Ribbon LoadBalancer: 开源负载均衡器

负载均衡的概念


[*]负载均衡是一种通过【分发请求】来优化服务器资源利用率和提高系统性能的技术。
它在微服务架构中尤为重要,常见的负载均衡方式包括服务端负载均衡和客户端负载均衡。


[*]服务端负载均衡是指请求首先被发送到【负载均衡服务器】,然后由该服务器根据【负载均衡算法】(如轮询、最小连接数等)将【请求分发】到后端服务器进行处理。


[*]常见的服务端负载均衡工具包括: 硬件设备(如F5)和软件(如Nginx、LVS)。
[*]这种方式的优点是: 客户端无感知、无需关心负载均衡的逻辑,所有的均衡操作都由服务端完成。


[*]客户端负载均衡则是由客户端直接从服务注册中心(如Nacos、Eureka)获取服务列表,并根据负载均衡算法选择目标服务器进行请求分发。
以Spring Cloud中的Ribbon为例,客户端通过RestTemplate触发负载均衡。
客户端负载均衡的特点是无需额外的负载均衡服务器(例如: ELB IP Server),分发逻辑完全由客户端实现。


[*]两者的主要区别在于:负载均衡的实现位置。
服务端负载均衡依赖于专门的负载均衡服务器,而客户端负载均衡则由客户端自行完成分发逻辑。
客户端式负载均衡方案的实现原理


[*]​服务发现客户端​,从注册中心获取服务实例列表并缓存。
[*]客户端请求被 ​负载均衡拦截器​ 截获(如 @LoadBalanced 标记的 RestTemplate/WebClient)。
org.springframework.cloud.client.loadbalancer.LoadBalanced
org.springframework.web.client.RestTemplate

[*]拦截器调用 ​LoadBalancerClient。
org.springframework.cloud.client.loadbalancer.LoadBalancerClient
org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient
4.​ LoadBalancerClient​ 调用底层的负载均衡器 (Ribbon / SCL) 选择一个实例。
5. 负载均衡器根据 ​负载均衡策略​ 从可用实例列表中选择一个目标实例。
6. 请求最终被​转发到选定的实例。
关键问题:负载均衡与请求客户端、连接池的集成


[*]Ribbon 等本身只是一个客户端负载均衡器,它负责从服务列表里挑一台机器,把原请求 URL 中的“服务名”替换成这台服务器的真实 IP+端口。
真正发出 HTTP 请求的是下游的 HTTP 客户端;


[*]若还想把“连接池”能力加进来,就是把这个客户端换成支持池化的实现(OkHttp / Apache HttpClient),并让它复用 Ribbon 等负载均衡框架已挑好的地址。
Ribbon LoadBalancer: 开源的客户端式负载均衡框架


[*]Ribbon


[*]Ribbon有很多子模块,官方文档中说明,目前 Netflix 公司主要用于生产环境的 Ribbon 子模块如下:
[*]ribbon-core:Ribbon 的核心API。
[*]ribbon-loadbalancer:可以独立使用或与其他模块一起使用的负载均衡器 API。
[*]ribbon-eureka:Ribbon 结合 注册中心 Eureka 客户端的 API,为负载均衡器提供动态服务注册列表信息。


[*]Ribbon LoadBalancer


[*]起源于 ​Netflix OSS,曾是 Spring Cloud ​默认的客户端负载均衡解决方案。
[*]Ribbon 是一个独立的、较为成熟的库,被广泛集成到 Spring Cloud Netflix 组件(如 Zuul、Feign)中。
[*]已进入维护模式,Netflix 官方不再积极开发新功能。


[*]技术架构与依赖​


[*]​非响应式 (阻塞式):​​ 核心 API 基于线程池和阻塞调用,在响应式编程场景下兼容性较差。
[*]​依赖较重:​​ 包含大量 Netflix 的内部组件 (如 Archaius 配置系统),包体积和复杂度较高。
[*]​独立的负载均衡器:​​ 需要额外的客户端负载均衡器实现 (如 RibbonLoadBalancerClient)。
springcloud 与 ribbon 整合

此小节旨在解释 spring cloud 项目中,如何与 ribbon 集成。ribbon 也可完全独立于 spring 项目,独立运行。
Ribbon 与 RestTemplate 整合使用


[*]在 Spring Cloud 构建的微服务系统中,Ribbon 作为服务消费者的负载均衡器,有2种使用方式:


[*]一种是和 RestTemplate 相结合;
[*]另一种是和 Feign 相结合。


[*]那么,Spring Cloud框架中,Ribbon (负载均衡器) 是如何与 Spring 的 RestTemplate / WebClient 集成的?
下面用一张图来看看 RestTemplate 基于 Ribbon 的远程调用:

from : https://www.cnblogs.com/chiangchou/p/ribbon-1.html


[*]RestTemplate 本身是不具备【负载均衡】的能力的。
1 RestTemplate 是 Spring Resources 中一个访问第三方 RESTful API 接口的网络请求框架,用于执行HTTP请求。
2 其暴露了一系列的模板方法API,便于操作底层的HTTP客户端库,如JDK的HttpURLConnection、Apache HttpComponents等。
3 RestTemplate 是用来消费 REST 服务的,所以 RestTemplate 的主要方法都与 REST 的 Http协议的一些方法紧密相连,例如 HEAD、GET、POST、PUT、DELETE 和 OPTIONS 等方法。
这些方法在 RestTemplate 类对应的方法为 headForHeaders()、getForObject()、postForObject()、put() 和 delete() 等。

4 RestTemplate通常作为【共享组件】使用,其配置不支持【并发修改】,因此通常在【启动时】准备好配置。
如果需要,可以在启动时创建多个配置不同的RestTemplate实例。
这些实例可以使用相同的底层`ClientHttpRequestFactory`,如果它们需要共享HTTP客户端资源。

[*]如果 RestTemplate 未使用 @LoadBalanced 标记,就通过服务名的形式来调用,必然会报错。
[*]用 @LoadBalanced 标记后,调用 RestTemplate 的 REST 方法就会通过【负载均衡】的方式通过一定的负载策略【路由】到某个【服务实例】上。
此时,其底层负责负载均衡的组件就是 Ribbon。
springcloud 、注册中心 eureka 、负载均衡器 ribbon 三者的整合


[*]与 eureka 整合到 springcloud 类似,springcloud 提供了对应的 spring-cloud-starter-netflix-eureka-client(server) 依赖包
[*]ribbon 则整合到了 spring-cloud-starter-netflix-ribbon 中。
一般也不需要单独引入 ribbon 的依赖包,spring-cloud-starter-netflix-eureka-client 中已经依赖了 spring-cloud-starter-netflix-ribbon。
因此,我们引入了 spring-cloud-starter-netflix-eureka-client 就可以使用 Ribbon 的功能了。

springcloud 、注册中心 nacos 、负载均衡器 ribbon 三者的整合

略,类同 eureka 。
客户端式负载均衡框架的同类竞品项目


[*]​Spring Cloud LoadBalancer (SCL)


[*]Spring 官方在 ​Spring Cloud Hoxton (2020年)​​ 推出,旨在替代 Ribbon。
[*]是 ​Spring Cloud Commons​ 项目的一部分,与 Spring 生态集成度更高。
[*]目前作为 ​Spring Cloud 官方推荐的负载均衡解决方案,持续更新迭代。
spring项目中,若同时引了 spring-cloud-starter-netflix-ribbon 与 spring-cloud-loadbalancer 会冲突,用 spring.cloud.loadbalancer.ribbon.enabled=false 可回退到 Ribbon。


[*]技术架构与依赖​


[*]​响应式优先:​​ 核心接口 ReactiveLoadBalancer 基于 ​Project Reactor​(Reactor Core),天然支持响应式编程,同时对阻塞式调用提供适配。
[*]​轻量级:​​ 源码简洁,依赖少 (spring-cloud-starter-loadbalancer),启动更快。
[*]​Spring原生集成:​​ 与 Spring 框架深度集成(如 Environment、BeanFactory),配置管理更简单。(既是优点,也是缺点)
Maven依赖

<dependency>
<groupId>com.netflix.ribbon</groupId>
ribbon</artifactId>

<version>${ribbon.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
ribbon-core</artifactId>
<version>${ribbon.version}</version>
</dependency>
<dependency>
<groupId>com.netflix.ribbon</groupId>
ribbon-loadbalancer</artifactId>
<version>${ribbon.version}</version>
</dependency>2 Ribbon LoadBalancer 核心 API


[*]IClientConfig:Ribbon 客户端配置类,默认实现是 DefaultClientConfigImpl。
[*]IRule:负载均衡策略规则组件,默认实现是 ZoneAvoidanceRule。
[*]IPing:判断 Server 是否存活,默认实现是 DummyPing,永远都是返回 true。
[*]ServerList:获取 Server 的组件,默认实现类为 ConfigurationBasedServerList,从配置文件获取。
[*]ServerListUpdater:Server 列表更新组件,默认实现类为 PollingServerListUpdater。
[*]ServerListFilter:过滤可用的 Server 列表,默认实现类为 ZonePreferenceServerListFilter。
[*]RibbonLoadBalancerContext:负载均衡客户端。
[*]RetryHandler:重试处理器,默认实现类为 DefaultLoadBalancerRetryHandler。
IClientConfig : 客户端配置


[*]com.netflix.client.config.IClientConfig : 管理客户端配置的核心接口,它的默认实现类是 DefaultClientConfigImpl。
可以看到在创建 IClientConfig 时,设置了 Ribbon 客户端默认的连接和读取超时时间为 1 秒,例如读取如果超过1秒,就会返回超时,这两个一般需要根据实际情况来调整。
import com.netflix.client.config.IClientConfig;
import com.netflix.client.config.CommonClientConfigKey;

@Bean
@ConditionalOnMissingBean
public IClientConfig ribbonClientConfig() {
    DefaultClientConfigImpl config = new DefaultClientConfigImpl();
    // 加载配置
    config.loadProperties(this.name);
    // 连接超时默认 1 秒
    config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
    // 读取超时默认 1 秒
    config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
    config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
    return config;
}

[*]com.netflix.client.config.CommonClientConfigKey
这个类定义了 Ribbon 客户端相关的所有配置的键常量,可以通过这个类来看有哪些配置。
https://github.com/Netflix/ribbon/blob/v2.7.18/ribbon-core/src/main/java/com/netflix/client/config/CommonClientConfigKey.java


[*]进入到 DefaultClientConfigImpl,可以看到 CommonClientConfigKey 中的每个配置都对应了一个默认值。
在加载配置的时候,如果用户没有定制配置,就会使用默认的配置。
https://github.com/Netflix/ribbon/blob/v2.7.18/ribbon-archaius/src/main/java/com/netflix/client/config/DefaultClientConfigImpl.java
https://github.com/Netflix/ribbon/blob/v2.7.18/ribbon-core/src/test/java/com/netflix/client/config/DefaultClientConfigImplTest.java
也可以在配置文件中定制配置,例如配置超时和重试:
# 全局配置
ribbon:
# 客户端读取超时时间
ReadTimeout: 3000
# 客户端连接超时时间
ConnectTimeout: 3000
# 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
OkToRetryOnAllOperations: false
# 重试次数
MaxAutoRetries: 1
# 最多重试几个实例
MaxAutoRetriesNextServer: 1

# 只针对 demo-producer 客户端
demo-producer:
ribbon:
    # 客户端读取超时时间
    ReadTimeout: 5000
    # 客户端连接超时时间
    ConnectTimeout: 3000IRule : 均衡策略


[*]IRule 是最终选择 Server 的策略规则类,核心的接口就是 choose。
public interface IRule{
    // 选择 Server
    public Server choose(Object key);

    // 设置 ILoadBalancer
    public void setLoadBalancer(ILoadBalancer lb);

    // 获取 ILoadBalancer
    public ILoadBalancer getLoadBalancer();
}

[*]Ribbon 提供了丰富的负载均衡策略,我们也可以通过配置指定使用某个均衡策略。下面是整个Ribbon提供的 IRule 均衡策略。

策略类命名描述RandomRule随机策略随机选择 serverRoundRobinRule轮询策略按顺序循环选择 serverRetryRule重试策略在一个配置时间段内当选择 server 不成功,则一直尝试选择一个可用的 serverBestAvailableRule最低并发策略逐个考察 server,如果 server 断路器打开,则忽略,再选择其中并发连接最低的 serverAvailabilityFilteringRule可用过滤策略过滤掉一直连接失败并被标记为 circuit tripped 的 server,过滤掉那些高并发连接的 server(active connections 超过配置的阈值)ResponseTimeWeightedRule响应时间加权策略根据 server 的响应时间分配权重。响应时间越长,权重越低,被选择到的概率就越低;响应时间越短,权重越高,被选择到的概率就越高。这个策略很贴切,综合了各种因素,如:网络、磁盘、IO 等,这些因素直接影响着响应时间ZoneAvoidanceRule区域权衡策略综合判断 server 所在区域的性能和 server 的可用性轮询选择 server,并且判定一个 AWS Zone 的运行性能是否可用,剔除不可用的 Zone 中的所有 server例如: RandomRule => com.netflix.loadbalancer.RandomRule
IPing : 服务检查


[*]com.netflix.loadbalancer.IPing :
用于定期检查 Server 的可用性的,它只提供了一个接口,用来判断 Server 是否存活:
package com.netflix.loadbalancer;

public interface IPing {
    boolean isAlive(Server var1);
}

[*]IPing 也提供了多种策略可选。


下面是整个 IPing 体系结构:

ServerList : 获取服务列表


[*]ServerList 提供了2个接口: 一个是第一次获取 Server 列表,一个是更新 Server 列表
其中 getUpdatedListOfServers 会每被 Loadbalancer 隔 30 秒调一次来更新 allServerList。
public interface ServerList<T extends Server> {

    public List<T> getInitialListOfServers();

    /**
   * Return updated list of servers. This is called say every 30 secs
   * (configurable) by the Loadbalancer's Ping cycle
   */
    public List<T> getUpdatedListOfServers();
}

[*]ServerList 也提供了多种实现
ServerList 体系结构如下:


ServerListFilter : 过滤服务


[*]ServerListFilter 提供了一个接口用来过滤出可用的 Server。
public interface ServerListFilter<T extends Server> {
    public List<T> getFilteredListOfServers(List<T> servers);
}
ServerListUpdater :服务列表更新


[*]ServerListUpdater 有多个接口,最核心的就是 start 开启定时任务调用 updateAction 来更新 allServerList。
public interface ServerListUpdater {

    /**
   * an interface for the updateAction that actually executes a server list update
   */
    public interface UpdateAction {
      void doUpdate();
    }

    /**
   * start the serverList updater with the given update action
   * This call should be idempotent.
   */
    void start(UpdateAction updateAction);
}

[*]默认有两个实现类:

ILoadBalancer : 负载均衡器


[*]ILoadBalancer 是负载均衡选择服务的核心接口,主要提供了如下的获取Server列表和根据客户端名称选择Server的接口。
public interface ILoadBalancer {
    // 添加Server
    public void addServers(List<Server> newServers);

    // 根据key选择一个Server
    public Server chooseServer(Object key);

    // 获取存活的Server列表,返回 upServerList
    public List<Server> getReachableServers();

    // 获取所有Server列表,返回 allServerList
    public List<Server> getAllServers();
}
Z 案例实践

CASE 实现客户端式负载均衡器(快速入门版)

package com.knowdata.framework.study.ribbon.lb;

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.List;

import com.netflix.loadbalancer.BaseLoadBalancer;
import com.netflix.loadbalancer.RoundRobinRule;
import com.netflix.loadbalancer.Server;

/*
* @description Ribbon 负载均衡框架的快速入门示例
* @updateTime 2025/09/14 16:39
*/
public class RibbonQuickStartTest {
    public static void main(String[] args) throws Exception {
      // 定义目标服务器列表
      List<Server> serverList = Arrays.asList(
            new Server("localhost", 8086),
            new Server("localhost", 8086),
            new Server("localhost", 8086)
      );

      // 创建(客户端式)负载均衡器
      BaseLoadBalancer loadBalancer = new BaseLoadBalancer();
      loadBalancer.setServersList(serverList);

      // 配置负载均衡策略(可选,默认为轮询)
      loadBalancer.setRule(new RoundRobinRule());
      // 其他策略示例:
      // loadBalancer.setRule(new RandomRule());
      // loadBalancer.setRule(new WeightedResponseTimeRule());

      // 模拟多次请求,查看负载均衡效果
      for (int i = 0; i < 10; i++) {
            Server server = loadBalancer.chooseServer(null);
            System.out.println("第 " + (i + 1) + " 次请求,选中服务器: " + server.getHostPort());
            sendRequest(server);
      }
    }

    private static void sendRequest(Server server) {
      try {
            URL url = new URL("http://" + server.getHost() + ":" + server.getPort() + "/api/hello");
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();
            connection.setRequestMethod("GET");
            int responseCode = connection.getResponseCode();
            System.out.println("响应码: " + responseCode);
            connection.disconnect();
      } catch (IOException e) {
            System.err.println("请求失败: " + e.getMessage());
      }
    }
}Y 推荐文献


[*]Ribbon


[*]https://github.com/Netflix/ribbon
[*]https://mvnrepository.com/artifact/com.netflix.ribbon/ribbon


[*] RestTemplate : Spring的HTTP网络请求框架 - 博客园/千千寰宇
X 参考文献


[*]Ribbon和LoadBalance-负载均衡 - 技术栈
[*]如何使用原生的Ribbon - Zhihu 【推荐】
[*]SpringCloud 源码系列(4)—— 负载均衡 Ribbon(上)- 博客园/bojiangzhou 【推荐】
[*]SpringCloud 源码系列(5)—— 负载均衡 Ribbon(下)- 博客园/bojiangzhou 【推荐】
    本文作者:      千千寰宇   
    本文链接:         https://www.cnblogs.com/johnnyzen   
    关于博文:评论和私信会在第一时间回复,或直接私信我。   
    版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA   许可协议。转载请注明出处!
    日常交流:大数据与软件开发-QQ交流群: 774386015      【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!   

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: [高可用/负载均衡] Ribbon LoadBalancer: 开源的客户端式负载均衡框架