客户端负载均衡
概述
在微服务架构中,服务需要调用其他服务而无需硬编码主机名。CoApi 与 Spring Cloud LoadBalancer 集成,提供客户端负载均衡:HTTP 客户端本身选择调用哪个服务实例。这消除了对外部负载均衡器的需求,并让应用程序直接控制实例选择、重试和断路。
CoApi 提供了三种选择负载均衡的方式,都解析为相同的机制:LoadBalancedExchangeFilterFunction(响应式)或 BlockingLoadBalancerInterceptor(同步)被添加到 HTTP 客户端的过滤器/拦截器链中。
一览
| 机制 | 注解 | 解析后的 URL | 负载均衡 | 来源 |
|---|---|---|---|---|
| 服务 ID | @CoApi(serviceId = "svc") | http://svc | 是 | CoApi.kt |
| LB 协议 | @CoApi(baseUrl = "lb://svc") | http://svc | 是 | CoApi.kt |
| 注解 | @CoApi @LoadBalanced | 空 | 是 | LoadBalanced.kt |
| 属性 | coapi.clients.<name>.load-balanced=true/false | 按属性 | true→是,false→否 | CoApiProperties.kt |
| 直接 URL | @CoApi(baseUrl = "http://...") | 按指定 | 否 | CoApi.kt |
URL 解析流程
当 toCoApiDefinition() 解析注解时,它会解析基础 URL 并确定负载均衡:
flowchart TD
A["@CoApi annotation"] --> B{baseUrl is not blank?}
B -->|Yes| C["Resolve ${} placeholders"]
B -->|No| D{serviceId is not blank?}
D -->|Yes| E["lb:// + serviceId"]
D -->|No| F[Empty string]
C --> G{Starts with lb:// ?}
E --> G
G -->|Yes| H["Strip lb:// → http://<br>loadBalanced = true"]
G -->|No| I["Use URL as-is<br>loadBalanced = false"]
F --> J{"@LoadBalanced present?"}
J -->|Yes| K["loadBalanced = true"]
J -->|No| L["loadBalanced = false"]
H --> M[CoApiDefinition]
I --> M
K --> M
L --> MCoApiDefinition.kt:70-97 中的解析逻辑:
| 输入 | 解析后的 URL | loadBalanced |
|---|---|---|
@CoApi(baseUrl = "lb://order-service") | http://order-service | true |
@CoApi(serviceId = "order-service") | http://order-service | true |
@CoApi @LoadBalanced | "" (空) | true |
@CoApi(baseUrl = "\${github.url}") | 解析后的值 | false |
运行时负载均衡决策
创建客户端时,工厂 Bean 先解析出生效的定义:ClientProperties.resolve(definition) 通过 CoApiDefinition.withOverrides() 这一条规则,应用 coapi.clients.<name>.* 的覆盖配置:
sequenceDiagram
autonumber
participant FB as WebClientFactoryBean / RestClientFactoryBean
participant Props as ClientProperties
participant Def as CoApiDefinition
FB->>Props: resolve(definition)
Props->>Def: withOverrides(base-url, load-balanced)
alt 配置了 load-balanced
Def-->>FB: 配置值(true/false)
else 配置了 base-url
Def-->>FB: 仅 lb:// URL 为 true(改写为 http://)
else 无覆盖配置
Def-->>FB: 注解决定的值
end优先级(CoApiDefinition.kt:156):
| 优先级 | 来源 | 效果 |
|---|---|---|
| 1(最高) | coapi.clients.<name>.load-balanced | 覆盖为配置值(true 启用,false 禁用) |
| 2 | coapi.clients.<name>.base-url(非空) | 仅 lb:// URL 启用负载均衡(自 v2.3.0 起);普通 http(s):// URL 则禁用 |
| 3(最低) | @CoApi / @LoadBalanced 注解 | 注解决定的默认值 |
lb:// 前缀匹配不区分大小写(LB:// 同样有效,自 v2.3.0 起)。如果某客户端被判定为负载均衡、但 classpath 上没有 Spring Cloud LoadBalancer,创建客户端时会抛出说明原因和解决办法的异常(自 v3.0.0 起),而不是 NoClassDefFoundError。
INFO
自 v2.1.1 起,显式配置 load-balanced: false 会被正确尊重并禁用负载均衡——在 v2.1.1 之前,任何配置值(包括 false)都会被当作 true。配置键中的 <name> 是 @CoApi 的 name 属性;未设置时为接口的简单类名。
WebClient 负载均衡
对于响应式堆栈,WebClientFactoryBean 添加 LoadBalancedExchangeFilterFunction:
sequenceDiagram
autonumber
participant FB as WebClientFactoryBean
participant CTX as ApplicationContext
participant Builder as WebClient.Builder
participant LB as LoadBalancedExchangeFilterFunction
FB->>FB: effectiveDefinition().loadBalanced → true
FB->>Builder: LoadBalancedWebClientBuilderCustomizer.customize(definition, builder)
Builder->>Builder: builder.filters { ... }
Builder->>Builder: check: any existing LB filter (LoadBalanced or Deferring)?
alt Already present
Builder->>Builder: skip
else Not present
Builder->>CTX: getBean(LoadBalancedExchangeFilterFunction)
CTX-->>LB: filter function
Builder->>Builder: filters.add(LB)
end内部的 LoadBalancedWebClientBuilderCustomizer(LoadBalancedWebClientBuilderCustomizer.kt:36)只作用于负载均衡的客户端,在添加前检查重复项,确保幂等性。该检查同时识别 LoadBalancedExchangeFilterFunction 与 Spring Cloud 的 DeferringLoadBalancerExchangeFilterFunction(@LoadBalanced WebClient.Builder 上的默认装配),因此已带负载均衡能力的 Builder 不会被重复装配。
RestClient 负载均衡
对于同步堆栈,RestClientFactoryBean 添加 BlockingLoadBalancerInterceptor:
sequenceDiagram
autonumber
participant FB as RestClientFactoryBean
participant CTX as ApplicationContext
participant Builder as RestClient.Builder
participant LB as BlockingLoadBalancerInterceptor
FB->>FB: effectiveDefinition().loadBalanced → true
FB->>Builder: LoadBalancedRestClientBuilderCustomizer.customize(definition, builder)
Builder->>Builder: builder.requestInterceptors { ... }
Builder->>Builder: check: any existing LB interceptor (Blocking or Deferring)?
alt Already present
Builder->>Builder: skip
else Not present
Builder->>CTX: getBean(BlockingLoadBalancerInterceptor)
CTX-->>LB: interceptor
Builder->>Builder: interceptors.add(LB)
end拦截器按 BlockingLoadBalancerInterceptor 接口解析,而非具体的 LoadBalancerInterceptor 类,因此无论 Spring Cloud 注册的是普通拦截器还是 RetryLoadBalancerInterceptor(启用重试时)都能正确工作。与响应式侧一致,去重检查也识别 Spring Cloud 安装在 @LoadBalanced RestClient.Builder 上的 DeferringLoadBalancerInterceptor。
每个客户端的过滤器和拦截器配置
除了负载均衡外,CoApi 还支持通过 YAML 属性配置每个客户端的过滤器/拦截器链:
coapi:
clients:
ServiceApiClientUseFilterBeanName:
reactive:
filter:
names:
- loadBalancerExchangeFilterFunction
ServiceApiClientUseFilterType:
reactive:
filter:
types:
- org.springframework.cloud.client.loadbalancer.reactive.LoadBalancedExchangeFilterFunction| 属性 | 类型 | 适用于 | 来源 |
|---|---|---|---|
coapi.clients.<name>.reactive.filter.names | Bean 名称 | WebClient(响应式) | CoApiProperties.kt |
coapi.clients.<name>.reactive.filter.types | 类类型 | WebClient(响应式) | CoApiProperties.kt |
coapi.clients.<name>.sync.interceptor.names | Bean 名称 | RestClient(同步) | CoApiProperties.kt |
coapi.clients.<name>.sync.interceptor.types | 类类型 | RestClient(同步) | CoApiProperties.kt |
工厂 Bean 通过 AbstractHttpClientFactoryBean.kt 从 ApplicationContext 解析这些引用——先按名称、再按类型。过滤器来自 ReactiveClientProperties,拦截器来自 SyncClientProperties(两者都由 CoApiProperties 实现)。
服务发现配置
CoApi 与任何 Spring Cloud DiscoveryClient 配合工作。开发环境的简单内存配置:
spring:
cloud:
discovery:
client:
simple:
instances:
github-service:
- host: api.github.com
secure: true
port: 443
provider-service:
- host: localhost
port: 8010需求
| 需求 | 如何实现 |
|---|---|
类路径上有 spring-cloud-starter-loadbalancer | Gradle/Maven 依赖 |
| 服务实例已注册 | Spring Cloud DiscoveryClient 或 SimpleDiscoveryClient |
| CoApi 中启用了负载均衡 | serviceId、lb://、@LoadBalanced 或属性 |
相关页面
- 注解(@CoApi, @LoadBalanced) — 注解参数和 URL 解析
- 客户端模式(响应式和同步) — WebClient 与 RestClient 内部原理
- 自定义和扩展 — 自定义 SPI 和过滤器链
- 配置参考 — 所有 YAML 属性
参考资料
- CoApi.kt —
api/src/main/kotlin/me/ahoo/coapi/api/CoApi.kt - LoadBalanced.kt —
api/src/main/kotlin/me/ahoo/coapi/api/LoadBalanced.kt - CoApiDefinition.kt —
spring/src/main/kotlin/me/ahoo/coapi/spring/CoApiDefinition.kt - ClientProperties.kt —
spring/src/main/kotlin/me/ahoo/coapi/spring/client/ClientProperties.kt - WebClientFactoryBean.kt —
spring/src/main/kotlin/me/ahoo/coapi/spring/client/reactive/WebClientFactoryBean.kt - RestClientFactoryBean.kt —
spring/src/main/kotlin/me/ahoo/coapi/spring/client/sync/RestClientFactoryBean.kt - CoApiProperties.kt —
spring-boot-starter/src/main/kotlin/.../CoApiProperties.kt - consumer application.yaml —
example/example-consumer-server/src/main/resources/application.yaml