Spring Cloud:微服务的基础设施
难度:高级 | 预计时间:60 分钟 | 前置:Spring Boot 项目结构
📖 一个单体应用的黄昏
故事背景
你的 Spring Boot 应用从 5000 行涨到了 50000 行——每次部署要 5 分钟,改一个订单功能可能影响用户模块。团队决定"拆分"——订单服务、用户服务、商品服务各自独立部署。然后问题来了:订单服务怎么知道用户服务的地址?用户服务挂了怎么办?几十个服务之间的调用链怎么追踪?
Spring Cloud 就是来解决这些"分布式系统通用问题"的套件——服务注册与发现(Nacos/Eureka)、远程调用(Feign/OpenFeign)、负载均衡(LoadBalancer)、网关(Gateway)、配置中心(Nacos Config)。作为前端,你可以把它理解为微服务版的"基础设施即代码"。
💻 代码演示
一个最简微服务调用链——订单服务通过 Feign 调用用户服务:
// ===== 用户服务(端口 8081)=====
@SpringBootApplication
@EnableDiscoveryClient // 注册到 Nacos
public class UserServiceApp {
public static void main(String[] args) {
SpringApplication.run(UserServiceApp.class, args);
}
}
@RestController
public class UserController {
@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return new User(id, "张三", "zhang@example.com");
}
}
// ===== 订单服务(端口 8082)=====
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients // 启用 Feign 远程调用
public class OrderServiceApp {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApp.class, args);
}
}
// Feign 接口 — 声明式远程调用(像调本地方法一样调远程服务)
@FeignClient(name = "user-service") // name 对应注册中心的服务名
public interface UserClient {
@GetMapping("/users/{id}")
User getUser(@PathVariable("id") Long id);
}
// 订单 Controller — 组合订单数据 + 用户数据
@RestController
public class OrderController {
private final UserClient userClient;
public OrderController(UserClient userClient) { this.userClient = userClient; }
@GetMapping("/orders/{id}")
public OrderDetail getOrder(@PathVariable Long id) {
Order order = findOrder(id); // 本服务数据
User user = userClient.getUser(order.userId()); // 远程调用:透明!
return new OrderDetail(order, user);
}
}
// application.yml 关键配置
// spring.cloud.nacos.discovery.server-addr=localhost:8848
// 两个服务都配同一 Nacos 地址,Feign 自动发现 + 负载均衡运行输出:
$ curl http://localhost:8082/orders/1
{
"orderId": 1,
"amount": 99.00,
"user": { "id": 100, "name": "张三", "email": "zhang@example.com" }
}
// 订单服务毫不知情用户服务的实际 IP 和端口——Nacos 和 Feign 全自动处理了🎯 三个核心问题
这是什么?
Spring Cloud 是一套微服务治理工具集:Nacos(服务注册/发现 + 配置中心,替代 Eureka+Config)、OpenFeign(声明式 HTTP 客户端,像调本地接口一样调远程服务)、Spring Cloud LoadBalancer(客户端负载均衡)、Spring Cloud Gateway(API 网关,统一入口、鉴权、限流、路由)、Spring Cloud CircuitBreaker(熔断降级,Sentinel/Resilience4j)。
为什么需要它?
微服务架构解决了单体应用的痛点——独立部署(改订单服务不需要重部署用户服务)、技术栈异构(不同服务可用不同语言/数据库)、团队自治(每个团队负责自己的服务)。但引入了新的复杂度——服务发现(不能硬编码 IP)、配置管理(几十个服务的配置分散在各处)、调用链追踪(一个请求可能经过 5 个服务)。Spring Cloud 就是一套标准化的"微服务操作系统"。
如果没有它会怎样?
如果不用 Spring Cloud——你得自己实现:HTTP 客户端 + 服务列表维护 + 健康检查 + 负载均衡算法 + 失败重试 + 熔断降级 + 统一配置管理。任何一个做不好都会在生产环境翻车。Spring Cloud 把这些分布式系统的共性需求标准化,你只需要关注业务。
📝 原理讲解
Spring Cloud 核心组件速查:
Nacos 服务发现:服务启动时向 Nacos 注册("我是 user-service,IP=10.0.0.5:8081")。调用方通过服务名从 Nacos 查询到实例列表。Nacos 还会做健康检查——不健康的实例自动剔除。
OpenFeign:写一个接口 + 注解 = HTTP 客户端。Feign 自动生成代理实现类,内部用动态代理 + HttpClient。支持:请求拦截器、超时配置、编码器/解码器、失败重试。
LoadBalancer:Feign 从 Nacos 拿到 user-service 的 3 个实例后,用负载均衡算法选一个(轮询/随机/权重/最小连接数)。
Gateway:所有外部请求先到网关,网关做:①路由(/api/users/** → user-service)②鉴权(JWT 校验)③限流(每秒 1000 次)④日志(记录所有请求)。前端所有请求只打一个域名!
Sentinel:熔断——当 user-service 错误率超过 50% 时,直接返回降级数据("用户服务暂不可用"),不再发起远程调用,防止级联故障。
Nacos Config:所有服务的 application.yml 集中管理,修改后实时推送——不用重启服务。
🎨 生活类比
类比理解
Nacos 服务注册中心像是公司前台的花名册——每个员工(服务)入职时报到(注册),调岗时更新(健康检查),离职时划掉(剔除)。要找某个员工,翻花名册就知道了。 Feign 像是公司内部电话总机——你不需要知道同事的手机号(IP),直接报名字(服务名),总机帮你转接。 Gateway 像是公司大楼的保安前台——所有访客(请求)先从大门进,保安查工牌(鉴权)、告知去哪层(路由)、人多时限流(限流)、出了事故记录日志。 Sentinel 熔断像是电路保险丝——当某路电流过大(服务错误率高),保险丝自动跳闸(熔断),保护整栋楼的电路(系统)不被烧毁。
✏️ 动手练习
练习 1
本地启动 Nacos(单机模式),创建两个 Spring Boot 服务(order-service 和 product-service),用 Feign 实现 order-service 远程调用 product-service。
<details> <summary>💡 查看提示</summary>
下载 nacos-server,bin/startup.sh -m standalone。两个服务都加 spring-cloud-starter-alibaba-nacos-discovery 和 spring-cloud-starter-openfeign 依赖。
</details>
练习 2
启动两个 product-service 实例(端口不同),在 order-service 中多次调用,观察 LoadBalancer 如何在两个实例间轮询。
<details> <summary>💡 查看提示</summary>
用 --server.port=8083 启动第二个实例。在 product 的 Controller 里返回实例端口号,观察每次调用的端口是否交替变化。
</details>
练习 3
为 product-service 配置 Sentinel 熔断规则:当慢调用比例超过 50% 时触发熔断,返回降级数据。模拟延迟触发熔断,再等熔断窗口过后自动恢复。
<details> <summary>💡 查看提示</summary>
加 spring-cloud-starter-alibaba-sentinel 依赖。在 Controller 方法上加 @SentinelResource(value="xxx", fallback="fallbackMethod")。
</details>
✅ 自检站
<details> <summary><strong>服务注册与发现解决了什么问题?没有它时服务间怎么调用?</strong></summary>
没有服务发现时,服务 A 调用服务 B 需要硬编码 B 的 IP 和端口——B 扩容了要更新 A 的配置、B 挂了 A 不知道还继续发请求、B 迁移了要改所有调用方。服务注册中心让 B 主动注册自己的地址,A 通过服务名查询 B 的可用实例列表——动态、自动、高可用。
</details>
<details> <summary><strong>Feign 的工作原理是什么?为什么可以像调本地方法一样调远程服务?</strong></summary>
Feign 使用 JDK 动态代理——你定义的接口在运行时被生成一个代理对象。当你调用 userClient.getUser(1L) 时,代理拦截调用,读取 @FeignClient 和 @GetMapping 注解中的元数据(服务名、URL、参数),构建 HTTP 请求,发出去,把响应反序列化为返回值类型。整个过程对调用方透明。
</details>
<details> <summary><strong>熔断(Circuit Breaker)解决什么问题?为什么微服务架构特别需要它?</strong></summary>
微服务中一个请求可能经过 A→B→C→D 四个服务。如果 D 挂了,A/B/C 的线程都会卡住等待,线程池耗尽后整个系统雪崩。熔断器监测 D 的失败率——超过阈值时直接短路,后续请求快速失败或返回降级数据,给 D 恢复的时间。就像电路保险丝——一个插座短路,跳闸保护整栋楼。
</details>