Ribbon 负载均衡
# Ribbon 负载均衡
提示
上一节我们学习了[SpringCloud的核心组件Eureka]
,但是它逐渐被Nacos替代了,在此之前我们先了解一下Ribbon负载均衡。
# 1. 关于负载均衡
负载均衡一般分为 服务器端负载均衡 和 客户端负载均衡。
服务器端负载均衡:如 Nginx、F5 这些工具,当请求到达服务器后,由这些负载均衡器根据一定的算法将请求路由到目标服务器进行处理。
客户端负载均衡:如 Netflix 的 Ribbon。服务消费者在客户端维护一个服务器地址列表,客户端在发送请求之前通过负载均衡算法选择一个服务器进行访问,负载均衡算法的执行是在客户端进行的。
Ribbon 是 Netflix 发布的一个客户端负载均衡器,通常与 Eureka 一起使用。Ribbon 会从 Eureka 中读取服务列表信息,并在调用服务提供者时,根据负载均衡策略选择合适的服务实例。
# 2. 实战演示
Ribbon 的默认负载均衡策略是轮询。在没有其他配置的情况下,只需在 RestTemplate 上加上 @LoadBalanced
注解,即可实现轮询策略的负载均衡。
我们进行一个测试,模拟多个请求,使用订单微服务远程调用用户微服务。
# 测试流程图
- 请求发起:我们同时发起四次不同请求,通过订单微服务访问用户微服务。
- 负载均衡结果:四次请求分别调用了两个用户微服务实例,并且按照轮询策略一人两次。
结果展示:
# 更改 Ribbon 策略
我们可以通过自定义策略改变 Ribbon 的负载均衡算法,例如使用随机策略。
代码示例:
在订单服务的启动类中加入如下配置:
@Bean
public IRule randomRule() {
return new RandomRule(); // 随机策略
}
2
3
4
重新启动订单微服务进行测试,观察负载均衡的效果。
测试结果展示:
可以看到,在随机策略下,所有请求都命中了用户微服务的一个实例,体现了随机策略的不确定性。
# 3. 负载均衡原理
我们添加了 @LoadBalanced
注解后,Spring Cloud 自动集成了 Ribbon 进行负载均衡。其底层原理如下:
- 请求转换:尽管我们在请求中输入的是服务名称(如
http://user-service/user/1
),最终会被转换为具体的服务地址(如http://localhost:9001/user/1
)。 - 负载均衡处理:Spring Cloud 利用
LoadBalancerInterceptor
对 RestTemplate 的请求进行拦截,动态从 Eureka 获取服务实例列表,并根据负载均衡算法选择具体的服务实例进行请求。
# 源码分析
LoadBalancerInterceptor
拦截请求:
在 LoadBalancerInterceptor
的 intercept
方法中,拦截了用户的 Http 请求,主要做了以下几件事:
- 通过
request.getURI()
获取原始请求 URI。 - 通过
originalUri.getHost()
获取服务 ID(如user-service
)。 - 通过
this.loadBalancer.execute()
方法处理服务 ID 和用户请求。
LoadBalancerClient
的execute
方法:
execute
方法核心流程:
getLoadBalancer(serviceId)
:根据服务 ID 获取ILoadBalancer
,LoadBalancer
会从 Eureka 中获取服务列表并保存。getServer(loadBalancer)
:利用负载均衡算法从服务列表中选择一个服务实例。
随后,真实请求被转发到选中的服务实例进行处理。
再次进行请求后,可以看到请求被分发到不同的端口(如 9001
和 9003
),实现了负载均衡。
# 4. Ribbon 负载均衡策略(IRule)
在负载均衡的过程中,关键点在于 IRule
的实现。IRule
决定了如何选择服务实例。默认情况下,Ribbon 使用 RoundRobinRule
,即轮询策略。
# 源码分析
在源码中,chooseServer
方法最终会调用负载均衡策略(rule.choose
)进行服务实例的选择:
默认的策略是 RoundRobinRule
(轮询策略),正是这一策略确保了请求按照顺序在服务实例之间轮转。
到此,我们已经完整理解了 Ribbon 的负载均衡机制。
# 整体流程
Ribbon 是 Spring Cloud 中实现客户端负载均衡的核心组件。它通过拦截 RestTemplate 请求,结合 Eureka 服务注册中心,动态获取服务实例列表,并根据负载均衡策略选择具体的服务实例。Ribbon 支持多种负载均衡策略(如轮询、随机、权重等),可以通过配置灵活切换。
以下是 Ribbon 负载均衡的整体流程图:
基本流程如下:
RestTemplate
请求被LoadBalancerInterceptor
拦截。- Ribbon 从请求 URI 中获取服务名称(如
user-service
)。 - Ribbon 向 Eureka 注册中心请求获取该服务的实例列表。
- 负载均衡算法从实例列表中选择一个实例(如
localhost:9001
)。 - 最终,Ribbon 将请求的 URI 替换为具体的服务实例地址,并发起请求。
# 5. 负载均衡策略
Ribbon 中的负载均衡规则都定义在 IRule
接口中,而 IRule
有多个不同的实现类,每种实现类对应不同的负载均衡策略:
不同规则的含义如下:
负载均衡策略 | 描述 |
---|---|
RoundRobinRule:轮询策略 | 简单轮询服务列表来选择服务器。这是 Ribbon 的默认负载均衡规则。默认情况下,如果获取到的服务器连续10次不可用,将返回一个空的服务器。 |
RandomRule:随机策略 | 随机选择一个可用的服务器。如果随机到的服务器为 null 或者不可用,则会在 while 循环中重新选择。 |
AvailabilityFilteringRule:最小连接数策略 | 1. 如果服务器连接失败超过3次,则进入“短路”状态,短路状态持续30秒,重试失败后短路时间会逐渐增加。 2. 忽略并发数过高的服务器。并发连接数上限可以通过客户端属性配置。 |
WeightedResponseTimeRule:加权响应时间规则 | 根据服务器响应时间赋予权重,响应时间越长权重越小,权重影响服务器选择。 |
ZoneAvoidanceRule:区域权衡策略(默认策略) | 扩展了轮询策略,过滤超时和连接数过多的服务器,同时过滤掉不符合要求的区域。最终在符合条件的服务实例中轮询。先过滤再轮询。 |
BestAvailableRule:最佳可用规则 | 忽略短路的服务器,选择并发数较低的服务器。 |
RetryRule:重试策略 | 支持在一定时间内循环重试,默认继承 RoundRobinRule 。在500ms 内会不停地选择并判断服务器是否可用。 |
默认的实现是 ZoneAvoidanceRule
,它是一种区域权衡策略的轮询方案。
# 自定义负载均衡策略
可以通过定义 IRule
来自定义负载均衡规则。常见有两种方式:
- 代码方式:在
order-service
中的OrderApplication
类中定义一个新的IRule
,该配置将针对所有微服务生效。
@Bean
public IRule randomRule() {
return new RandomRule();
}
2
3
4
- 配置文件方式:在
order-service
的application.yml
中添加如下配置,可以针对指定微服务自定义负载均衡规则。
userservice: # 为 userservice 微服务配置负载均衡规则
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 指定随机策略
2
3
注意:在实际项目中,通常使用默认的负载均衡规则即可,不建议随意修改。
# 6. 饥饿加载
Ribbon 默认采用懒加载策略,只有在第一次请求时才会创建 LoadBalanceClient
,因此第一次访问可能会较慢。为了优化首次请求的响应时间,可以启用饥饿加载,在项目启动时就创建 LoadBalanceClient
。
通过以下配置开启饥饿加载:
ribbon:
eager-load:
enabled: true
clients: userservice # 指定要提前加载的服务
2
3
4
启用饥饿加载后,Ribbon 会在项目启动时为指定的服务创建负载均衡客户端,从而减少首次请求的延迟。