SpringCloud使用Ribbon负载均衡器
Ribbon简介
Ribbon是Neflix发布的开源项目,后由Spring Cloud开发团队封装于Spring Cloud中,可以让我们轻松地将面向服务的REST模版请求自动转换成客户端负载均衡的服务调用。功能是提供客户端的软件负载均衡算法和服务调用。Ribbon是一个基于HTTP和CP的客户端负载均衡工具,Spring Cloud Ribbon虽然只是一个工具类框架,它不像服务注册中心、配置中心、API网关那样需要独立部署,但是它几乎存在于每一个Spring Cloud构建的微服务和基础设施中。因为微服务间的调用,API网关的请求转发等内容,实际上都是通过Ribbon来实现的,包括后续我们将要介绍的Feign,它也是基于Ribbon实现的工具。所以,对Spring Cloud Ribbon的理解和使用,对于我们使用Spring Cloud来构建微服务非常重要。Ribbon客户端提供一系列的完善的配置项,如连接超时,重试等。简单的说,就是配置文件中列出Load Balancer(简称LB)后面所有的机器,Ribbon会自动的帮助你基于某种规则(如简单轮询,随机等)去连接这些机器。我们很容器使用Ribbon实现自定义负载均衡算法。
什么是Load Balancer?
简单的说就是将用户的请求平摊的分配到多个服务上,从而达到系统的HA(高可用)。常见的负载均衡的软件有:Nginx,LVS,硬件F5等。
Ribbon本地负载均衡与Nginx服务端负载均衡的区别?
Nginx是服务器的负载均衡,客户端所有的请求都会交给Nginx,然后由Nginx实现转发请求,即负载均衡是由服务端实现的。
Ribbon本地负载均衡,在调用服务接口的时候,会在注册中心上获取注册信息服务列表之后缓冲到JVM本地,从而在本地实现RPC远程服务调用技术。
LB负载均衡分为哪两种?
集中式LB,即在服务的消费方和提供方之间是有独立的LB设施(可以是硬件,如F5,也可以是软件,如Nginx),由该设施负责把访问请求通过某种策略转发至服务的提供方。
进程内LB,将LB逻辑集成于消费方,消费方从注册中心获知有哪些地址可用,然后自己再从这些地址中选择出一个 合适的服务器。Ribbon就属于进程内LB,它只是一个类库,集成于消费方进程,消费方通过它来获取到服务提供方的地址。
Ribbon 核心组件
Ribbon 主要有五大功能组件:ServerList、Rule、Ping、ServerListFilter、ServerListUpdater。
负载均衡器 LoadBalancer
用于管理负载均衡的组件。初始化的时候通过加载 YMAL 配置文件创建出来的。
服务列表 ServerList
ServerList 主要用来获取所有服务的地址信息,并存到本地。
根据获取服务信息的方式不同,又分为静态存储和动态存储。
静态存储:从配置文件中获取服务节点列表并存储到本地。
动态存储:从注册中心获取服务节点列表并存储到本地
服务列表过滤 ServerListFilter
将获取到的服务列表按照过滤规则过滤。
- 通过 Eureka 的分区规则对服务实例进行过滤。
- 比较服务实例的通信失败数和并发连接数来剔除不够健康的实例。
- 根据所属区域过滤出同区域的服务实例。
服务列表更新 ServerListUpdater
服务列表更新就是 Ribbon 会从注册中心获取最新的注册表信息。是由这个接口 ServerListUpdater 定义的更新操作。而它有两个实现类,也就是有两种更新方式:
- 通过定时任务进行更新。由这个实现类 PollingServerListUpdater 做到的。
- 利用 Eureka 的事件监听器来更新。由这个实现类 EurekaNotificationServerListUpdater 做到的。
心跳检测 Ping
IPing 接口类用来检测哪些服务可用。如果不可用了,就剔除这些服务。
实现类主要有这几个:PingUrl、PingConstant、NoOpPing、DummyPing、NIWSDiscoveryPing。
心跳检测策略对象 IPingStrategy,默认实现是轮询检测。
负载均衡策略 Rule
主要由以下几种均衡策略:
1)线性轮询均衡(RoundRobinRule):轮流依次请求不同的服务器。优点是无需记录当前所有连接的状态,无状态调度。
2)可用服务过滤负载均衡(AvailabilityFilteringRule):过滤多次访问故障而处于断路器状态的服务,还有过滤并发连接数量超过阈值的服务,然后对剩余的服务列表按照轮询策略进行访问。默认情况下,如果最近三次连接均失败,则认为该服务实例断路。然后保持 30s 后进入回路关闭状态,如果此时仍然连接失败,那么等待进入关闭状态的时间会随着失败次数的增加呈指数级增长。
3)加权响应时间负载均衡(WeightedResponseTimeRule):为每个服务按响应时长自动分配权重,响应时间越长,权重越低,被选中的概率越低。
4)区域感知负载均衡(ZoneAvoidanceRule):更倾向于选择发出调用的服务所在的托管区域内的服务,降低延迟,节省成本。Spring Cloud Ribbon 中默认的策略。
5)重试负载均衡(RetryRule):通过轮询均衡策略选择一个服务器,如果请求失败或响应超时,可以选择重试当前服务节点,也可以选择其他节点。
6)高可用均衡(BestAvailableRule):忽略请求失败的服务器,尽量找并发比较低的服务器。注意:这种会给服务器集群带来成倍的压力。
7)随机负载均衡(RandomRule):随机选择服务器。适合并发比较大的场景。
Ribbon 拦截请求的原理
负载均衡器如何将客户端请求进行拦截然后选择服务器进行转发?
结合上面介绍的 Ribbon 核心组件,我们可以画一张原理图来梳理下 Ribbon 拦截请求的原理:
1)Ribbon 拦截所有标注 @loadBalance 注解的 RestTemplate。RestTemplate 是用来发送 HTTP 请求的。
2)将 Ribbon 默认的拦截器 LoadBalancerInterceptor 添加到 RestTemplate 的执行逻辑中,当 RestTemplate 每次发送 HTTP 请求时,都会被 Ribbon 拦截。
3)拦截后,Ribbon 会创建一个 ILoadBalancer 实例。
4)ILoadBalancer 实例会使用 RibbonClientConfiguration 完成自动配置。就会配置好 IRule,IPing,ServerList。
5)Ribbon 会从服务列表中选择一个服务,将请求转发给这个服务。
Ribbon 初始化的原理
当我们去剖析 Ribbon 源码的时候,需要找到一个突破口,而 @LoadBalanced 注解就是一个比较好的入口。
先来一张 Ribbon 初始化的流程图:
添加注解的代码如下所示:
@LoadBalanced
@Bean
public RestTemplate restTemplate{
return new RestTemplate();
}
第一步:Ribbon 有一个自动配置类 LoadBalancerAutoConfiguration,SpringBoot 加载自动配置类,就会去初始化 Ribbon。
第二步:当我们给 RestTemplate 或者 AsyncRestTemplate 添加注解后,Ribbon 初始化时会收集加了 @LoadBalanced 注解的 RestTemplate 和 AsyncRestTemplate ,把它们放到一个 List 里面。
第三步:然后 Ribbon 里面的 RestTemplateCustomizer 会给每个 RestTemplate 进行定制化,也就是加上了拦截器:LoadBalancerInterceptor。
第四步:从 Eureka 注册中心获取服务列表,然后存到 Ribbon 中。
第五步:加载 YMAL 配置文件,配置好负载均衡配置,创建一个 ILoadbalancer 实例。
Ribbon 同步服务列表原理
Ribbon 首次从 Eureka 获取全量注册表后,就会隔一定时间获取注册表。原理图如下:
之前我们提到过 Ribbon 的核心组件 ServerListUpdater,用来同步注册表的,它有一个实现类 PollingServerListUpdater ,专门用来做定时同步的。默认1s 后执行一个 Runnable 线程,后面就是每隔 30s 执行 Runnable 线程。这个 Runnable 线程就是去获取 Eureka 注册表的。
Ribbon 心跳检测的原理
Ribbon 的心跳检测原理和 Eureka 不一样, Ribbon 不是通过每个服务向 Ribbon 发送心跳或者 Ribbon 给每个服务发送心跳来检测服务是否存活的。
先来一张图看下 Ribbon 的心跳检测机制:
Ribbon 心跳检测原理:对自己本地缓存的 Server List 进行遍历,看下每个服务的状态是不是 UP 的。具体的代码就是 isAlive 方法。
isAlive = status.equals(InstanceStatus.UP);
那么多久检测一次呢?
默认每隔 30s执行以下 PingTask 调度任务,对每个服务执行 isAlive 方法,判断下状态。
SpringCloud整合Ribbon
基础使用
1、引入相关的依赖
<!-- 添加eureka客户端的依赖 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<!-- 新版本loadbalancer替换了ribbon,这里我们剔除掉-->
<exclusions>
<exclusion>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--ribbon负载均衡依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
<exclusions>
<exclusion>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- 新版本的eureka客户端已经剔除了ribbon-->
<!-- 上面我们只是引入了ribbon和eureka client,还需要引入的它们的关系jar-->
<!-- 引入完该包后,我们能找到DiscoveryEnabledNIWSServerList类了-->
<!-- 但是要确保与netflix-ribbon引入的ribbon-core一致,否则会发生一些找不到class或找不到方法的问题-->
<!-- 如果要升级版本,也要升级netflix-ribbon引入的ribbon相关的依赖-->
<dependency>
<groupId>com.netflix.ribbon</groupId>
<artifactId>ribbon-eureka</artifactId>
<version>2.3.0</version>
</dependency>
<!-- spring-retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.3</version>
</dependency>
2、application.yml配置
# ribbon配置,部分配置项参考RibbonProperties类
ribbon:
eager-load:
#开启饥饿加载模式
enabled: true
#指定需要饥饿加载的服务名
clients: CLOUD-ORDER-SERVICE
#是否禁用eureka,如果禁用(false),就需要按下面针对具体的服务配置
eureka:
enabled: true
# 请求连接的超时时间
connectTimeout: 2000
# 请求处理的超时时间
readTimeout: 5000
# 最大连接数
maxTotalConnections: 500
# 每个host最大连接数
maxConnectionsPerHost: 500
#对当前实例重试的次数
maxAutoRetries: 3
#切换实例的重试次数
maxAutoRetriesNextServer: 3
#对所有的操作请求都进行重试
okToRetryOnAllOperations: true
#Http响应码进行重试
retryableStatusCodes: 500,404,502
######针对具体的服务配置#####
## 禁用 Eureka 后就需要手动配置服务地址
#CLOUD-USER-SERVICE: # 具体的服务名
# ribbon:
# # 指定服务名对应的服务地址列表
# listOfServers: http://localhost:8888,http://localhost:8899
# # 请求连接的超时时间
# connectTimeout: 2000
# # 请求处理的超时时间
# readTimeout: 5000
3、创建RestTemplate Bean
//如果是集群服务, 调用时使用了服务名请求,则必须添加@LoadBalanced注解,使用ip请求不用添加
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
如何获得动态的服务列表(ServerList)
1)AbstractServerList
主要作用是提供了对服务的过滤,通过getFilterImpl方法加载ServerListFilter。最后过滤器肯定是用于对服务的过滤。
2)ConfigurationBasedServerList
从配置中加载出服务列表
3)DiscoveryEnabledNIWSServerList
通过注册中心的客户端,从注册中心获取服务列表
4)DomainExtractingServerList
将通过注册中心发现的服务元数据,进行分区,转化成DomainExtractingServer
5)StaticServerList
静态的服务列表
深入Ribbon配置
1、常用配置
1)禁用Eureka
当我们在RestTemplate上添加 @LoadBalanced 注解后,就可以用服务名称来调用接口了,当有多个服务的时候,还能做负载均衡。
这是因为Eureka中的服务信息已经被拉取到了客户端本地,如果我们不想和 Eureka 集成,可以通过下面的配置方法将其禁用。
# 禁用 Eureka
ribbon:
eureka:
enabled: false
当我们禁用了 Eureka 之后,就不能使用服务名称去调用接口了,必须指定服务地址。
2)配置接口地址列表
上面我们讲了可以禁用 Eureka,禁用之后就需要手动配置调用的服务地址了,配置如下:
CLOUD-USER-SERVICE: # 具体的服务名
ribbon:
# 指定服务名对应的服务地址列表
listOfServers: http://localhost:8888,http://localhost:8899
这个配置是针对具体服务的,前缀就是服务名称,配置完之后就可以和之前一样使用服务名称来调用接口了。
3)配置负载均衡策略
Ribbon默认的策略是轮询,Ribbon 中提供了很多的策略,我们通过配置可以指定服务使用哪种策略来进行负载操作。
4)超时时间
Ribbon 中有两种和时间相关的设置,分别是请求连接的超时时间和请求处理的超时时间,设置规则如下:
ribbon:
# 请求连接的超时时间
connectTimeout: 2000
# 请求处理的超时时间
readTimeout: 5000
#也可以为每个Ribbon客户端设置不同的超时时间, 通过服务名称进行指定:
CLOUD-USER-SERVICE: # 具体的服务名
ribbon:
# 请求连接的超时时间
connectTimeout: 2000
# 请求处理的超时时间
readTimeout: 5000
5)并发参数
ribbon:
# 最大连接数
maxTotalConnections: 500
# 每个host最大连接数
maxConnectionsPerHost: 500
2、代码配置 Ribbon
配置 Ribbon 最简单的方式就是通过配置文件实现。当然我们也可以通过代码的方式来配置。
通过代码方式来配置之前自定义的负载策略,首先需要创建一个配置类,初始化自定义的策略,代码如下所示:
@Configuration
public class RibbonIRule {
@Bean
public IRule ribbonRule() {
// 设置随机的负载策略
return new RandomRule();
//设置重试的负载策略
//return new RetryRule();
//设置轮询的负载策略
//return new RoundRobinRule();
}
}
创建一个 Ribbon 客户端的配置类,关联 RibbonIRule,用 name 来指定调用的服务名称,代码如下所示:
@RibbonClient(name = "CLOUD-USER-SERVICE", configuration = RibbonIRule.class)
public class RibbonClientConfig {
}
3、配置文件方式配置 Ribbon
除了使用代码进行 Ribbon 的配置,我们还可以通过配置文件的方式来为 Ribbon 指定对应的配置:
<clientName>.ribbon.NFLoadBalancerClassName: Should implement ILoadBalancer(负载均衡器操作接口)
<clientName>.ribbon.NFLoadBalancerRuleClassName: Should implement IRule(负载均衡算法)
<clientName>.ribbon.NFLoadBalancerPingClassName: Should implement IPing(服务可用性检查)
<clientName>.ribbon.NIWSServerListClassName: Should implement ServerList(服务列表获取)
<clientName>.ribbon.NIWSServerListFilterClassName: Should implement ServerListFilter(服务列表的过滤)
4、重试机制
在集群环境中,用多个节点来提供服务,难免会有某个节点出现故障。用 Nginx 做负载均衡的时候,如果你的应用是无状态的、可以滚动发布的,也就是需要一台台去重启应用,这样对用户的影响其实是比较小的,因为 Nginx 在转发请求失败后会重新将该请求转发到别的实例上去。
由于 Eureka 是基于 AP 原则构建的,牺牲了数据的一致性,每个 Eureka 服务都会保存注册的服务信息,当注册的客户端与 Eureka 的心跳无法保持时,有可能是网络原因,也有可能是服务挂掉了。
在这种情况下,Eureka 中还会在一段时间内保存注册信息。这个时候客户端就有可能拿到已经挂掉了的服务信息,故 Ribbon 就有可能拿到已经失效了的服务信息,这样就会导致发生失败的请求。
这种问题我们可以利用重试机制来避免。重试机制就是当 Ribbon 发现请求的服务不可到达时,重新请求另外的服务。
解决上述问题,最简单的方法就是利用 Ribbon 自带的重试策略进行重试,此时只需要指定某个服务的负载策略为重试策略即可:
CLOUD-USER-SERVICE:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RetryRule
除了使用 Ribbon 自带的重试策略,我们还可以通过集成 Spring Retry 来进行重试操作。
在 pom.xml 中添加 Spring Retry 的依赖:
<!-- spring-retry -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
<version>1.3.3</version>
</dependency>
配置内容如下:
ribbon:
#对当前实例重试的次数
maxAutoRetries: 3
#切换实例的重试次数
maxAutoRetriesNextServer: 3
#对所有的操作请求都进行重试
okToRetryOnAllOperations: true
#Http响应码进行重试
retryableStatusCodes: 500,404,502
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!