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

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

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

相关推荐
空中海19 小时前
第六篇:架构篇 — 微服务、部署、高并发与专家级能力
微服务·云原生·架构
空中海1 天前
Spring Cloud 专家级面试题库
spring·spring cloud·面试
heimeiyingwang1 天前
【架构实战】编排vs协同:微服务通信架构选型
微服务·云原生·架构
日取其半万世不竭1 天前
用 Netdata 实时监控服务器,比 Prometheus + Grafana 轻量得多
linux·服务器·网络·系统架构·负载均衡·zabbix·grafana
信徒_1 天前
负载均衡技术选型
运维·负载均衡
007张三丰1 天前
系统架构设计师范文4:论微服务架构及其应用
微服务·云原生·架构·软考·系统架构设计师
phltxy1 天前
Spring Cloud入门到实战:微服务架构一站式学习
spring cloud·微服务·架构
CDN3601 天前
DNS 负载均衡技术架构与调度策略解析
运维·架构·负载均衡
忡黑梨1 天前
eNSP_路由策略
运维·服务器·网络·华为·智能路由器·负载均衡