专栏Spring Cloud个人主页手握风云目录一、负载均衡1.1. 问题1.2. 定义1.3. 分类二、Spring Cloud LoadBalancer2.1. 快速上手2.2. 负载均衡策略2.3. 底层原理三、多机部署3.1. 前置准备3.2. Maven 项目打包3.3. 服务器 Jar 包上传与后台启动一、负载均衡1.1. 问题在前面的远程调用基础代码中程序会通过 DiscoveryClient 根据服务名称拉取完整的服务实例列表但代码逻辑固定选取列表下标为 0 的第一个实例发起请求。这种写法在服务仅部署单实例时不会出现问题可一旦同一服务启动多份实例流量会全部集中到首个实例多节点扩容的价值完全无法发挥。右键服务启动配置选择 Copy Configuration 复制配置再通过 Modify options 菜单栏添加 VM 启动参数 -Dserver.port端口号以此修改不同实例的占用端口。本次演示共启动 3 个 product-service 服务端口分别为 9090、9091、9092全部注册至 Eureka 注册中心。package com.yang.order.service; import com.yang.order.mapper.OrderMapper; import com.yang.order.model.OrderInfo; import com.yang.order.model.ProductInfo; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; Slf4j Service public class OrderService { Autowired private OrderMapper orderMapper; Autowired private RestTemplate restTemplate; Autowired private DiscoveryClient discoveryClient; private AtomicInteger count new AtomicInteger(1); private ListServiceInstance instances; PostConstruct public void init() { //从Eureka中获取服务列表 instances discoveryClient.getInstances(product-service); } public OrderInfo selectOrderById(Integer orderId) { OrderInfo orderInfo orderMapper.selectOrderById(orderId); // 通过计数器 % 实例 下标索引 int index count.getAndIncrement() % instances.size(); String uri instances.get(index).getUri().toString(); String url uri /product/ orderInfo.getProductId(); log.info(远程调用url:{}, url); ProductInfo productInfo restTemplate.getForObject(url, ProductInfo.class); orderInfo.setProductInfo(productInfo); return orderInfo; } }借助 AtomicInteger 实现原子自增计数通过计数值对实例列表长度取模循环选中不同服务实例改造代码后重新测试请求会均匀分配至三台实例直观展现出负载均衡的基础效果。1.2. 定义负载均衡英文缩写为 LB是构建高并发、高可用分布式系统不可或缺的核心组件。随着业务流量持续上涨开发者通常会通过新增服务节点完成水平扩容而负载均衡的核心作用就是按照预设规则将客户端流量合理分摊到多台服务机器避免单节点压力过载整体提升集群的处理效率。团队初期仅一名员工工作量提升后新增多名员工负载均衡就相当于分配工作的规则均衡分摊任务充分发挥团队整体处理能力和多服务实例分流流量的设计思路完全一致。1.3. 分类真实业务场景下负载均衡的分配规则十分丰富除基础轮询外还可根据服务器硬件性能分配流量硬件配置更高的节点承载更多请求。为统一标准化实现行业内衍生出两类成熟的负载均衡架构分别是服务端负载均衡与客户端负载均衡二者最核心的区分标准是服务实例清单的存储位置。第一类是服务端负载均衡典型代表工具为 Nginx。整体流程为用户请求先统一发送到独立的负载均衡中间设备由均衡器存储全部后端服务清单通过内置算法挑选节点后转发请求所有流量都需要经过这一台集中式设备中转。第二类是客户端负载均衡功能以 SDK 类库的形式集成在调用方服务内部无需独立中间设备。Spring Cloud 早期采用 Ribbon 组件实现该能力但该组件已停止维护2020.0.1 及以上新版本统一使用官方自研的 Spring Cloud LoadBalancer。执行流程为客户端先向 Eureka 注册中心拉取全部可用服务实例列表在本地运行负载均衡算法筛选目标节点之后直接与选中的服务实例建立连接发起调用。二、Spring Cloud LoadBalancer2.1. 快速上手Spring Cloud 在 2020.0.1 版本之后正式移除了原有的 Ribbon 组件改用 Spring Cloud LoadBalancer 作为官方默认的客户端负载均衡实现方案也是当前新项目统一采用的标准组件。想要快速启用该组件实现负载均衡仅需完成两步核心配置。第一步是改造 RestTemplate 的 Bean 配置在创建 RestTemplate 的方法上添加 LoadBalanced 注解该注解会开启负载均衡拦截能力为后续服务名转发提供基础支撑。第二步需要修改远程调用的请求地址摒弃硬编码的 IP 与端口直接使用注册中心内注册的服务名称拼接 URL例如把 http://127.0.0.1:9090/product/{id} 替换为 http://product-service/product/{id}。package com.yang.order.config; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.loadbalancer.core.RandomLoadBalancer; 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.core.env.Environment; public class CustomLoadBalancerConfiguration { Bean ReactorLoadBalancerServiceInstance randomLoadBalancer(Environment environment, LoadBalancerClientFactory loadBalancerClientFactory) { String name environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME); return new RandomLoadBalancer(loadBalancerClientFactory .getLazyProvider(name, ServiceInstanceListSupplier.class), name); } }package com.yang.order.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; LoadBalancerClient(name product-service, configuration CustomLoadBalancerConfiguration.class) Configuration public class BeanConfig { Bean LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }Spring Cloud LoadBalancerpackage com.yang.order.service; import com.yang.order.mapper.OrderMapper; import com.yang.order.model.OrderInfo; import com.yang.order.model.ProductInfo; import jakarta.annotation.PostConstruct; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.stereotype.Service; import org.springframework.web.client.RestTemplate; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * author gao * date 2026/7/22 11:34 */ Slf4j Service public class OrderService { Autowired private OrderMapper orderMapper; Autowired private RestTemplate restTemplate; Autowired private DiscoveryClient discoveryClient; private AtomicInteger count new AtomicInteger(1); private ListServiceInstance instances; PostConstruct public void init() { //从Eureka中获取服务列表 instances discoveryClient.getInstances(product-service); } public OrderInfo selectOrderById(Integer orderId) { OrderInfo orderInfo orderMapper.selectOrderById(orderId); String url http://product-service/product/ orderInfo.getProductId(); log.info(远程调用url:{}, url); ProductInfo productInfo restTemplate.getForObject(url, ProductInfo.class); orderInfo.setProductInfo(productInfo); return orderInfo; } }持续重复调用订单接口 http://127.0.0.1:8080/order/1 进行功能验证查看三个 product-service 实例的运行日志能够清晰发现请求会自动分发到三台不同实例负载均衡能力正式生效。2.2. 负载均衡策略负载均衡策略是一套通用的流量分配逻辑不管是服务端均衡器还是客户端均衡组件底层设计思路都高度相似。Spring Cloud LoadBalancer 内置仅提供两种原生负载均衡策略分别是轮询策略与随机策略。轮询策略 RoundRobin 是组件默认使用的分配方案逻辑为请求按照顺序轮流分配给集群内每一台可用实例实现简单、适用绝大多数常规业务场景。随机选择 Random 策略则是每次发起请求时从全部可用实例中随机挑选一台处理请求分配逻辑无固定顺序。如果业务场景需要切换为随机策略支持自定义负载均衡算法实现。首先需要新建负载均衡配置类 LoadBalancerConfig在类中注入 RandomLoadBalancer 类型的 Bean同时有两个硬性要求配置类不能标注 Configuration 注解且该类需要处于 Spring 组件扫描范围内。完成策略 Bean 编写后还需要借助 LoadBalancerClient 注解绑定策略与目标服务。该注解添加在 RestTemplate 配置类上方其中 name 属性指定需要生效负载均衡策略的服务名称configuration 属性指向自定义的策略配置类。项目中仅存在 product-service 一个服务提供者因此直接使用单服务绑定注解LoadBalancerClient即可完成配置。2.3. 底层原理Spring Cloud LoadBalancer 的核心拦截载体是 LoadBalancerInterceptor该拦截器会拦截所有通过 RestTemplate 发起的 HTTP 远程调用自动完成服务名解析、实例筛选、地址替换全流程操作。public class LoadBalancerInterceptor implements ClientHttpRequestInterceptor { private LoadBalancerClient loadBalancer; private LoadBalancerRequestFactory requestFactory; public LoadBalancerInterceptor(LoadBalancerClient loadBalancer, LoadBalancerRequestFactory requestFactory) { this.loadBalancer loadBalancer; this.requestFactory requestFactory; } public LoadBalancerInterceptor(LoadBalancerClient loadBalancer) { this(loadBalancer, new LoadBalancerRequestFactory(loadBalancer)); } public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException { URI originalUri request.getURI(); String serviceName originalUri.getHost(); Assert.state(serviceName ! null, Request URI does not contain a valid hostname: originalUri); return (ClientHttpResponse)this.loadBalancer.execute(serviceName, this.requestFactory.createRequest(request, body, execution)); } }当请求进入 intercept 拦截方法后第一步会读取请求原始 URI提取 URI 中的 Host 作为服务 ID也就是我们拼接 URL 时填写的服务名称 product-service第二步调用 LoadBalancerClient 的 execute 执行方法传入解析得到的服务 ID进入实例选择逻辑。在 BlockingLoadBalancerClient 的 execute 方法内部会先调用 choose 方法执行负载均衡筛选。程序会根据服务 ID 获取对应的负载均衡器再调用均衡器内置的 choose 方法基于预设的轮询或随机策略从 Eureka 拉取的服务实例列表中选出一台可用 ServiceInstance。如果筛选后没有找到任何可用实例程序会直接抛出无服务实例异常若成功获取有效实例则将请求 URL 中的服务名替换为实例真实 IP 与端口最终发起远程调用完成一整套客户端负载均衡流程。三、多机部署3.1. 前置准备服务器安装 MySQL导入业务初始化 SQL修改项目配置文件替换数据库连接账号、密码适配服务器 MySQL。3.2. Maven 项目打包分别对 3 个微服务打包eureka-server、order-service、product-service标准 SpringBoot Maven 打包流程。3.3. 服务器 Jar 包上传与后台启动Eureka、Order 单实例启动Product 多实例通过 --server.port端口 区分端口分别输出独立日志文件