微服务多机部署与负载均衡实战:从手写轮询到 Spring Cloud LoadBalancer 落地

微服务多机部署与负载均衡实战:从手写轮询到 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

连续请求,日志自动轮询分发。


五、负载均衡策略:轮询 &amp; 随机 &amp; 自定义

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 拦截器:

  1. 拦截 RestTemplate 请求,提取服务名(如 product-service)

  2. 从 Eureka 获取该服务的所有实例

  3. 按负载均衡策略选中一个实例

  4. 把服务名替换成真实 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

  • 请求订单接口,负载均衡正常分发


八、总结

  1. 负载均衡是微服务高并发、高可用的必备组件

  2. 客户端负载均衡:调用方本地选实例,无单点、性能好

  3. Spring Cloud LoadBalancer:@LoadBalanced 一行开启,默认轮询,支持随机 / 自定义

  4. 生产建议:

    • 禁用手写轮询,统一用官方 LoadBalancer

    • 按实例性能配置权重策略(扩展)

    • 配合健康检查,自动剔除异常实例

相关推荐
fanly113 天前
Surging AI Agent 完整产品介绍
微服务·microservice
吃饱了得干活5 天前
Spring Cloud Gateway 微服务网关:路由、断言、过滤器
java·spring cloud
蝎子莱莱爱打怪9 天前
XZLL-IM干货系列 04|Netty 长连接实战:Pipeline 怎么排、心跳怎么跳、连接怎么管
后端·微服务·面试
SamDeepThinking10 天前
Java微服务练习方式
java·后端·微服务
米丘13 天前
微前端之 Web Components 完全指南
微服务·html
霸道流氓气质16 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
慧一居士16 天前
Feign的GET请求如何传递对象参数?
java·spring cloud
我登哥MVP16 天前
SpringCloud Alibaba 核心组件解析:服务链路追踪
java·spring boot·后端·spring·spring cloud·java-ee·maven
慧一居士16 天前
SpringCloud 微服务Feigin 用的完整调用端和被调用的示例
java·spring cloud
霸道流氓气质16 天前
Spring Boot 微服务性能优化完全指南
spring boot·微服务·性能优化