微服务多机部署与负载均衡实战:从手写轮询到 Spring Cloud LoadBalancer 落地
在微服务架构里,单实例服务扛不住高并发 ,多实例部署是常态。但多实例跑起来后,请求怎么均匀分给不同机器?这就是负载均衡要解决的核心问题。
本文基于 Spring Cloud 环境,从手写轮询踩坑,到 Spring Cloud LoadBalancer 开箱即用,再到 Linux 多实例部署,带你彻底吃透客户端负载均衡。
一、为什么要做负载均衡?先看痛点
先回顾一下原生服务调用代码:
java
// 1. 根据服务名获取实例列表
List<ServiceInstance> instances = discoveryClient.getInstances("product-service");
// 2. 直接拿第一个实例
EurekaServiceInstance instance = (EurekaServiceInstance) instances.get(0);
问题很明显:
-
服务多实例部署,请求永远打在第一台机器
-
其他实例完全闲置,单节点压力巨大,容易雪崩
-
完全达不到高可用、水平扩容的目的
我们启动 3 个 product-service 实例(9090/9091/9092),连续请求订单服务,日志会发现:
Plain
LUCF: product-service:9090
LUCF: product-service:9090
LUCF: product-service:9090
所有流量都堆在一台机器,这绝对不是我们想要的。
二、手写负载均衡:最简单的轮询实现
既然默认只取第一个,那我们自己按顺序轮询不就行了?
核心思路:
-
用 AtomicInteger 保证线程安全计数
-
每次请求对实例数量取模,循环选择实例
代码改造如下:
java
@Configuration
public class BeanConfig {
// 原子计数器,保证线程安全
private static final AtomicInteger atomicInteger = new AtomicInteger(1);
private static List<ServiceInstance> instances;
// 项目启动时加载服务列表
@PostConstruct
public void init() {
instances = discoveryClient.getInstances("product-service");
}
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
// 轮询计算索引
int index = atomicInteger.getAndIncrement() % instances.size();
ServiceInstance instance = instances.get(index);
// 拼接真实地址
String url = instance.getUri() + "/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
}
再看日志,请求均匀分配:
Plain
LUCF: product-service:9091
LUCF: product-service:9090
LUCF: product-service:9092
LUCF: product-service:9091
✅ 负载均衡生效!
但手写有明显缺点:
-
代码侵入业务,冗余且难维护
-
只支持简单轮询,不支持权重、随机等策略
-
生产环境不推荐
三、负载均衡核心:服务端 vs 客户端
1. 服务端负载均衡
-
请求先到独立负载均衡器(Nginx、F5)
-
由均衡器统一分发到后端服务
-
代表组件:Nginx
2. 客户端负载均衡
-
均衡逻辑集成在调用方(客户端)
-
客户端从注册中心拉取服务列表
-
发送请求前自己选实例
-
代表组件:Spring Cloud LoadBalancer(替代 Ribbon)
两者最大区别:服务实例清单存在哪里
-
服务端:均衡器维护
-
客户端:调用方本地维护
四、Spring Cloud LoadBalancer 开箱即用
Spring Cloud 2020.0.1 后移除 Ribbon,官方主推 Spring Cloud LoadBalancer。
1. 三步快速集成
① 给 RestTemplate 加注解
java
@Configuration
public class BeanConfig {
@Bean
@LoadBalanced // 开启负载均衡
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
② 调用地址改用服务名
java
public OrderInfo selectOrderById(Integer orderId) {
OrderInfo orderInfo = orderMapper.selectOrderById(orderId);
// 不再写死 IP:端口,直接用服务名
String url = "http://product-service/product/" + orderInfo.getProductId();
ProductInfo productInfo = restTemplate.getForObject(url, ProductInfo.class);
orderInfo.setProductInfo(productInfo);
return orderInfo;
}
③ 启动多实例测试
IDEA 复制启动配置,添加 VM 参数:
Plain
-Dserver.port=9091
-Dserver.port=9092
连续请求,日志自动轮询分发。
五、负载均衡策略:轮询 & 随机 & 自定义
Spring Cloud LoadBalancer 自带两种策略:
1. 轮询(默认)
请求按顺序轮流分配,适合实例配置相近场景。
2. 随机
随机选一个实例,实现简单,流量分布较均匀。
3. 自定义策略(以随机为例)
① 定义策略配置类
java
public class LoadBalancerConfig {
@Bean
public ReactorLoadBalancer<ServiceInstance> randomLoadBalancer(
Environment environment,
LoadBalancerClientFactory factory) {
String name = environment.getProperty(LoadBalancerClientFactory.PROPERTY_NAME);
return new RandomLoadBalancer(
factory.getLazyProvider(name, ServiceInstanceListSupplier.class),
name
);
}
}
注意:不要加 @Configuration,避免被全局扫描。
② 绑定到服务
java
@Configuration
// 对 product-service 启用随机策略
@LoadBalancerClient(name = "product-service", configuration = LoadBalancerConfig.class)
public class BeanConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
六、LoadBalancer 核心原理
核心是 LoadBalancerInterceptor 拦截器:
-
拦截 RestTemplate 请求,提取服务名(如 product-service)
-
从 Eureka 获取该服务的所有实例
-
按负载均衡策略选中一个实例
-
把服务名替换成真实 IP: 端口,发起调用
源码简化流程:
java
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) {
// 1. 获取服务名
String serviceName = request.getURI().getHost();
// 2. 负载均衡选择实例
ServiceInstance instance = loadBalancer.choose(serviceName);
// 3. 替换地址并转发
return execution.execute(request, body);
}
七、Linux 环境多实例部署实战
本地测试完,上线到 Linux 服务器:
1. 服务打包
Maven 执行 package,得到 3 个 jar:
-
eureka-server.jar
-
order-service.jar
-
product-service.jar
2. 后台启动多实例
bash
# 启动注册中心
nohup java -jar eureka-server.jar > logs/eureka.log &
# 启动订单服务
nohup java -jar order-service.jar > logs/order.log &
# 启动 3 个商品服务实例
nohup java -jar product-service.jar --server.port=9090 > logs/product-9090.log &
nohup java -jar product-service.jar --server.port=9091 > logs/product-9091.log &
nohup java -jar product-service.jar --server.port=9092 > logs/product-9092.log &
3. 防火墙开放端口
以腾讯云为例:
-
进入防火墙 → 添加规则
-
协议 TCP,端口 8761,8080,9090,9091,9092
-
策略允许
4. 验证
-
访问 Eureka:http:// 服务器 IP:8761
-
查看 product-service 有 3 个实例 UP
-
请求订单接口,负载均衡正常分发
八、总结
-
负载均衡是微服务高并发、高可用的必备组件
-
客户端负载均衡:调用方本地选实例,无单点、性能好
-
Spring Cloud LoadBalancer:@LoadBalanced 一行开启,默认轮询,支持随机 / 自定义
-
生产建议:
-
禁用手写轮询,统一用官方 LoadBalancer
-
按实例性能配置权重策略(扩展)
-
配合健康检查,自动剔除异常实例
-