You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)对负载均衡相关的自动配置逻辑做了不兼容变更:

  1. 框架默认自动配置的zookeeperDiscoveryClientServiceInstanceListSupplier、retryAwareDiscoveryClientServiceInstanceListSupplier在新版本中也配置了Primary优先级,和自定义带@Primary的Bean同时存在于负载均银子上下文时,Spring无法选出唯一的Primary候选Bean
  2. 自定义的ClientConfiguration作为LoadBalancer默认配置类,其定义的Bean会和Spring Cloud LoadBalancer的自动配置Bean在同一个子上下文中加载,既没有通过@Order调整自定义Bean的加载优先级,也没有排除默认自动装配的Supplier Bean,直接导致多Primary Bean冲突
  3. 附带代码缺陷: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 07:54:32