springcloud
Spring Cloud / 微服务学习总结
今天重点学习:Nacos、OpenFeign、Spring Cloud LoadBalancer、Gateway、Sentinel、Seata、RocketMQ。
目标不是背源码,而是理解组件之间的职责、调用链、故障处理和面试表达。
一、整体架构
先记住这条主线:
用户
↓
Nginx / SLB
↓
Gateway
↓
Sentinel
↓
LoadBalancer
↓
Nacos 服务发现
↓
Order Service
↓ Feign
Stock / Account Service
↓
Seata
↓
各自 MySQL
Order Service
↓
RocketMQ
↓
积分 / 营销 / 通知等异步业务
七个组件分别解决不同问题:
| 组件 | 核心职责 |
|---|---|
| Nacos | 服务注册、发现、配置 |
| LoadBalancer | 从多个实例中选择一个 |
| OpenFeign | 声明式 HTTP 服务调用 |
| Gateway | 外部请求统一入口、路由、过滤 |
| Sentinel | 限流、熔断、降级、热点保护 |
| Seata | 分布式事务 |
| RocketMQ | 异步解耦、削峰、可靠消息、最终一致性 |
二、Nacos
核心理解
Nacos 最重要的是:
服务注册 + 服务发现
Provider 启动后注册:
user-service
├── 10.0.0.1:8080
├── 10.0.0.2:8080
└── 10.0.0.3:8080
Consumer 通过 Nacos Client 获取服务实例信息。
最容易混淆的点
业务请求不会经过 Nacos。
不是:
Order → Nacos → User
而是:
Nacos
↓
Nacos Client 本地服务列表
↓
LoadBalancer
↓
User
Nacos 更像控制面,业务请求属于数据面。
临时实例
临时实例通过心跳维持:
Provider → heartbeat → Nacos
Provider 崩溃后,Nacos 经过检测窗口发现实例异常。
Nacos 挂了怎么办?
短时间内通常不会导致所有业务马上挂掉,因为客户端已经有本地实例信息。
但会产生:
- 新实例无法及时发现
- 已失效实例可能仍在本地列表
- 服务列表逐渐过期
因此需要结合超时、重试、熔断等机制。
三、LoadBalancer
核心就一句:
Nacos 告诉我有哪些实例,LoadBalancer 决定这次选哪个。
例如:
Nacos
↓
A / B / C
↓
LoadBalancer
↓
选择 B
LoadBalancer 通常运行在 Gateway、业务服务进程内部,不是一个单独的服务器。
Gateway 中
Gateway
↓
LoadBalancer
↓
user-service 实例
Feign 中
Feign
↓
LoadBalancer
↓
user-service 实例
所以:
Nacos → 服务发现
LoadBalancer → 实例选择
四、OpenFeign
Feign 的定位:
声明式 HTTP 客户端。
调用方只需要定义接口:
@FeignClient("user-service")
public interface UserClient {
@GetMapping("/user/{id}")
UserDTO getUser(@PathVariable("id") Long id);
}
业务代码:
userClient.getUser(100L);
背后:
Java 方法
↓
Feign Proxy
↓
LoadBalancer
↓
HTTP
↓
Provider
Feign 和 Dubbo
Feign → HTTP
Dubbo → RPC
不要把 Feign 当成 Dubbo。
DTO
服务之间传输的是 JSON 数据,不要求两边使用完全相同的 Java 类。
如果多个服务需要共享 DTO / Feign 接口,可以抽:
common-api
但不要让 Consumer 直接依赖整个 Provider 工程。
五、Gateway
Gateway 是:
整个微服务系统对外的统一入口。
主要负责:
路由
鉴权
过滤
Header
CORS
限流
日志
路径重写
Route
可以理解成:
匹配条件 + 目标服务
例如:
uri: lb://user-service
predicates:
- Path=/user/**
Predicate
负责:
请求是否匹配这条路由。
常见:
Path
Method
Header
Query
Host
Filter
负责:
对请求/响应进行处理。
例如:
认证
日志
添加 Header
StripPrefix
Gateway 集群
Gateway 本身也需要高可用:
Nginx / SLB
├── Gateway-1
├── Gateway-2
└── Gateway-3
所以实际上有两层负载均衡:
Nginx / SLB
↓
选择 Gateway
Gateway LoadBalancer
↓
选择业务服务实例
Gateway vs Feign
Gateway:
客户端 → 微服务
Feign:
微服务 → 微服务
六、Sentinel
核心:
保护系统,不让流量和故障把整个系统拖垮。
主要能力:
限流
熔断
降级
热点参数保护
系统保护
限流
请求太多
↓
超过阈值
↓
拒绝 / 排队
解决的是:
流量过大。
熔断
下游持续异常
↓
CLOSED
↓
OPEN
↓
HALF_OPEN
↓
成功 → CLOSED
失败 → OPEN
解决的是:
不要持续调用一个已经不健康的下游服务。
QPS vs 并发
QPS = 单位时间进入多少请求
并发 = 当前同时执行多少请求
多实例注意事项
如果三个 Gateway:
Gateway-1:1000 QPS
Gateway-2:1000 QPS
Gateway-3:1000 QPS
不能直接认为整个集群限制就是 1000 QPS。
本地限流和集群全局限流是两个概念。
令牌桶
今天也理解了令牌桶的核心:
固定速率产生令牌
↓
令牌进入桶
↓
请求拿令牌
↓
允许 / 拒绝
桶容量决定突发能力。
注意:
Sentinel 的 QPS 限流不能简单等同于“令牌桶”。
七、Seata
为什么需要?
单体:
一个服务
↓
一个数据库
可以:
@Transactional
微服务:
Order DB
Stock DB
Account DB
普通本地事务无法跨多个服务和数据库。
可能:
订单成功
库存成功
扣余额失败
于是需要分布式事务协调。
Seata 三个角色
TC → 总协调者
TM → 管理全局事务
RM → 管理分支事务 / 资源
可以记:
TM
↓
TC
↓
RM / RM / RM
AT 模式
这是今天重点掌握的。
业务还是正常写 SQL:
UPDATE stock
SET count = count - 1
WHERE id = 100;
Seata 会记录:
beforeImage
afterImage
并保存到:
undo_log
最关键理解
AT 不是:
所有服务一直不提交
↓
最后统一 commit
而是:
本地业务 SQL
↓
undo_log
↓
本地 COMMIT
如果最终全局失败:
读取 undo_log
↓
检查当前数据
↓
补偿回 beforeImage
所以:
AT 的全局回滚本质上是逻辑补偿,不是原来的 MySQL ROLLBACK。
Seata undo_log vs MySQL undo log
MySQL undo log
InnoDB 内部机制:
事务回滚
MVCC
Seata undo_log
Seata 自己的表:
保存 beforeImage / afterImage
用于全局回滚补偿
两个不要混。
全局锁
今天重点理解了:
MySQL 行锁
+
Seata 全局锁
MySQL 行锁:
解决本地数据库并发。
Seata 全局锁:
协调参与 Seata 的全局事务对同一资源的竞争。
不要简单理解成“一个数据只能一个事务处理”。
@GlobalLock
@Transactional
+
@GlobalLock
适用于:
本身不是全局事务,但需要遵守 Seata 全局锁的本地事务。
而:
@GlobalTransactional
是:
真正开启全局分布式事务。
脏数据 / afterImage 校验
例如:
初始:10
Seata A:
10 → 9
其他普通事务:
9 → 8
A 全局回滚
Seata 发现:
afterImage = 9
current = 8
说明数据期间被别人修改。
因此不能简单:
8 → 10
否则会覆盖其他事务的业务结果。
所以 Seata 会检测这种冲突,无法安全自动补偿时需要进一步人工处理、告警或自定义失败处理。
八、AT / TCC / XA / Saga
只记住核心思想即可。
AT
先本地提交
↓
undo_log
↓
失败后补偿
低业务侵入。
TCC
Try
Confirm
Cancel
业务自己定义正向和反向操作,侵入更高。
XA
PREPARE
↓
COMMIT / ROLLBACK
更接近数据库原生两阶段提交,但资源持有时间和性能开销更明显。
Saga
A → B → C → D
D失败
↓
补偿 C
↓
补偿 B
↓
补偿 A
适合较长的业务流程。
九、RocketMQ:今天学习到的部分
今天暂时只整理:
数据库和 MQ 的一致性 + 可靠消息投递。
后面的消费者失败、重试、死信等内容明天再继续。
DB + MQ 为什么不一致?
数据库和 MQ 不是一个原子操作。
DB 成功,MQ 失败
DB COMMIT
↓
MQ 发送失败
结果:
订单存在
消息不存在
→ 消息丢失。
MQ 成功,DB 回滚
MQ 已发送
↓
DB ROLLBACK
结果:
下游认为订单成功
但订单实际不存在
MQ 实际成功,但 Producer 没收到 ACK
Broker 已保存
↓
ACK 返回失败
↓
Producer 重试
可能:
同一消息发送两次
所以最终要考虑:
可靠投递
+
幂等消费
十、本地消息表 / Outbox
核心思想:
业务数据和消息记录放在同一个本地数据库事务中。
@Transactional
创建订单
+
写 outbox 消息
COMMIT
然后:
Outbox
↓
异步 Dispatcher
↓
RocketMQ
这样:
订单成功
+
消息发送意图一定存在
即使 MQ 暂时不可用,也可以后续重试。
Outbox 的另一个问题
可能发生:
发送 MQ 成功
↓
程序宕机
↓
还没来得及更新消息状态
恢复后再次发送。
所以:
可靠投递仍然可能产生重复消息。
因此消费者必须幂等。
十一、消费者幂等
常见方式:
业务唯一键
+
数据库唯一索引
例如:
UNIQUE KEY uk_order_id(order_id)
第一次:
orderId=10001
↓
处理成功
第二次:
orderId=10001
↓
唯一键冲突
↓
说明已经处理
更严谨的做法:
业务处理
+
消费记录
放在同一个本地事务中。
十二、RocketMQ 事务消息
RocketMQ 也可以解决:
本地事务
+
MQ 消息
的一致性问题。
大致流程:
发送事务消息
↓
执行本地事务
↓
Commit / Rollback / Unknown
如果状态不明确,Broker 可以回查本地事务状态。
十三、Seata 和 RocketMQ 不要混
这是今天最后需要形成的整体认知。
Seata
解决:
Order DB
Stock DB
Account DB
多个服务之间:
同步分布式事务一致性。
RocketMQ
解决:
订单
↓
积分
↓
营销
↓
通知
这类:
异步解耦、削峰、最终一致性。
两者不是互相替代关系。
实际项目中可以:
Seata
+
RocketMQ
一起使用。
十四、今天最应该记住的 10 句话
- Nacos 负责服务发现,不负责转发业务请求。
- Nacos 告诉 LoadBalancer“有哪些实例”,LoadBalancer 决定“选哪个”。
- Feign 是声明式 HTTP 客户端,不是 Dubbo。
- Gateway 负责客户端到微服务,Feign 负责微服务到微服务。
- Sentinel 的限流解决流量过大,熔断解决下游持续异常。
- Seata AT 是本地提交 + undo_log + 失败补偿。
- Seata undo_log 和 MySQL InnoDB undo log 不是一个东西。
- Seata 全局回滚不是原来的数据库 ROLLBACK,而是逻辑补偿。
- DB + MQ 不能天然保证原子性,需要 Outbox/事务消息等可靠投递方案。
- MQ 可靠投递最终一定要考虑重复消息和消费者幂等。
十五、最终记忆模型
不要把今天的知识记成七个孤立组件。
直接记成:
Nacos
↓
“服务在哪里?”
LoadBalancer
↓
“这次选哪个?”
Feign
↓
“怎么调用?”
Gateway
↓
“外部请求怎么进来?”
Sentinel
↓
“流量和故障怎么保护?”
Seata
↓
“多个数据库怎么保证事务一致?”
RocketMQ
↓
“后续业务怎么异步处理并保证最终一致?”
这就是今天 Spring Cloud / 微服务这一整块的核心知识骨架。
十六、服务容错:Timeout / Retry / 限流 / 熔断 / 隔离
这一部分重点理解:
限流保护被调用服务,熔断保护调用方,Timeout 防止一次调用长期占用资源,线程池隔离防止一个下游故障耗尽整个服务的线程资源。
1. Timeout
例如:
Order
↓ Feign
Stock
↓
等待超过 2 秒
↓
Timeout
注意:
调用方超时,不代表下游一定没有执行成功。
可能出现:
0s:Order 调用 Stock
2s:Order Timeout
5s:Stock 实际执行完成
因此 Timeout 和重试必须结合幂等性考虑。
2. Retry
重试适合临时性故障,例如短暂网络抖动、数据库连接瞬时失败等。
但不能无限重试:
下游故障
↓
Retry
↓
请求更多
↓
下游压力更大
↓
更多 Timeout
↓
更多 Retry
可能形成重试风暴。
因此实际使用时应该:
- 有限次数重试
- 合理退避
- 只对适合重试的异常重试
- 业务必须具备幂等性
- 配合熔断保护
十七、Sentinel:限流和熔断到底写在哪里?
核心判断:
限流:
谁需要被保护,就可以在哪里做限流。
熔断:
谁调用了不健康的下游,通常由调用方负责熔断。
典型架构:
用户
↓
Gateway
↓ 入口限流
Order Service
↓ Feign
↓ 调用方熔断
Stock Service
↓ 服务自身限流
MySQL
1. Gateway 限流
Gateway 可以作为整个系统的第一道流量防线:
大量请求
↓
Gateway
↓
限流
↓
Order Service
目的是保护整个后端系统。
2. 服务自身限流
例如 Stock 自己最多允许 100 QPS:
@SentinelResource(
value = "checkStock",
blockHandler = "blockHandler"
)
public String checkStock(Long productId) {
return "库存充足";
}
public String blockHandler(Long productId, BlockException e) {
return "库存服务繁忙,请稍后再试";
}
然后给 checkStock 配置流控规则:
资源:checkStock
规则:QPS
阈值:100
超过阈值后:
checkStock
↓
FlowRule
↓
超过 QPS 阈值
↓
BlockException
↓
blockHandler
3. Feign 调用方熔断
Order 调用 Stock:
@FeignClient(name = "stock-service")
public interface StockClient {
@GetMapping("/stock/check")
String checkStock(@RequestParam("productId") Long productId);
}
Order 可以把调用行为定义成 Sentinel Resource:
@SentinelResource(
value = "stockServiceCall",
fallback = "fallback"
)
public String checkStock(Long productId) {
return stockClient.checkStock(productId);
}
public String fallback(Long productId, Throwable e) {
return "库存服务暂时不可用,请稍后再试";
}
然后给 stockServiceCall 配置熔断规则。
例如:
资源:stockServiceCall
策略:异常比例
统计时长:10 秒
异常比例阈值:50%
熔断时长:5 秒
达到条件:
大量异常
↓
异常比例超过 50%
↓
熔断器 OPEN
↓
后续请求快速失败 / fallback
注意
@SentinelResource 不是专门负责熔断的注解。
它的作用是:
把方法声明成 Sentinel Resource,并指定规则阻断或异常发生后的处理方式。
真正决定“什么时候限流、什么时候熔断”的是 Sentinel 规则:
FlowRule
↓
流控规则
DegradeRule
↓
熔断规则
十八、blockHandler 和 fallback
可以先这样理解:
blockHandler
↓
Sentinel 规则阻断
↓
最典型:限流
fallback
↓
业务执行异常 / 降级
↓
熔断时常作为兜底
严格来说:
fallback不是“熔断器本身”,熔断器是否打开由熔断规则决定;熔断打开后可以触发 fallback。
例如:
第一次调用异常
↓
fallback
不代表此时熔断器已经 OPEN。
Sentinel 需要持续统计异常/慢调用,达到熔断规则后才进入:
CLOSED
↓
OPEN
↓
HALF_OPEN
恢复成功:
HALF_OPEN → CLOSED
继续失败:
HALF_OPEN → OPEN
十九、Sentinel 的规则类型
1. 流控规则 FlowRule
解决:
请求太多怎么办?
例如:
checkStock
QPS > 100
↓
限流
2. 熔断规则 DegradeRule
解决:
下游持续异常或者响应过慢怎么办?
常见指标:
异常比例
10 秒内调用 100 次
60 次异常
↓
异常比例 60%
如果规则:
异常比例 > 50%
则触发熔断。
异常数
例如:
10 秒内异常次数 >= 20
触发熔断。
慢调用比例
例如:
10 秒内 100 次请求
其中 60 次耗时超过 1 秒
↓
慢调用比例 60%
达到规则后触发熔断。
二十、服务雪崩
典型链路:
Order
↓
Stock
↓
MySQL
MySQL 变慢:
Stock
↓
MySQL
↓
5 秒
于是:
Order
↓
Stock
↓
等待 5 秒
如果 Order 线程池只有 200 个线程:
200 个请求
↓
200 个线程全部等待 Stock
↓
线程池耗尽
↓
新的 Order 请求无法正常处理
故障继续向上游传播:
Stock 变慢
↓
Order 调用变慢
↓
Order 线程池耗尽
↓
Order 也不可用
↓
上游服务继续受到影响
这就是典型的服务雪崩。
二十一、线程池隔离 / 舱壁模式
假设 Order Service 所有业务共用一个线程池:
Order 线程池
├── 创建订单
├── 调用 Stock
└── 查询 User
Stock 挂掉以后:
调用 Stock 大量阻塞
↓
共享线程池被占满
↓
创建订单也无法执行
↓
查询 User 也无法执行
解决方法之一是隔离资源:
Order Service
订单线程池
Stock 调用线程池
User 调用线程池
例如:
Stock 调用线程池:20 个线程
Order 核心业务线程池:10 个线程
User 调用线程池:10 个线程
Stock 调用线程池耗尽:
Stock 故障
↓
Stock 专用资源耗尽
↓
Order 核心业务仍然保留资源
这就是舱壁模式(Bulkhead)。
核心思想:
即使一个下游故障,也不要让它耗尽整个服务的线程资源。
二十二、Timeout / 限流 / 熔断 / 线程池隔离的区别
直接记:
Timeout
↓
“我最多等你多久?”
限流
↓
“我最多让多少请求进来?”
熔断
↓
“你已经不健康了,我暂时不调用你。”
线程池隔离
↓
“即使你把我的资源占满,也不能影响其他业务。”
它们可以组合使用:
请求
↓
Gateway 限流
↓
Order Service
↓
线程池隔离
↓
Feign
↓
Timeout
↓
Stock
↓
持续异常
↓
Sentinel 熔断
↓
Fallback
二十三、服务容错最终记忆模型
流量太大
↓
限流
↓
保护被调用服务
单次调用太慢
↓
Timeout
↓
释放调用方资源
临时故障
↓
有限 Retry
↓
必须考虑幂等
下游持续异常 / 慢调用
↓
熔断
↓
保护调用方
某个下游长期占用资源
↓
线程池隔离 / 舱壁
↓
保护其他业务
最终目的都是:
让一个局部故障尽量停留在局部,而不是扩散成整个系统的故障。
二十四、今天新增的面试重点
- 限流通常可以在 Gateway 和具体服务两层做。
- Gateway 限流保护整个系统,服务自身限流保护具体服务。
- 熔断通常由调用方负责,例如 Order 对 Stock 的调用进行熔断。
@SentinelResource只是定义 Sentinel Resource,不等于自动开启熔断。- FlowRule 决定什么时候限流,DegradeRule 决定什么时候熔断。
blockHandler主要处理 Sentinel 规则阻断,fallback主要处理异常/降级。- Timeout 不代表下游没有执行成功,所以 Retry 必须考虑幂等。
- 无限 Retry 可能形成重试风暴。
- 服务雪崩本质上是故障通过同步调用链消耗上游线程、连接池等资源并逐级扩散。
- 线程池隔离的核心是隔离资源,让一个下游故障不能耗尽整个服务的线程资源。

浙公网安备 33010602011771号