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有很多子模块,官方文档中说明,目前 Netflix 公司主要用于生产环境的 Ribbon 子模块如下:
- ribbon-core:Ribbon 的核心API。
- ribbon-loadbalancer:可以独立使用或与其他模块一起使用的负载均衡器 API。
- ribbon-eureka:Ribbon 结合 注册中心 Eureka 客户端的 API,为负载均衡器提供动态服务注册列表信息。
- 起源于 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: 3000
复制代码 IRule : 均衡策略
- 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 体系结构:
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 体系结构如下:
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 推荐文献
- 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 【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |