目录
[Spring Cloud LoadBalancer](#Spring Cloud LoadBalancer)
问题分析
在 服务注册与发现------Eureka-CSDN博客 中,我们根据应用名称 获取了服务实例列表,并从列表中选择了一个服务实例:

若一个服务对应多个实例,是否能够将流量合理的分配到多个实例呢?
我们启动多个 product-service 实例

修改端口号:


再添加一个实例,并启动:

观察 Eureka,可以看到 product-service 中有三个实例:

此时,我们多次访问 127.0.0.1:8080/order/1

可以看到,多次访问的都是同一台机器,我们启动多个实例,就是希望能够减轻单机压力,也就是每个实例处理部分请求,而不是让同一台机器处理所有请求
那么,如何实现多个机器分摊负荷呢?
我们可以依次将请求分发给服务器列表中的每一台机器 ,因此,我们对 OrderService中代码进行修改:
java
@Slf4j
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
// 当前选择实例
private static AtomicInteger atomicInteger = new AtomicInteger(1);
// 实例列表
private static List<ServiceInstance> instances;
@PostConstruct
public void init() {
// 获取服务列表
instances = discoveryClient.getInstances("product-service");
}
public OrderInfo findOrderInfoById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
// 计算当前访问实例
int index = atomicInteger.getAndIncrement() % instances.size();
EurekaServiceInstance instance = (EurekaServiceInstance)instances.get(index);
log.info("选择实例: " + instance.getInstanceId());
// 拼接 URL
String url = instance.getUri() + "/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
多次访问 127.0.0.1:8080/order/1:

此时请求被均衡地分配到了不同实例上,上述这种方式,就是负载均衡
负载均衡
负载均衡(Load Balance,简称 LB) 是高并发、高可用系统必不可少的关键组件,目的是将网络流量或计算任务智能地分配到多个服务器(或资源) 上**,** 从而 提高系统性能 (响应更快)、增强可用性与可靠性 (一台服务器宕机,其他还能继续服务)、提升可扩展性 (通过增加服务器来应对更多请求)以及 避免单点过载(防止某台服务器被压垮)
我们通过一个生活中的示例来理解:
想象你有一家热门奶茶店,若只有一个收银员时,此时队伍会排得很长
而如果开了 3 个收银台,并有一个引导员把顾客平均分配到各窗口 ,整体效率就大幅提升 ------ 这个"引导员"就是负载均衡器。
负载均衡的核心作用:
1. 分发请求:把用户请求(如 HTTP 请求)分给后端多个服务器。
2. 健康检查:自动检测服务器是否宕机,剔除故障节点。
3. 缓存与压缩:部分负载均衡器还能缓存静态内容,加速响应
常见负载均衡策略:
| 策略 | 说明 |
|---|---|
| 轮询(Round Robin) | 依次轮流分配请求(最简单常用) |
| 加权轮询 | 性能强的服务器分配更多请求(如 A 权重 3,B 权重 1) |
| 最少连接 | 把请求发给当前连接数最少的服务器 |
| IP Hash | 根据用户 IP 固定分配到某台服务器(实现会话保持) |
| 响应时间优先 | 选择响应最快、负载最低的服务器 |
负载均衡的实现可分为服务端负载均衡 和 客户端负载均衡, 这两种不同的流量分发策略**,** 核心区别在于:"谁来决定请求发给哪台后端服务器?"
服务端负载均衡
由独立的负载均衡器(如 Nginx、云 LB) 位于客户端和后端服务之间,统一接收所有请求并分发到后端服务器

客户端负载均衡
客户端自己决定 将请求发给哪一台后端服务器。客户端需事先获取服务实例列表 (通常通过服务注册中心),并在本地执行负载均衡算法

接下来,我们来学习 Spring Cloud LoadBalance
Spring Cloud LoadBalancer
Spring Cloud LoadBalancer 是Spring Cloud 提供的一个客户端负载均衡器 ,用于在微服务架构中,让服务消费者(客户端)能够从多个服务实例中智能选择一个可用实例进行调用
在 Spring Cloud Netflix Ribbon 被弃用后,Spring Cloud LoadBalancer 成为了官方推荐的替代方案
要使用 Spring Cloud LoadBalancer 实现负载均衡策略非常简单,只需要给 RestTemplate 添加 @LoadBalanced 注解即可
java
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
将IP 和端口号 修改为服务名称:
java
@Slf4j
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private RestTemplate restTemplate;
public OrderInfo findOrderInfoById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
// 访问 URL
String url = "http://product-service/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
多次发起请求 127.0.0.1:8080/order/1,观察 product-service 的日志,可以看到请求被分配到这三个实例上:

Spring Cloud LoadBalancer 仅支持两种负载均衡策略:轮询策略 和 随机策略 ,默认的负载均衡策略是 轮询策略 (RoundRobinLoadBalancer)
若需要采用随机策略,也非常简单
自定义负载均衡策略
定义随机策略对象,并通过**@Bean** 将其加载到 Spring 容器中:
java
public class CustomLoadBalancerConfiguration {
@Bean
ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(Environment environment,
LoadBalancerClientFactory loadBalancerClientFactory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(loadBalancerClientFactory
.getLazyProvider(name, ServiceInstanceListSupplier.class),
name);
}
}
使用**@LoadBalancerClient**注解配置上述随机策略:
java
@LoadBalancerClient(name = "product-service", configuration = CustomLoadBalancerConfiguration.class)
@Configuration
public class BeanConfig {
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
name:配置的负载均衡策略对哪个服务生效
configuration:使用的负载均衡策略
多次发起请求 127.0.0.1:8080/order/1,观察product-service 的日志:

此时请求分布接近均匀
那么 Spring Cloud LoadBalancer 具体是如何实现负载均衡的呢?接下来我们就来看 Spring Cloud LoadBalancer 的具体实现
实现原理
Spring Cloud LoadBalancer 主要是通过 LoadBalancerInterceptor 来实现的,LoadBalancerInterceptor 会对 RestTemplate 的 请求进行拦截 ,然后从 Eureka 根据服务 id 获取服务列表 ,最后根据负载均衡算法 得到真实的服务地址信息 ,并替换服务 id
我们来看 LoadBalancerInterceptor的具体实现:
java
public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor {
private LoadBalancerClient loadBalancer;
private LoadBalancerRequestFactory requestFactory;
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
final ClientHttpRequestExecution execution) throws IOException {
// 从请求中获取 url, 如 http://product-service/product/1001
final URI originalUri = request.getURI();
// 获取路径主机名, 也就是服务id, 即 product-service
String serviceName = originalUri.getHost();
// 判断 serviceName 是否为空
Assert.state(serviceName != null, "Request URI does not contain a valid hostname: " + originalUri);
// 根据服务 id 进行负载均衡,并处理请求
return this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution));
}
}
继续看 execute实现:
java
public class BlockingLoadBalancerClient implements LoadBalancerClient {
private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;
@Override
public <T> T execute(String serviceId, LoadBalancerRequest<T> request) throws IOException {
String hint = getHint(serviceId);
LoadBalancerRequestAdapter<T, TimedRequestContext> lbRequest = new LoadBalancerRequestAdapter<>(request,
buildRequestContext(request, hint));
Set<LoadBalancerLifecycle> supportedLifecycleProcessors = getSupportedLifecycleProcessors(serviceId);
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStart(lbRequest));
// 根据 serviceId 和 lbRequest(负载均衡策略)选择服务
ServiceInstance serviceInstance = choose(serviceId, lbRequest);
if (serviceInstance == null) {
supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onComplete(
new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, new EmptyResponse())));
throw new IllegalStateException("No instances available for " + serviceId);
}
return execute(serviceId, serviceInstance, lbRequest);
}
@Override
public <T> ServiceInstance choose(String serviceId, Request<T> request) {
// 获取负载均衡器
ReactiveLoadBalancer<ServiceInstance> loadBalancer = loadBalancerClientFactory.getInstance(serviceId);
if (loadBalancer == null) {
return null;
}
// 根据负载均衡算法,从列表中选择一个服务实例
Response<ServiceInstance> loadBalancerResponse = Mono.from(loadBalancer.choose(request)).block();
if (loadBalancerResponse == null) {
return null;
}
return loadBalancerResponse.getServer();
}
}
我们继续看不同负载均衡策略 的选择 实现,先来看轮询策略 (RoundRobinLoadBalancer)实现:
java
public class RoundRobinLoadBalancer implements ReactorServiceInstanceLoadBalancer {
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
// 通过 processInstanceResponse 进行处理
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
// 调用 getInstanceResponse 获取选择的实例
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
if (instances.size() == 1) {
return new DefaultResponse(instances.get(0));
}
// 原子性地增加并返回新值 同时 任何数字 & MAX_VALUE = 保留低31位,清除符号位
int pos = this.position.incrementAndGet() & Integer.MAX_VALUE;
// 将无限增长的 pos 映射到有限的实例范围内
ServiceInstance instance = instances.get(pos % instances.size());
return new DefaultResponse(instance);
}
}
再来看随机策略 (RandomLoadBalancer)实现:
java
public class RandomLoadBalancer implements ReactorServiceInstanceLoadBalancer {
@Override
public Mono<Response<ServiceInstance>> choose(Request request) {
ServiceInstanceListSupplier supplier = serviceInstanceListSupplierProvider
.getIfAvailable(NoopServiceInstanceListSupplier::new);
// 通过 processInstanceResponse 进行处理
return supplier.get(request).next()
.map(serviceInstances -> processInstanceResponse(supplier, serviceInstances));
}
private Response<ServiceInstance> processInstanceResponse(ServiceInstanceListSupplier supplier,
List<ServiceInstance> serviceInstances) {
// 调用 getInstanceResponse 获取选择的实例
Response<ServiceInstance> serviceInstanceResponse = getInstanceResponse(serviceInstances);
if (supplier instanceof SelectedInstanceCallback && serviceInstanceResponse.hasServer()) {
((SelectedInstanceCallback) supplier).selectedServiceInstance(serviceInstanceResponse.getServer());
}
return serviceInstanceResponse;
}
private Response<ServiceInstance> getInstanceResponse(List<ServiceInstance> instances) {
if (instances.isEmpty()) {
if (log.isWarnEnabled()) {
log.warn("No servers available for service: " + serviceId);
}
return new EmptyResponse();
}
// ThreadLocalRandom.current(): 获取当前线程的随机数生成器
// instances.size(): 获取可用服务实例的数量
// nextInt(bound): 生成 [0, bound) 范围内的随机整数
// 生成一个介于0(包含)和instances.size()(不包含)之间的随机整数
int index = ThreadLocalRandom.current().nextInt(instances.size());
ServiceInstance instance = instances.get(index);
return new DefaultResponse(instance);
}
}