目录
一、概念
1.1 概念
1.2 核心价值
1.3 能力边界
1.4 应用场景
1.5 现状与未来
二、工作原理与架构
2.1 工作原理
2.2 核心架构与核心组件
2.2.1 ServerList(服务列表)
2.2.2 ServerListFilter(服务列表过滤器)
2.2.3 IRule(负载均衡规则)- 最核心的组件
2.2.4 IPing(健康检查)
2.2.5 ILoadBalancer(负载均衡器接口)- 组件的容器
2.2.6 ServerListUpdater(服务列表更新器)
三、使用
3.1 与 Eureka 搭配为例
3.1.1 添加依赖
3.1.2 配置文件
3.1.3 启用服务发现与负载均衡
3.1.4 使用负载均衡的 RestTemplate 以服务名进行服务调用
3.1.5 配置负载均衡规则
3.2 核心配置
一、概念
1.1 概念
Ribbon 是一个由 ***flix 开源的、基于 HTTP 和 TCP 的客户端负载均衡器。
-
客户端 (Client-side): 负载均衡的逻辑和执行不是在一個集中的、独立的负载均衡服务器(如 Nginx, F5)上完成的,而是内嵌在每一个服务消费者的进程内。每个服务消费者自己都持有一份服务提供者的地址列表,并自己决定向哪里发送请求。
-
负载均衡 (Load Balancing): 它提供了多种规则(如轮询、随机、根据响应时间加权等)来分配请求,以达到将负载分散到多个服务实例的目的。
核心功能是:当你的服务消费者(Client)需要调用另一个服务(例如 User-Service)时,Ribbon 会帮助你从服务实例列表中(例如有3台服务器都提供了 User-Service)选择一个最合适的实例来进行请求,而不是你自己去写代码决定调用哪一台。
技术定位: 在 Spring Cloud 生态中,Ribbon 通常与服务发现组件(如 Eureka)和声明式 REST 客户端(如 OpenFeign)协同工作,为它们提供负载均衡能力。
1.2 核心价值
-
提升系统可用性与伸缩性:
-
通过将请求分散到多个服务实例,避免了单点故障。即使某个实例宕机,Ribbon 也会自动跳过它,将请求发往健康的实例。
-
方便地进行水平扩容。只需启动新的服务实例并注册到服务发现中心,Ribbon 会自动将其纳入负载均衡的范围,无需修改消费者代码或配置中心负载均衡器。
-
-
减少网络跳数,降低延迟:
-
与传统的中介式负载均衡(如 Nginx)相比,客户端负载均衡少了一次网络跳转。请求直接从消费者发往选定的提供者实例,路径更短,理论上延迟更低。
-
-
提供灵活的负载均衡策略:
-
Ribbon 内置了丰富的规则(Rule),如轮询(RoundRobin)、随机(Random)、重试(Retry)、根据响应时间加权(WeightedResponseTime)等。
-
开发者可以非常方便地扩展自定义的负载策略,以满足特定业务场景的需求(例如,优先调用同机房的服务)。
-
-
与开发框架无缝集成:
-
与 Spring Cloud 生态深度整合,通过简单的注解(如
@LoadBalanced)或配合 OpenFeign 即可使用,对业务代码侵入性极低,开发体验非常好。
-
1.3 能力边界
-
它是一个库,不是一个独立的中介服务:
-
这意味着它的负载均衡能力分散在各个服务消费者中。如果需要全局性的、统一的流量控制策略(如全局限流、全链路压测流量染色),Ribbon 本身难以做到,需要配合 API 网关或服务网格(如 Istio)来实现。
-
-
对“服务状态”的感知是最终一致性的:
-
Ribbon 通常从服务注册中心(如 Eureka)获取服务列表。当服务实例上线或下线时,消费者端的服务列表更新会有一定的延迟(取决于 Eureka 的心跳和缓存刷新机制)。在此期间,Ribbon 可能会将请求发往已经下线的实例,因此必须配合重试机制和断路器(如 Hystrix 或 Resilience4j)来提升鲁棒性。
-
Ribbon 可以集成断路器(如 Hystrix,虽然现在已进入维护模式),当某个服务实例调用失败多次后,可以将其标记为“短路”状态,暂时从选择列表中排除,避免重复向故障实例发送请求。
-
-
负载均衡视角是局部的:
-
每个 Ribbon 客户端只从自己的视角出发来做负载均衡决策。它无法感知整个集群的全局负载状态。例如,它很难实现“最少连接数”这种需要全局视野的策略。
-
-
语言和技术栈绑定:
-
作为 Java 客户端库,它主要服务于 JVM 生态的应用。对于非 JVM 语言(如 Go, Python, Node.js)的服务,无法直接使用 Ribbon。
-
-
社区状态与演进:
-
重要提示: ***flix Ribbon 已经进入维护模式,不再添加新功能。Spring Cloud 官方也从 Spring Cloud 2020.0.0 版本开始,移除了 Ribbon 的支持,推荐使用 Spring Cloud LoadBalancer 作为其替代品。Spring Cloud LoadBalancer 提供了类似的客户端负载均衡能力,并旨在成为新一代的默认选择。但目前讨论 Ribbon 的概念和价值依然具有现实意义,因为其设计思想被广泛继承。
-
1.4 应用场景
-
微服务间的内部通信:
-
这是最经典的应用场景。在基于 Spring Cloud ***flix 或 Alibaba 的微服务架构中,服务 A 通过 RestTemplate 或 OpenFeign 调用服务 B 时,默认就通过 Ribbon 来实现负载均衡。
-
示例:
@Autowired @LoadBalanced private RestTemplate restTemplate; ... restTemplate.getForObject("http://user-service/users/1", User.class);这里的user-service会被 Ribbon 解析为具体的实例地址。
-
-
需要自定义路由策略的场景:
-
当负载均衡策略有特殊要求时,Ribbon 的灵活性得以体现。
-
示例: 实现一个“版本号路由”规则,将请求优先转发到与消费者版本号匹配的服务提供者实例上;或者实现“灰度发布”逻辑,将少量流量导入到新版本实例。
-
-
对延迟非常敏感的内部服务调用:
-
由于客户端负载均衡省去了一次网络跳转,对于微服务集群内部大量、高频的 RPC 调用,使用 Ribbon 有助于降低整体延迟。
-
-
与 API 网关协同工作:
-
即使在引入了 Spring Cloud Gateway 或 Zuul 等 API 网关后,网关本身作为一个客户端,在调用下游微服务集群时,同样需要使用 Ribbon(或 LoadBalancer)来做负载均衡。
-
1.5 现状与未来
-
维护模式:***flix 已经将 Ribbon 置于维护模式,意味着不再会增加新功能,只会修复关键bug。
-
替代方案:Spring Cloud 官方推荐迁移到 Spring Cloud LoadBalancer。它是 Spring Cloud 自己开发的客户端负载均衡器,旨在取代 Ribbon。它提供了更简洁的 API 和对响应式编程(WebFlux)的更好支持。
-
现状:尽管如此,由于 Ribbon 非常稳定且已有大量存量项目在使用,它在未来一段时间内仍会广泛存在。
二、工作原理与架构
2.1 工作原理
以一个典型的基于 Spring Cloud 和 Eureka 的微服务调用为例:
-
服务注册:所有
user-service的实例启动后,都向 Eureka 服务器注册自己。 -
获取列表:
order-service(消费者)在启动时,或者通过定时任务,从 Eureka 服务器拉取user-service的服务实例列表,并缓存在本地。 -
选择目标:当
order-service需要调用user-service的 API 时,它会委托给 Ribbon。 -
执行规则:Ribbon 根据配置的负载均衡规则(如轮询),从本地的服务实例列表中选择一个目标实例(例如选择
192.168.1.101:8080)。 -
发起请求:Ribbon 将实际的 HTTP 请求(通常通过 RestTemplate 或 OpenFeign)发送到上一步选择的目标实例。
关键在于,负载均衡的决策是在服务消费者(客户端)本地完成的,而不是由一个中心化的负载均衡器(如 Nginx)来完成。 这就是“客户端负载均衡”与“服务器端负载均衡”的根本区别。
2.2 核心架构与核心组件
Ribbon 的架构是高度可插拔和可定制化的。每个核心组件都是一个接口,你可以轻松地提供自己的实现来覆盖默认行为。例如:
-
实现一个自定义的
IRule,根据业务逻辑(如用户ID、版本号)来路由。 -
实现一个自定义的
IPing,用你的业务健康检查端点(如/health)来判定服务是否可用。 -
实现一个自定义的
ServerListFilter,实现基于元数据的灰度发布。
2.2.1 ServerList(服务列表)
-
职责: 定义了服务的“候选集”。它负责提供一个
List<Server>,即所有可用的服务实例列表。这里的Server对象通常包含host和port等关键信息。 -
实现:
-
静态配置:
ConfigurationBasedServerList,允许在配置文件中直接写死服务器地址列表(如user-service.ribbon.listOfServers=localhost:8001,localhost:8002)。 -
动态发现: 这是最常用的方式。例如
DiscoveryEnabledNIWSServerList,它会与 Eureka、Consul 等服务注册中心集成,自动查询并获取指定服务名的所有实例列表。这是 Ribbon 实现客户端负载均衡并与微服务生态融合的基石。
-
2.2.2 ServerListFilter(服务列表过滤器)
-
职责: 在
ServerList获取到原始列表后,ServerListFilter可以对其进行过滤或加工,返回一个“定制化”的列表。 -
应用场景: 用于实现更精细的路由控制。
-
ZoneAffinityServerListFilter: 具有区域亲和性,会优先过滤出与调用方处于同一个“区域”(Zone/Availability Zone)的服务实例,这在大规模分布式系统中对于降低延迟和跨区流量成本非常重要。
-
可以实现自定义的过滤器,根据元数据(Metadata)、标签(Tags)等进行过滤。
-
2.2.3 IRule(负载均衡规则)- 最核心的组件
-
职责: 这是决定最终选择哪个服务器的核心算法所在。它从
ServerList(或经过ServerListFilter过滤后)的列表中,根据特定算法选出一个实例。 -
常用内置实现:
-
RoundRobinRule: 轮询。依次循环选择每个服务器。 -
RandomRule: 随机。随机选择一个服务器。 -
WeightedResponseTimeRule: 加权响应时间。根据服务器的平均响应时间计算权重,响应越快,权重越高,被选中的概率越大。用于实现自适应负载均衡。 -
ZoneAvoidanceRule: 区域回避(默认规则)。它综合判断两个因素:1) 选择一个区域(Zone);2) 在该区域内部使用RoundRobinRule选择服务器。它试图避免将流量路由到整个区域性能不佳或故障的服务器群。 -
AvailabilityFilteringRule: 会先过滤掉多次连接失败和并发连接数过高的服务器,然后再对剩余服务器进行轮询。 -
BestAvailableRule: 选择当前请求数最少的服务器。
-
-
用户可以自定义负载均衡规则和插件,以满足特定需求。
2.2.4 IPing(健康检查)
-
职责: 定期在后台检查
ServerList中各个服务器的健康状态(是否存活)。IRule在做出选择时,会依赖IPing的结果来排除掉不健康的实例。 -
实现:
-
NoOpPing: 什么都不做,假定所有服务器永远健康。 -
DummyPing: 总是返回true,同样假定所有服务器健康。 -
NIWSDiscoveryPing: 与 Eureka 集成时常用。它并不真正发送网络 ping 命令,而是依赖 Eureka 客户端提供的服务实例状态(UP,DOWN)。如果实例在 Eureka 上状态为UP,则认为是健康的。这种方式更高效,因为健康检查由 Eureka Server 和 Client 的心跳机制保证。
-
2.2.5 ILoadBalancer(负载均衡器接口)- 组件的容器
-
职责: 这是以上所有组件的协调者和容器。可以将它视为 Ribbon 负载均衡功能的入口点和总控制器。
-
核心方法:
-
addServers(List<Server> newServers): 添加服务器列表。 -
chooseServer(Object key): 关键方法,根据某种规则(IRule)选择一个服务器。这个key可用于传递一些用于选择的 hint 信息。 -
markServerDown(Server server): 标记某个服务器下线。 -
getReachableServers(): 获取所有健康(可达)的服务器。 -
getAllServers(): 获取所有已知的服务器。
-
-
常用实现:
ZoneAwareLoadBalancer,它提供了对多区域(Zone)支持的高级功能,是微服务架构中的默认选择。
2.2.6 ServerListUpdater(服务列表更新器)
-
职责: 控制如何以及何时从原始源(如 Eureka)更新本地的
ServerList缓存。它负责驱动动态服务发现的核心循环。 -
实现:
PollingServerListUpdater是默认实现,它会启动一个定时任务,定期(例如每30秒)从服务注册中心拉取最新的服务列表。
三、使用
3.1 与 Eureka 搭配为例
3.1.1 添加依赖
通常,如果已经引入了 spring-cloud-starter-***flix-eureka-client,它默认会包含 Ribbon 的依赖,无需单独引入 Ribbon 的 starter。
<!-- Eureka Client 依赖 (通常已包含 ribbon) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-***flix-eureka-client</artifactId>
</dependency>
<!-- 执行器监控 (可选,用于健康检查等信息) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
3.1.2 配置文件
在服务消费者的配置文件 (application.yml 或 application.properties)中,指定 Eureka Server 的地址和应用信息。
spring:
application:
name: your-consumer-service-name # 你的消费者服务名称
server:
port: 8080 # 消费者服务端口
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka # Eureka Server地址
register-with-eureka: true # 是否注册到Eureka (根据需求,消费者不一定需要注册)
fetch-registry: true # 是否获取注册表
instance:
prefer-ip-address: true # 使用IP地址注册
3.1.3 启用服务发现与负载均衡
在服务消费者的主启动类上,添加 @EnableDiscoveryClient 或 @EnableEurekaClient 注解以启用服务发现功能。使用 @LoadBalanced 注解修饰 RestTemplate Bean,使其具备 Ribbon 的负载均衡能力。
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.web.client.RestTemplate;
@SpringBootApplication
@EnableDiscoveryClient // 或 @EnableEurekaClient
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
}
@Bean
@LoadBalanced // 关键注解,使RestTemplate具备负载均衡能力
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3.1.4 使用负载均衡的 RestTemplate 以服务名进行服务调用
在需要调用其他服务的地方(如 Controller 或 Service),注入配置好的 RestTemplate,并使用服务名(而不再是具体的 IP 和端口)作为 URL 的 host 部分进行调用。Ribbon 会自动拦截请求,进行服务发现和负载均衡。
import org.springframework.web.client.RestTemplate;
@RestController
@RequestMapping("/consumer")
public class ConsumerController {
@Autowired
private RestTemplate restTemplate; // 注入负载均衡的RestTemplate
// 使用服务提供方的 application.name 替代具体地址
private static final String PROVIDER_SERVICE_URL = "http://your-provider-service-name";
@GetMapping("/getOrderWithUser")
public User getOrderWithUser(Long usetId) {
// 使用服务名(USER-SERVICE)代替硬编码的IP和端口,进行调用
// URL格式: http://<service-name>/<endpoint>
String userServiceUrl = "http://user-service/users/" + usetId;
User user = restTemplate.getForObject(userServiceUrl, User.class);
return user;
}
}
3.1.5 配置负载均衡规则
Ribbon 默认使用轮询(RoundRobin)规则。可以通过配置或代码为特定服务或全局更改负载均衡策略。
通过配置文件定制 (常用): 在服务消费者的 application.yml 中,为指定的服务提供者配置规则
# 修改针对某个服务提供者的负载均衡规则
your-provider-service-name: # 替换为你的服务提供方应用名称
ribbon:
NFLoadBalancerRuleClassName: ***.***flix.loadbalancer.RandomRule # 随机规则
# NFLoadBalancerRuleClassName: ***.***flix.loadbalancer.RoundRobinRule # 轮询(默认)
# NFLoadBalancerRuleClassName: ***.***flix.loadbalancer.WeightedResponseTimeRule # 权重
# NFLoadBalancerRuleClassName: ***.***flix.loadbalancer.RetryRule # 重试
# NFLoadBalancerRuleClassName: ***.***flix.loadbalancer.ZoneAvoidanceRule # 区域回避(默认)
通过 Java 代码定制: 可以创建一个配置类来定义 IRule Bean。注意:此类不应在主应用上下文组件的扫描范围内,否则会成为全局默认配置,影响所有 Ribbon 客户端。通常使用 @RibbonClient 注解指定配置类。
@Configuration
public class MyRibbonConfiguration {
@Bean
public IRule ribbonRule() {
return new RandomRule(); // 随机规则
}
}
然后在主启动类上使用 @RibbonClient 注解指定要配置的服务名和配置类:
@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient(name = "your-provider-service-name", configuration = MyRibbonConfiguration.class)
public class ConsumerApplication {
// ...
}
同时,确保自定义配置类 MyRibbonConfiguration 不在主启动类的组件扫描范围内,或者在配置类上使用自定义注解并通过 @***ponentScan 排除。
3.2 核心配置
| 配置项 | 默认值 | 说明 |
|---|---|---|
| ribbon.eureka.enabled | true | 是否启用 Eureka 服务发现 |
| <clientName>.ribbon.listOfServers | - | 禁用 Eureka 后,手动指定服务地址列表 |
| ribbon.ConnectTimeout | 1000 (1秒) | 建立连接的超时时间(毫秒) |
| ribbon.ReadTimeout | 1000 (1秒) | 请求处理的超时时间(毫秒) |
| ribbon.MaxAutoRetries | 0 | 对当前实例的重试次数(不含首次调用) |
| ribbon.MaxAutoRetriesNextServer | 1 | 切换下一个实例的重试次数(不含首次调用) |
| ribbon.OkToRetryOnAllOperations | false | 是否对所有操作(如 POST)进行重试,默认只对 GET 重试 |
| ribbon.retryableStatusCodes | - | 针对哪些 HTTP 状态码进行重试 |
| ribbon.MaxTotalConnections | 200 | 所有主机的最大连接数 |
| ribbon.MaxConnectionsPerHost | 50 | 每个主机的最大连接数 |
| <clientName>.ribbon.NFLoadBalancerRuleClassName | ZoneAvoidanceRule | 指定负载均衡规则 |
| ribbon.ServerListRefreshInterval | 30000 (30秒) | 从服务器源刷新列表的间隔时间(毫秒) |
| ribbon.eager-load.enabled | false | 是否启用饥饿加载,防止首次请求过慢 |
| ribbon.eager-load.clients | - | 启用饥饿加载时,需要预初始化的客户端名称列表 |
举例:
# 全局默认配置
ribbon:
ConnectTimeout: 1000
ReadTimeout: 3000
OkToRetryOnAllOperations: false
MaxAutoRetries: 1
MaxAutoRetriesNextServer: 1
eager-load:
enabled: true
clients: user-service, product-service # 启动时立即初始化这些客户端的Ribbon组件
# 针对特定服务的配置(覆盖全局配置)
user-service:
ribbon:
NFLoadBalancerRuleClassName: ***.***flix.loadbalancer.RandomRule
ConnectTimeout: 2000
ReadTimeout: 5000
listOfServers: localhost:8201,localhost:8202 # 仅在禁用Eureka后使用
注意:
-
超时与重试的陷阱:配置重试时,务必考虑整个调用链路的超时设置。例如,如果你使用了 Hystrix,还需要注意 Ribbon 的超时时间和 Hystrix 的命令超时时间(
hystrix.***mand.default.execution.isolation.thread.timeoutInMilliseconds)之间的关系,确保 Hystrix 的超时时间大于 Ribbon 的超时时间和重试时间的总和,否则重试机制可能还未生效,Hystrix 就已经超时熔断了。 -
非幂等操作重试:
OkToRetryOnAllOperations=true时会对所有 HTTP 方法(包括 POST, PUT 等)进行重试。对于非幂等操作(如创建订单),这可能引发数据不一致(例如重复创建)。生产环境中对非幂等操作应谨慎开启全操作重试,通常默认的false只对 GET 请求重试是更安全的选择。 -
Ribbon 默认是懒加载(lazy-loading)的,即只有在第一次发起调用时才会初始化客户端的负载均衡器,这会导致首次请求速度较慢。对于生产环境,建议通过
ribbon.eager-load.enabled=true和ribbon.eager-load.clients开启饥饿加载,并在应用启动时预初始化所需服务的Ribbon上下文。