负载均衡 -LoadBalance
目录
问题分析
在 服务注册与发现——Eureka-ZEEKLOG博客 中,我们根据应用名称获取了服务实例列表,并从列表中选择了一个服务实例:

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

修改端口号:


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

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

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

可以看到,多次访问的都是同一台机器,我们启动多个实例,就是希望能够减轻单机压力,也就是每个实例处理部分请求,而不是让同一台机器处理所有请求
那么,如何实现多个机器分摊负荷呢?
我们可以依次将请求分发给服务器列表中的每一台机器,因此,我们对 OrderService 中代码进行修改:
@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 注解即可
@Configuration public class BeanConfig { @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }将 IP 和端口号修改为服务名称:
@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 容器中:
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 注解配置上述随机策略:
@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 的具体实现:
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 实现:
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)实现:
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)实现:
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); } }