You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

在Spring负载均衡微服务应用中,哪种客户端负载均衡算法可实现先向副本微服务B1发送100个请求、再向B2发送100个请求的需求

实现批量实例绑定的Spring客户端负载均衡方案

嘿,这个需求其实很清晰——要实现前100次请求固定发往B1、后续100次发往B2的批量分发逻辑,咱们可以通过自定义客户端负载均衡算法来搞定,而且在Spring生态里不管是用现在主流的Spring Cloud LoadBalancer,还是老版本的Ribbon,都能轻松实现。

核心思路

本质上是基于计数控制的轮询变种算法

  • 维护一个原子计数器来跟踪请求次数,避免并发场景下的计数混乱
  • 维护一个当前选中的实例索引(0对应B1,1对应B2)
  • 每达到指定批次请求数(100次),切换到另一个实例;如果需要循环重复逻辑,可在计数器达到200时重置状态

方案一:基于Spring Cloud LoadBalancer(推荐,Ribbon已进入维护状态)

Spring Cloud LoadBalancer是Spring官方主推的负载均衡组件,咱们可以通过自定义ReactorServiceInstanceLoadBalancer来实现需求:

1. 自定义负载均衡配置类

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.loadbalancer.core.ReactorLoadBalancer;
import org.springframework.cloud.loadbalancer.core.ServiceInstanceListSupplier;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import reactor.core.publisher.Mono;

import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

@Configuration
public class CustomBatchLoadBalancerConfig {

    @Bean
    public ReactorLoadBalancer<ServiceInstance> customBatchLoadBalancer(Environment env,
                                                                        LoadBalancerClientFactory clientFactory) {
        String serviceId = env.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
        return new BatchRoundRobinLoadBalancer(
                clientFactory.getLazyProvider(serviceId, ServiceInstanceListSupplier.class),
                serviceId);
    }

    // 自定义批量轮询负载均衡器
    static class BatchRoundRobinLoadBalancer implements ReactorLoadBalancer<ServiceInstance> {
        private final ServiceInstanceListSupplier instanceSupplier;
        private final String serviceId;
        private final AtomicInteger requestCounter = new AtomicInteger(0);
        private volatile int currentInstanceIndex = 0; // 0=B1,1=B2
        private static final int BATCH_COUNT = 100; // 每批次请求数

        public BatchRoundRobinLoadBalancer(ServiceInstanceListSupplier instanceSupplier, String serviceId) {
            this.instanceSupplier = instanceSupplier;
            this.serviceId = serviceId;
        }

        @Override
        public Mono<ServiceInstance> choose(Request request) {
            return instanceSupplier.get().next()
                    .map(this::selectTargetInstance);
        }

        private ServiceInstance selectTargetInstance(List<ServiceInstance> instances) {
            if (instances.isEmpty()) {
                return null;
            }
            // 确保服务B只有两个实例(B1、B2)
            if (instances.size() != 2) {
                throw new IllegalStateException("服务" + serviceId + "必须有且仅有2个实例");
            }

            int currentCount = requestCounter.incrementAndGet();
            // 当请求数超过当前批次阈值,切换实例
            if (currentCount > (currentInstanceIndex + 1) * BATCH_COUNT) {
                currentInstanceIndex = 1 - currentInstanceIndex; // 在0和1之间切换
                // 如果需要循环重复前100B1、后100B2的逻辑,打开下面的注释:
                // if (currentCount >= 2 * BATCH_COUNT) {
                //     requestCounter.set(0);
                //     currentInstanceIndex = 0;
                // }
            }

            return instances.get(currentInstanceIndex);
        }
    }
}

2. 绑定微服务A与自定义配置

在微服务A的启动类上,通过@LoadBalancerClient注解将自定义配置与服务B绑定:

@SpringBootApplication
@LoadBalancerClient(name = "service-b", configuration = CustomBatchLoadBalancerConfig.class)
public class ServiceAApplication {
    public static void main(String[] args) {
        SpringApplication.run(ServiceAApplication.class, args);
    }
}

方案二:基于Ribbon(仅适用于老版本Spring Cloud)

如果你的项目还在使用Ribbon,可以自定义IRule来实现相同逻辑:

1. 自定义Ribbon规则类

import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.IRule;
import com.netflix.loadbalancer.Server;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class BatchRoundRobinRule extends AbstractLoadBalancerRule {
    private final AtomicInteger requestCounter = new AtomicInteger(0);
    private volatile int currentInstanceIndex = 0;
    private static final int BATCH_COUNT = 100;

    @Override
    public Server choose(Object key) {
        List<Server> reachableServers = getLoadBalancer().getReachableServers();
        if (reachableServers.isEmpty()) {
            return null;
        }
        if (reachableServers.size() != 2) {
            throw new IllegalStateException("目标服务必须有且仅有2个实例");
        }

        int currentCount = requestCounter.incrementAndGet();
        if (currentCount > (currentInstanceIndex + 1) * BATCH_COUNT) {
            currentInstanceIndex = 1 - currentInstanceIndex;
            // 循环逻辑可选:
            // if (currentCount >= 2 * BATCH_COUNT) {
            //     requestCounter.set(0);
            //     currentInstanceIndex = 0;
            // }
        }

        return reachableServers.get(currentInstanceIndex);
    }

    @Override
    public void initWithNiwsConfig(com.netflix.client.config.IClientConfig clientConfig) {
        // 无需额外初始化操作
    }
}

2. 配置Ribbon使用自定义规则

在微服务A的application.yml中添加配置:

service-b:
  ribbon:
    NFLoadBalancerRuleClassName: com.your.package.BatchRoundRobinRule

注意事项

  • 确保服务B的实例数量严格为2(B1和B2),否则自定义算法会抛出异常,你也可以根据需求调整异常处理逻辑(比如 fallback 到默认轮询)
  • 原子计数器AtomicInteger保证了并发场景下的计数准确性
  • 如果需要循环重复分发逻辑,只需打开代码中注释的计数器重置部分即可

内容的提问来源于stack exchange,提问作者Murugesh

火山引擎 最新活动