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 句话

  1. Nacos 负责服务发现,不负责转发业务请求。
  2. Nacos 告诉 LoadBalancer“有哪些实例”,LoadBalancer 决定“选哪个”。
  3. Feign 是声明式 HTTP 客户端,不是 Dubbo。
  4. Gateway 负责客户端到微服务,Feign 负责微服务到微服务。
  5. Sentinel 的限流解决流量过大,熔断解决下游持续异常。
  6. Seata AT 是本地提交 + undo_log + 失败补偿。
  7. Seata undo_log 和 MySQL InnoDB undo log 不是一个东西。
  8. Seata 全局回滚不是原来的数据库 ROLLBACK,而是逻辑补偿。
  9. DB + MQ 不能天然保证原子性,需要 Outbox/事务消息等可靠投递方案。
  10. 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
 ↓
必须考虑幂等

下游持续异常 / 慢调用
 ↓
熔断
 ↓
保护调用方

某个下游长期占用资源
 ↓
线程池隔离 / 舱壁
 ↓
保护其他业务

最终目的都是:

让一个局部故障尽量停留在局部,而不是扩散成整个系统的故障。


二十四、今天新增的面试重点

  1. 限流通常可以在 Gateway 和具体服务两层做。
  2. Gateway 限流保护整个系统,服务自身限流保护具体服务。
  3. 熔断通常由调用方负责,例如 Order 对 Stock 的调用进行熔断。
  4. @SentinelResource 只是定义 Sentinel Resource,不等于自动开启熔断。
  5. FlowRule 决定什么时候限流,DegradeRule 决定什么时候熔断。
  6. blockHandler 主要处理 Sentinel 规则阻断,fallback 主要处理异常/降级。
  7. Timeout 不代表下游没有执行成功,所以 Retry 必须考虑幂等。
  8. 无限 Retry 可能形成重试风暴。
  9. 服务雪崩本质上是故障通过同步调用链消耗上游线程、连接池等资源并逐级扩散。
  10. 线程池隔离的核心是隔离资源,让一个下游故障不能耗尽整个服务的线程资源。
posted @ 2026-09-28 10:55  zhangph  阅读(1)  评论(0)    收藏  举报