Ribbon:客户端负载均衡器

目录

一、概念

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 的微服务调用为例:

  1. 服务注册:所有 user-service 的实例启动后,都向 Eureka 服务器注册自己。

  2. 获取列表order-service(消费者)在启动时,或者通过定时任务,从 Eureka 服务器拉取 user-service 的服务实例列表,并缓存在本地。

  3. 选择目标:当 order-service 需要调用 user-service 的 API 时,它会委托给 Ribbon。

  4. 执行规则:Ribbon 根据配置的负载均衡规则(如轮询),从本地的服务实例列表中选择一个目标实例(例如选择 192.168.1.101:8080)。

  5. 发起请求: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 客户端提供的服务实例状态(UPDOWN)。如果实例在 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上下文。

转载请说明出处内容投诉
CSS教程网 » Ribbon:客户端负载均衡器

发表评论

欢迎 访客 发表评论

一个令你着迷的主题!

查看演示 官网购买