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依赖
<!-- 客户端式负载均衡器 https://github.com/Netflix/ribbon | https://mvnrepository.com/artifact/com.netflix.ribbon/ribbon -->
<dependency><groupId>com.netflix.ribbon</groupId><artifactId>ribbon</artifactId><!-- 2.7.18 --><version>${ribbon.version}</version>
</dependency>
<dependency><groupId>com.netflix.ribbon</groupId><artifactId>ribbon-core</artifactId><version>${ribbon.version}</version>
</dependency>
<dependency><groupId>com.netflix.ribbon</groupId><artifactId>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、DELETEOkToRetryOnAllOperations: false# 重试次数MaxAutoRetries: 1# 最多重试几个实例MaxAutoRetriesNextServer: 1# 只针对 demo-producer 客户端
demo-producer:ribbon:# 客户端读取超时时间ReadTimeout: 5000# 客户端连接超时时间ConnectTimeout: 3000
IRule : 均衡策略
IRule
是最终选择 Server 的策略规则类,核心的接口就是choose
。
public interface IRule{// 选择 Serverpublic Server choose(Object key);// 设置 ILoadBalancerpublic void setLoadBalancer(ILoadBalancer lb);// 获取 ILoadBalancerpublic ILoadBalancer getLoadBalancer();
}
Ribbon
提供了丰富的负载均衡策略,我们也可以通过配置指定使用某个均衡策略。下面是整个Ribbon提供的 IRule 均衡策略。
策略类 | 命名 | 描述 |
---|---|---|
RandomRule | 随机策略 | 随机选择 server |
RoundRobinRule | 轮询策略 | 按顺序循环选择 server |
RetryRule | 重试策略 | 在一个配置时间段内当选择 server 不成功,则一直尝试选择一个可用的 server |
BestAvailableRule | 最低并发策略 | 逐个考察 server,如果 server 断路器打开,则忽略,再选择其中并发连接最低的 server |
AvailabilityFilteringRule | 可用过滤策略 | 过滤掉一直连接失败并被标记为 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 {// 添加Serverpublic void addServers(List<Server> newServers);// 根据key选择一个Serverpublic Server chooseServer(Object key);// 获取存活的Server列表,返回 upServerListpublic List<Server> getReachableServers();// 获取所有Server列表,返回 allServerListpublic 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
-
SpringCloud 源码系列(4)—— 负载均衡 Ribbon(上) - 博客园/bojiangzhou 【推荐】
-
SpringCloud 源码系列(5)—— 负载均衡 Ribbon(下) - 博客园/bojiangzhou 【推荐】
-
[HTTP/Spring] RestTemplate : Spring的HTTP网络请求框架 - 博客园/千千寰宇
X 参考文献
- Ribbon和LoadBalance-负载均衡 - 技术栈