Spring Boot2.6升级负载均衡多primary ServiceInstanceListSupplier报错
问题背景
存量项目从Spring Boot 2.2.x升级至2.6.x版本,跨版本升级涉及多处代码适配,经API网关发起请求时抛出负载均衡异常,报错信息如下:
more than one 'primary' bean found among candidates: [zookeeperDiscoveryClientServiceInstanceListSupplier, serviceInstanceListSupplier, retryAwareDiscoveryClientServiceInstanceListSupplier]
自定义的serviceInstanceListSupplier已经添加@Primary注解,预期优先于其他同类型Bean加载,报错触发点在CustomRoundRobinLoadBalancer的choose方法中调用serviceInstanceListSupplierProvider.getIfAvailable(...)时抛出。
根因分析
Spring Cloud 2021.0.x(对应Spring Boot 2.6.x)对负载均衡相关的自动配置逻辑做了不兼容变更:
- 框架默认自动配置的
zookeeperDiscoveryClientServiceInstanceListSupplier、retryAwareDiscoveryClientServiceInstanceListSupplier在新版本中也配置了Primary优先级,和自定义带@Primary的Bean同时存在于负载均银子上下文时,Spring无法选出唯一的Primary候选Bean - 自定义的
ClientConfiguration作为LoadBalancer默认配置类,其定义的Bean会和Spring Cloud LoadBalancer的自动配置Bean在同一个子上下文中加载,既没有通过@Order调整自定义Bean的加载优先级,也没有排除默认自动装配的Supplier Bean,直接导致多Primary Bean冲突 - 附带代码缺陷:
CustomExchangeFilterFunction存在构造函数递归调用、transformer参数未正确注入、普通类内定义@Bean方法无法被扫描、静态字段持有上下文对象的问题,即使解决Primary冲突后续也会触发栈溢出、空指针等运行时异常
修复方案
按以下步骤调整代码即可解决问题:
1. 解决ServiceInstanceListSupplier多Primary冲突
在自定义ServiceInstanceListSupplier的Bean定义上添加最高优先级注解,确保自定义实现优先加载,覆盖默认自动配置的Bean:
@Bean @Primary @Order(Ordered.HIGHEST_PRECEDENCE) public ServiceInstanceListSupplier serviceInstanceListSupplier( @Qualifier("filter") Predicate<ServiceInstance> filter, DiscoveryClient discoveryClient, Environment environment, @Qualifier("loadBalancerName") String loadBalancerName ) { // 原有实例过滤逻辑保持不变 if(environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME) == null) { StandardEnvironment wrapped = new StandardEnvironment(); if(environment instanceof ConfigurableEnvironment) { ((ConfigurableEnvironment) environment).getPropertySources() .forEach(s -> wrapped.getPropertySources().addLast(s)); } Map<String, Object> additionalProperties = new HashMap<>(); additionalProperties.put(LoadBalancerClientFactory.PROPERTY_NAME, loadBalancerName); wrapped.getPropertySources().addLast(new MapPropertySource(loadBalancerName, additionalProperties)); environment = wrapped; } return new FilteringInstanceListSupplier(filter, discoveryClient, environment); }
如果上述调整仍存在冲突,可在ClientConfiguration类上添加注解,直接排除默认Supplier的自动配置类:
@LoadBalancerClientConfiguration( proxyBeanMethods = false, excludedConfigurations = { ServiceInstanceListSupplierConfiguration.class, ZookeeperLoadBalancerConfiguration.class, RetryAwareLoadBalancerConfiguration.class } ) public class ClientConfiguration { // 原有其他代码保持不变 }
2. 修复CustomExchangeFilterFunction的代码缺陷
移除原代码中递归调用的构造函数、错误放置在普通类内的@Bean方法、静态持有上下文的字段,适配新版本父类方法签名,修正后代码如下:
public class CustomExchangeFilterFunction extends ReactorLoadBalancerExchangeFilterFunction { private static final ThreadLocal<ClientRequest> REQUEST_HOLDER = new ThreadLocal<>(); private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory; public CustomExchangeFilterFunction ( ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory, List<LoadBalancerClientRequestTransformer> transformersList ) { super(loadBalancerFactory, transformersList == null ? Collections.emptyList() : transformersList); this.loadBalancerFactory = loadBalancerFactory; } @Override public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) { REQUEST_HOLDER.set(request); try { return super.filter(request, next); } finally { REQUEST_HOLDER.remove(); } } // 适配新版本父类choose方法签名,删除旧签名的重载方法 @Override protected Mono<Response<ServiceInstance>> choose( ReactiveLoadBalancer<ServiceInstance> loadBalancer, Request<?> request ) { ClientRequest originRequest = REQUEST_HOLDER.get(); Assert.notNull(originRequest, "request不能为null,底层实现已变更线程模型"); // 原有实例过滤、版本路由逻辑保持不变 return Mono.from(loadBalancer.choose(request)); } }
同时修正WebClientConfiguration中Filter Bean的注入逻辑,正确注入transformer列表,不要手动传null,将原本写在Filter类内的transformer Bean移到配置类中:
@Configuration @LoadBalancerClients(defaultConfiguration = ClientConfiguration.class) public class WebClientConfiguration { // 原有WebClient、WebClient.Builder的Bean定义保持不变 @Bean @Primary public ReactorLoadBalancerExchangeFilterFunction reactorLoadBalancerExchangeFilterFunction( ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerFactory, ObjectProvider<List<LoadBalancerClientRequestTransformer>> transformersProvider ) { List<LoadBalancerClientRequestTransformer> transformers = transformersProvider.getIfAvailable(Collections::emptyList); return new CustomExchangeFilterFunction(loadBalancerFactory, transformers); } @Bean public LoadBalancerClientRequestTransformer instanceIdHeaderTransformer() { return (request, instance) -> ClientRequest.from(request) .header("X-Instance-Id", instance.getInstanceId()) .build(); } }
3. 验证结果
调整完成后重启服务:
- 多Primary Bean的冲突错误直接消失,
ObjectProvider.getIfAvailable()可以正常拿到唯一的自定义ServiceInstanceListSupplier实例 - 负载均衡逻辑正常执行,请求会按自定义的轮询规则路由到后端实例
- 不会出现构造函数递归栈溢出、transformer空指针、方法签名不匹配导致自定义逻辑不生效等潜在问题
内容的提问来源于stack exchange,提问作者Pompompurin
相关产品推荐
相关产品推荐

