Skip to content
Home
Go back

Spring Cloud 负载均衡:当一个服务有很多实例

回顾:上篇做了什么

上篇我们用 Nacos 搭了注册中心,创建了 payment-serviceorder-serviceorder-service 通过 @LoadBalanced 修饰的 RestTemplate,用服务名 http://payment-service/pay 就能完成远程调用,不用写死 IP 和端口。

但我们故意留了一个尾巴:payment-service 当时只有一个实例,@LoadBalanced 的「均衡」部分根本没体现。

这篇就来补上。

单实例够用吗?

当然不够。别说双十一级别的流量,就算一个普通的生产系统,关键服务几乎都要跑多个实例:

那么问题来了:当 payment-service 有三台实例,order-service 每次调用应该选哪一台?

这就是负载均衡要解决的问题。

负载均衡的两种形态

在实际架构中它有两种落地方式:服务端负载均衡客户端负载均衡

服务端负载均衡

客户端只知道自己访问的是 api.example.com,不知道背后有几台服务器。流量到达负载均衡器(比如 Nginx、F5、ALB),由它决定转发给哪台后端。这是最常见的外部入口方案。

客户端负载均衡

客户端自己维护一份服务实例列表,每次调用时自己在列表里选一个。Spring Cloud 走的就是这条路——order-service 从 Nacos 拿到 payment-service 的所有实例,自己在本地决定发给谁。

为什么要客户端自己管?因为在微服务内部调用的场景里,如果每次都要经过一层中心化负载均衡器,那个均衡器本身就会变成瓶颈。把选择权下放到每个客户端身上,既减少了网络跳数,也消除了单点故障。

我们这篇讨论的,是客户端负载均衡。

从 Ribbon 到 Spring Cloud LoadBalancer

这又是一个「组件会迭代,思想不淘汰」的故事。

Netflix Ribbon 曾经是 Spring Cloud 客户端负载均衡的标准实现。它提供了轮询、随机、加权等策略,和 RestTemplate + @LoadBalanced 搭配得很好。但 Netflix 同样把 Ribbon 送进了维护模式,社区需要一个新的替代品。

Spring Cloud 团队自己出手,推出了 Spring Cloud LoadBalancer。它不再依赖 Netflix 的代码,而是在 Spring 体系内实现了相同的负载均衡能力。对使用者来说其实没什么感知——@LoadBalanced 注解你照用,底层是谁在执行策略相当于换了个引擎。

当前 Spring Cloud Alibaba 推荐的技术栈里,负载均衡环节就是 Spring Cloud LoadBalancer。

动手:多实例验证负载均衡

启动两个 payment-service 实例

同一个项目,通过启动参数指定不同端口。第一个实例:

mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8081"

第二个实例,换个端口:

mvn spring-boot:run -Dspring-boot.run.arguments="--server.port=8083"

为了验证负载均衡效果,我们让两个实例返回不同内容,方便区分当前打到的是哪一台。修改 PaymentController

@RestController
public class PaymentController {

    @Value("${server.port}")
    private String port;

    @GetMapping("/pay")
    public String pay() {
        return "Payment successful from port: " + port;
    }
}

观察 Nacos

两个实例都启动后,打开 Nacos 控制台 → 服务列表 → payment-service → 详情。你应该能看到两个实例:80818083,状态都是健康。

验证轮询

浏览器连续访问 http://localhost:8082/order 几次:

Order created. Payment successful from port: 8081
Order created. Payment successful from port: 8083
Order created. Payment successful from port: 8081
Order created. Payment successful from port: 8083

8081、8083 交替出现——这就是默认的轮询策略在工作。

如果你想进一步验证,可以停掉 8081 的实例,再次访问,所有请求会全部打到 8083。这说明健康检查机制在配合负载均衡:不健康的实例已被摘除,请求不会发到死地址。

核心原理简述

不用深究源码,但面试中能把原理说清楚会很加分。整个流程是这样的:

  1. RestTemplate 的拦截器里有一个 LoadBalancerInterceptor
  2. 它拦截到 http://payment-service/pay 这个请求,发现 payment-service 不是真实 IP 而是服务名
  3. 交给 LoadBalancerClient 处理:先从 Nacos 拿到 payment-service 的所有健康实例列表
  4. 再用内置的负载均衡策略(默认轮询 RoundRobinLoadBalancer)选出一个实例
  5. 把 URL 替换成真实地址 http://192.168.x.x:8081/pay,发出真正的 HTTP 请求

如果你想换策略,比如改成随机,只需注入一个 RandomLoadBalancer 的 Bean,不需要改业务代码。这正是客户端负载均衡设计得好的地方:策略和业务完全解耦。

小结与下篇预告

这一篇我们讲清了:

  1. 客户端负载均衡 vs 服务端负载均衡,以及为什么微服务内部调用适合前者
  2. Ribbon → Spring Cloud LoadBalancer 的又一次组件演进,思想始终是「选出一个可用实例」
  3. 双实例验证轮询分发,直观感受负载均衡的实际效果
  4. 原理层面:拦截器 → 服务发现 → 策略选择 → 真实请求的链路

现在 order-service 调用 payment-service 的链路已经完整了:注册中心 + 负载均衡。但代码里那个长长的 restTemplate.getForObject("http://payment-service/pay", String.class) 看着实在不够优雅。

下一篇,我们引入 OpenFeign,把远程调用写得像本地方法调用一样干净。



Previous Post
Spring Cloud OpenFeign:让远程调用像本地方法
Next Post
Spring Cloud 上手:用 Nacos 注册中心让服务找到彼此