在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




