找回密码
 立即注册
首页 业界区 业界 [高可用/负载均衡] Ribbon LoadBalancer: 开源的客户端 ...

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

章娅萝 昨天 23:27
0 序言


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


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


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

负载均衡的概念


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


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


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


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


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


  • ​服务发现客户端​,从注册中心获取服务实例列表并缓存。
  • 客户端请求被 ​负载均衡拦截器​ 截获(如 @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,为负载均衡器提供动态服务注册列表信息。
4.png


  • 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 的远程调用:
5.png

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


  • RestTemplate 本身是不具备【负载均衡】的能力的。
  1. 1 RestTemplate 是 Spring Resources 中一个访问第三方 RESTful API 接口的网络请求框架,用于执行HTTP请求。
  2. 2 其暴露了一系列的模板方法API,便于操作底层的HTTP客户端库,如JDK的HttpURLConnection、Apache HttpComponents等。
  3. 3 RestTemplate 是用来消费 REST 服务的,所以 RestTemplate 的主要方法都与 REST 的 Http协议的一些方法紧密相连,例如 HEAD、GET、POST、PUT、DELETE 和 OPTIONS 等方法。
  4. 这些方法在 RestTemplate 类对应的方法为 headForHeaders()、getForObject()、postForObject()、put() 和 delete() 等。
  5. 4 RestTemplate通常作为【共享组件】使用,其配置不支持【并发修改】,因此通常在【启动时】准备好配置。
  6.   如果需要,可以在启动时创建多个配置不同的RestTemplate实例。
  7.   这些实例可以使用相同的底层`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 的功能了。
6.png

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依赖
  1. <dependency>
  2.   <groupId>com.netflix.ribbon</groupId>
  3.   ribbon</artifactId>
  4.   
  5.   <version>${ribbon.version}</version>
  6. </dependency>
  7. <dependency>
  8.   <groupId>com.netflix.ribbon</groupId>
  9.   ribbon-core</artifactId>
  10.   <version>${ribbon.version}</version>
  11. </dependency>
  12. <dependency>
  13.   <groupId>com.netflix.ribbon</groupId>
  14.   ribbon-loadbalancer</artifactId>
  15.   <version>${ribbon.version}</version>
  16. </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秒,就会返回超时,这两个一般需要根据实际情况来调整。
  1. import com.netflix.client.config.IClientConfig;
  2. import com.netflix.client.config.CommonClientConfigKey;
  3. @Bean
  4. @ConditionalOnMissingBean
  5. public IClientConfig ribbonClientConfig() {
  6.     DefaultClientConfigImpl config = new DefaultClientConfigImpl();
  7.     // 加载配置
  8.     config.loadProperties(this.name);
  9.     // 连接超时默认 1 秒
  10.     config.set(CommonClientConfigKey.ConnectTimeout, DEFAULT_CONNECT_TIMEOUT);
  11.     // 读取超时默认 1 秒
  12.     config.set(CommonClientConfigKey.ReadTimeout, DEFAULT_READ_TIMEOUT);
  13.     config.set(CommonClientConfigKey.GZipPayload, DEFAULT_GZIP_PAYLOAD);
  14.     return config;
  15. }
复制代码

  • 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
也可以在配置文件中定制配置,例如配置超时和重试:
  1. # 全局配置
  2. ribbon:
  3.   # 客户端读取超时时间
  4.   ReadTimeout: 3000
  5.   # 客户端连接超时时间
  6.   ConnectTimeout: 3000
  7.   # 默认只重试 GET,设置为 true 时将重试所有类型,如 POST、PUT、DELETE
  8.   OkToRetryOnAllOperations: false
  9.   # 重试次数
  10.   MaxAutoRetries: 1
  11.   # 最多重试几个实例
  12.   MaxAutoRetriesNextServer: 1
  13. # 只针对 demo-producer 客户端
  14. demo-producer:
  15.   ribbon:
  16.     # 客户端读取超时时间
  17.     ReadTimeout: 5000
  18.     # 客户端连接超时时间
  19.     ConnectTimeout: 3000
复制代码
IRule : 均衡策略


  • IRule 是最终选择 Server 的策略规则类,核心的接口就是 choose。
  1. public interface IRule{
  2.     // 选择 Server
  3.     public Server choose(Object key);
  4.     // 设置 ILoadBalancer
  5.     public void setLoadBalancer(ILoadBalancer lb);
  6.     // 获取 ILoadBalancer
  7.     public ILoadBalancer getLoadBalancer();
  8. }
复制代码

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

策略类命名描述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 是否存活:
  1. package com.netflix.loadbalancer;
  2. public interface IPing {
  3.     boolean isAlive(Server var1);
  4. }
复制代码

  • IPing 也提供了多种策略可选。
    8.png

9.png

下面是整个 IPing 体系结构:
10.png

ServerList : 获取服务列表


  • ServerList 提供了2个接口: 一个是第一次获取 Server 列表,一个是更新 Server 列表
其中 getUpdatedListOfServers 会每被 Loadbalancer 隔 30 秒调一次来更新 allServerList。
  1. public interface ServerList<T extends Server> {
  2.     public List<T> getInitialListOfServers();
  3.     /**
  4.      * Return updated list of servers. This is called say every 30 secs
  5.      * (configurable) by the Loadbalancer's Ping cycle
  6.      */
  7.     public List<T> getUpdatedListOfServers();
  8. }
复制代码

  • ServerList 也提供了多种实现
ServerList 体系结构如下:
11.png

12.png

ServerListFilter : 过滤服务


  • ServerListFilter 提供了一个接口用来过滤出可用的 Server。
  1. public interface ServerListFilter<T extends Server> {
  2.     public List<T> getFilteredListOfServers(List<T> servers);
  3. }
复制代码
13.png

ServerListUpdater :服务列表更新


  • ServerListUpdater 有多个接口,最核心的就是 start 开启定时任务调用 updateAction 来更新 allServerList。
  1. public interface ServerListUpdater {
  2.     /**
  3.      * an interface for the updateAction that actually executes a server list update
  4.      */
  5.     public interface UpdateAction {
  6.         void doUpdate();
  7.     }
  8.     /**
  9.      * start the serverList updater with the given update action
  10.      * This call should be idempotent.
  11.      */
  12.     void start(UpdateAction updateAction);
  13. }
复制代码

  • 默认有两个实现类:
14.png

ILoadBalancer : 负载均衡器


  • ILoadBalancer 是负载均衡选择服务的核心接口,主要提供了如下的获取Server列表和根据客户端名称选择Server的接口。
  1. public interface ILoadBalancer {
  2.     // 添加Server
  3.     public void addServers(List<Server> newServers);
  4.     // 根据key选择一个Server
  5.     public Server chooseServer(Object key);
  6.     // 获取存活的Server列表,返回 upServerList
  7.     public List<Server> getReachableServers();
  8.     // 获取所有Server列表,返回 allServerList
  9.     public List<Server> getAllServers();
  10. }
复制代码
15.png

Z 案例实践

CASE 实现客户端式负载均衡器(快速入门版)
  1. package com.knowdata.framework.study.ribbon.lb;
  2. import java.io.IOException;
  3. import java.net.HttpURLConnection;
  4. import java.net.URL;
  5. import java.util.Arrays;
  6. import java.util.List;
  7. import com.netflix.loadbalancer.BaseLoadBalancer;
  8. import com.netflix.loadbalancer.RoundRobinRule;
  9. import com.netflix.loadbalancer.Server;
  10. /*
  11. * @description Ribbon 负载均衡框架的快速入门示例
  12. * @updateTime 2025/09/14 16:39
  13. */
  14. public class RibbonQuickStartTest {
  15.     public static void main(String[] args) throws Exception {
  16.         // 定义目标服务器列表
  17.         List<Server> serverList = Arrays.asList(
  18.             new Server("localhost", 8086),
  19.             new Server("localhost", 8086),
  20.             new Server("localhost", 8086)
  21.         );
  22.         // 创建(客户端式)负载均衡器
  23.         BaseLoadBalancer loadBalancer = new BaseLoadBalancer();
  24.         loadBalancer.setServersList(serverList);
  25.         // 配置负载均衡策略(可选,默认为轮询)
  26.         loadBalancer.setRule(new RoundRobinRule());
  27.         // 其他策略示例:
  28.         // loadBalancer.setRule(new RandomRule());
  29.         // loadBalancer.setRule(new WeightedResponseTimeRule());
  30.         // 模拟多次请求,查看负载均衡效果
  31.         for (int i = 0; i < 10; i++) {
  32.             Server server = loadBalancer.chooseServer(null);
  33.             System.out.println("第 " + (i + 1) + " 次请求,选中服务器: " + server.getHostPort());
  34.             sendRequest(server);
  35.         }
  36.     }
  37.     private static void sendRequest(Server server) {
  38.         try {
  39.             URL url = new URL("http://" + server.getHost() + ":" + server.getPort() + "/api/hello");
  40.             HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  41.             connection.setRequestMethod("GET");
  42.             int responseCode = connection.getResponseCode();
  43.             System.out.println("响应码: " + responseCode);
  44.             connection.disconnect();
  45.         } catch (IOException e) {
  46.             System.err.println("请求失败: " + e.getMessage());
  47.         }
  48.     }
  49. }
复制代码
Y 推荐文献


  • Ribbon


  • https://github.com/Netflix/ribbon
  • https://mvnrepository.com/artifact/com.netflix.ribbon/ribbon


  • [HTTP/Spring] 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        【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!   

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册