08.3 — 熔断降级与分布式事务
核心: Sentinel 限流熔断、Micrometer Tracing 链路追踪、Seata AT/TCC/Saga 面试高频度: ⭐⭐⭐⭐
← 返回 **返回 Spring Cloud 索引**
5. 服务熔断降级 — Sentinel
Sentinel 是阿里巴巴开源的面向分布式服务架构的流量控制、熔断降级组件,被视为 Spring Cloud Circuit Breaker 规范的实现之一。相较于已停更的 Hystrix,Sentinel 提供限流 + 熔断 + 系统保护 + 实时监控的一站式解决方案。
5.1 核心概念:资源与规则
资源(Resource)
Sentinel 中一切皆资源。可以是:
| 资源类型 | 示例 | 说明 |
|---|---|---|
| HTTP API | GET:/api/items | Spring MVC URL 映射自动注册 |
| 方法调用 | ItemClient.queryItemByIds() | Feign 调用自动包装 |
| 代码片段 | SphU.entry("createOrder") | 手动定义资源 |
| 外部依赖 | 数据库、缓存、MQ | 需手动声明 |
// 手动定义资源的方式
Entry entry = null;
try {
entry = SphU.entry("createOrder"); // 定义资源名称
// 业务逻辑
} catch (BlockException ex) {
// 流控/降级触发时的处理
} finally {
if (entry != null) entry.exit();
}在 Spring Cloud 集成中,Sentinel 会自动包装 Spring MVC URL 映射、Feign 调用、RestTemplate 调用和 Gateway 路由。
规则(Rule)
规则定义了对资源的保护策略:
| 规则类型 | 维度 | 作用 |
|---|---|---|
| 流量控制规则 | QPS / 线程数 | 限制请求速率 |
| 熔断降级规则 | 异常比例 / 慢调用 / 异常数 | 熔断不稳定链路 |
| 系统保护规则 | Load / CPU / RT | 系统级自我保护 |
| 热点参数规则 | 特定参数值 | 对热点参数限流 |
| 授权规则 | 调用来源 | 黑白名单控制 |
@SentinelResource 注解
// 1. 资源定义
@Service
public class OrderService {
@SentinelResource(
value = "createOrder",
blockHandler = "createOrderBlockHandler", // 限流/降级处理
fallback = "createOrderFallback" // 业务异常处理
)
public Order createOrder(OrderRequest request) {
// 业务逻辑
return orderRepository.save(request.toOrder());
}
// blockHandler:限流/熔断时调用
public Order createOrderBlockHandler(OrderRequest request, BlockException e) {
throw new BusinessException("订单创建被限流,请稍后重试");
}
// fallback:业务异常时调用
public Order createOrderFallback(OrderRequest request, Throwable e) {
log.error("创建订单失败", e);
throw new BusinessException("订单创建失败");
}
}| 属性 | 说明 | 必填 |
|---|---|---|
value | 资源名称 | ✅ |
blockHandler | 限流/熔断降级的处理方法 | ❌ |
fallback | 业务异常的回退方法 | ❌ |
blockHandlerClass | blockHandler 所在类(用于非本类方法) | ❌ |
fallbackClass | fallback 所在类 | ❌ |
exceptionsToTrace | 需要追踪的异常类(默认所有) | ❌ |
exceptionsToIgnore | 忽略的异常类(不触发 fallback) | ❌ |
blockHandler vs fallback 的区别:
blockHandler:当 Sentinel 规则被触发(限流/熔断/系统保护)时调用,参数需与原始方法一致并在末尾加BlockExceptionfallback:当业务方法抛出异常时调用,参数需与原始方法一致并在末尾加Throwable两者可以同时存在,互不干扰。
5.2 三大核心保护能力
1. 流量控制(Flow Control)
基于令牌桶或漏桶算法,控制资源的 QPS 或并发线程数。
流控模式:
| 模式 | 说明 | 典型场景 |
|---|---|---|
| 直接 | 对当前资源直接限流 | API 接口限流 |
| 关联 | 关联资源达到阈值时,限流当前资源 | 支付链路繁忙时限流查询 |
| 链路 | 基于调用链路入口限流 | 区分 Web 入口和 MQ 入口 |
流控效果:
| 效果 | 行为 | 适合场景 |
|---|---|---|
| 快速失败 | 直接抛出 BlockException | 对延迟敏感 |
| Warm Up | 冷启动逐步提升阈值 | 系统刚启动,JIT 未预热 |
| 排队等待 | 匀速排队通过 | 消息、削峰填谷 |
示例:QPS 限流
规则:item-service 的 /items 接口 QPS 阈值 = 100
时间线:
T1: QPS=80 -> 通过
T2: QPS=120 -> 超过100的20个请求被 Block
T3: QPS=90 -> 通过2. 熔断降级(Circuit Breaking)
监控资源的调用状态,当不稳定指标达到阈值时,熔断该资源的调用,快速返回 fallback,避免级联故障。
三种熔断策略:
| 策略 | 阈值条件 | 适用场景 |
|---|---|---|
| 慢调用比例 | RT > 最大RT 的请求比例超阈值 | 依赖响应变慢 |
| 异常比例 | 异常请求占总请求比例超阈值 | 依赖不稳定 |
| 异常数 | 一分钟内异常数超阈值 | 异常频发 |
熔断器状态机:
+-----------+
| CLOSED | (关闭状态:正常调用)
+------+----+
|
失败达到阈值
|
v
+-----------+
| OPEN | (开启状态:直接熔断,走 fallback)
+------+----+
|
熔断超时(默认5秒)
|
v
+-----------+
| HALF_OPEN | (半开状态:尝试放行一个请求)
+------+----+
|
+--------+--------+
| |
成功(恢复) 失败(继续熔断)
| |
v v
CLOSED OPEN状态转换说明:
- CLOSED:正常状态,所有请求通过并统计指标
- OPEN:熔断状态,请求快速失败走 fallback,持续
timeWindow秒 - HALF_OPEN:半开状态,放行一个探测请求,成功则恢复(→CLOSED),失败则继续熔断(→OPEN)
示例:异常比例熔断
// Nacos 中的规则配置
[
{
"resource": "GET:/items",
"grade": 1, // 0=RT, 1=异常比例, 2=异常数
"count": 0.5, // 异常比例阈值 50%
"timeWindow": 10, // 熔断时长(秒)
"minRequestAmount": 5 // 触发熔断的最小请求数
}
]3. 系统自适应保护(System Protection)
从系统整体负载角度保护,结合系统 Load、CPU 使用率、入口 QPS、平均 RT、线程数五个维度。
| 维度 | 说明 |
|---|---|
| Load (仅Linux) | 系统负载超过阈值,且当前并发线程数 > 系统容量时触发 |
| CPU | CPU 使用率超过阈值时触发 |
| RT | 所有入口流量的平均 RT |
| 线程数 | 入口流量的并发线程数 |
| 入口 QPS | 所有入口流量的 QPS |
自适应公式:
系统容量 = maxQps(秒级统计的最大QPS) * minRt(秒级统计的最小RT)
系统阈值 = 系统容量 / 当前线程数
当 系统负载 > 阈值 且 当前线程数 > 系统容量 时触发保护5.3 工作机制:滑动窗口与责任链
StatisticSlot:滑动窗口统计
Sentinel 不使用简单的时间窗口计数器,而是使用滑动窗口算法进行实时统计。
滑动窗口原理:
时间窗口 = 1秒,采样窗口 = 500ms(2个格子)
格子0 [0ms ~ 500ms) 格子1 [500ms ~ 1000ms)
QPS=30 QPS=50
当前时间 = 600ms:
窗口覆盖 [100ms ~ 600ms) = 格子0(部分) + 格子1(全部)
统计 QPS = 30 + 50 = 80
当前时间 = 1200ms:
窗口覆盖 [600ms ~ 1200ms) = 格子1(部分) + 新格子0(全部)
格子0(旧数据) 被淘汰
统计 QPS = 50 + 20 = 70为什么用滑动窗口?
| 方案 | 精度 | 内存 | 毛刺 |
|---|---|---|---|
| 固定窗口(1秒) | 低 | 低 | 有(边界突刺) |
| 滑动窗口(多格子) | 高 | 中 | 无 |
| 令牌桶/漏桶 | 更高 | 高 | 无 |
固定窗口缺陷示例:
阈值=100QPS,在第999ms时来了100个请求,第1000ms时又来了100个请求
固定窗口会认为"前1秒100个ok,后1秒100个ok"
但实际是 200ms 内 200个请求,系统被打垮
滑动窗口能准确识别这种突刺责任链模式
Sentinel 对每个资源调用使用责任链模式执行一系列的 Slot:
调用 SphU.entry("resourceName")
│
▼
┌─────────────────────────────┐
│ NodeSelectorSlot │ ── 构建调用树
│ ClusterBuilderSlot │ ── 维护集群节点统计
│ StatisticSlot │ ── 实时滑动窗口统计
│ AuthoritySlot │ ── 授权规则校验(黑白名单)
│ SystemSlot │ ── 系统保护规则校验
│ FlowSlot │ ── 流量控制规则校验
│ DegradeSlot │ ── 熔断降级规则校验
│ ParamFlowSlot │ ── 热点参数限流校验
└─────────────────────────────┘
│
▼
通过 → 执行业务逻辑
被 Block → 抛出 BlockException → 触发 Fallback每个 Slot 只负责一件事,新增规则只需添加新的 Slot,扩展性极强。
5.4 Feign FallbackFactory 降级实践
项目中已通过 OpenFeign 原生降级机制 FallbackFactory 实现了远程调用的容错:
// hm-api/src/main/java/com/hmall/api/client/fallback/ItemClientFallbackFactory.java
@Slf4j
@Component
public class ItemClientFallbackFactory implements FallbackFactory<ItemClient> {
@Override
public ItemClient create(Throwable cause) {
return new ItemClient() {
@Override
public List<ItemDTO> queryItemByIds(Collection<Long> ids) {
log.error("远程调用ItemClient#queryItemByIds出现异常", cause);
return CollUtils.emptyList(); // 降级:返回空列表
}
@Override
public void deductStock(List<OrderDetailDTO> items) {
log.error("远程调用ItemClient#deductStock扣减库存失败", items, cause);
// 降级:什么也不做(注意这只是兜底,业务方需要额外处理)
}
};
}
}// 在 ItemClient 中绑定 FallbackFactory
@FeignClient(
value = "item-service",
path = "/items",
fallbackFactory = ItemClientFallbackFactory.class
)
public interface ItemClient { ... }调用链路的降级触发点:
cart-service 或 trade-service
│
├── itemClient.queryItemByIds() ← 远程调用
│ │
│ ├── 成功 → 正常返回商品数据
│ │
│ └── 失败 → FallbackFactory 捕获异常
│ ├── 打印错误日志
│ └── 返回空列表 CollUtils.emptyList()
│
└── itemClient.deductStock() ← 远程调用
│
├── 成功 → 正常扣减库存
│
└── 失败 → FallbackFactory 捕获异常
├── 打印错误日志
└── 什么也不做(仅兜底,业务上可能仍需要重试)5.5 Sentinel Dashboard 集成
# 启动 Sentinel Dashboard(在项目根目录执行)
java -jar sentinel/sentinel-dashboard.jar \
--server.port=8090 \
--sentinel.dashboard.auth.username=sentinel \
--sentinel.dashboard.auth.password=sentinel# 在每个微服务的 application-dev.yaml 中添加
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8090 # Dashboard 地址
port: 8719 # 与 Dashboard 通信的端口(默认8719)
eager: true # 应用启动时就连接 Dashboard(而不是第一次访问时)同时,将 Sentinel 与 OpenFeign 集成:
# 开启 Feign 对 Sentinel 的支持
feign:
sentinel:
enabled: true开启后,Sentinel 会自动包装所有 @FeignClient 的资源,在 Dashboard 上可以看到每个 Feign 调用的实时统计,并直接为其配置限流/熔断规则。
5.6 规则配置:Nacos 动态配置
生产环境推荐通过 Nacos 动态下发规则:
spring:
cloud:
sentinel:
datasource:
ds-flow:
nacos:
server-addr: ${NACOS_ADDR:192.168.32.91:8848}
namespace: ${NACOS_NAMESPACE}
data-id: sentinel-flow-rules
group-id: DEFAULT_GROUP
rule-type: flow
ds-degrade:
nacos:
server-addr: ${NACOS_ADDR:192.168.32.91:8848}
namespace: ${NACOS_NAMESPACE}
data-id: sentinel-degrade-rules
group-id: DEFAULT_GROUP
rule-type: degrade在 Nacos 中创建对应的 dataId(JSON 格式):
// sentinel-flow-rules (限流规则)
[
{
"resource": "GET:/items",
"limitApp": "default",
"grade": 1,
"count": 100,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
},
{
"resource": "com.hmall.api.client.ItemClient:deductStock(java.util.List)",
"limitApp": "default",
"grade": 1,
"count": 50,
"strategy": 0,
"controlBehavior": 1
}
]
// sentinel-degrade-rules (熔断规则)
[
{
"resource": "com.hmall.api.client.ItemClient:deductStock(java.util.List)",
"grade": 1,
"count": 0.3,
"timeWindow": 10,
"minRequestAmount": 5,
"statIntervalMs": 1000
}
]代码方式配置(兜底):
@Configuration
public class SentinelConfig {
@PostConstruct
public void initFlowRules() {
List<FlowRule> flowRules = new ArrayList<>();
FlowRule flowRule = new FlowRule();
flowRule.setResource("GET:/items");
flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS);
flowRule.setCount(100);
flowRule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
flowRules.add(flowRule);
FlowRuleManager.loadRules(flowRules);
List<DegradeRule> degradeRules = new ArrayList<>();
DegradeRule degradeRule = new DegradeRule();
degradeRule.setResource("deductStock");
degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO);
degradeRule.setCount(0.2);
degradeRule.setTimeWindow(10);
degradeRule.setMinRequestAmount(5);
degradeRules.add(degradeRule);
DegradeRuleManager.loadRules(degradeRules);
}
}5.7 Sentinel 最佳实践
渐进式集成路径:
Step 1: 引入依赖 + 启动 Dashboard
Step 2: 开启 Feign Sentinel 整合 (feign.sentinel.enabled=true)
Step 3: 接入 Nacos 动态配置数据源
Step 4: 关键接口添加 @SentinelResource
Step 5: 扩展到所有微服务限流规则建议:
| 接口 | 建议阈值 | 策略 | 说明 |
|---|---|---|---|
| /items (查询) | QPS=200 | Warm Up | 商品查询高频,Warm Up 防缓存雪崩 |
| /orders (下单) | QPS=20 | 排队等待 | 下单核心链路,排队削峰 |
| /pay (支付) | QPS=10 | 排队等待 | 支付涉及金额,稳定优先 |
熔断规则建议:
| Feign 调用 | 策略 | 阈值 | 熔断时长 |
|---|---|---|---|
| ItemClient.queryItemByIds | 慢调用比例 | RT>500ms 比例>50% | 30秒 |
| ItemClient.deductStock | 异常比例 | 异常比例>20% | 10秒 |
常见问题:@SentinelResource 失效
// 错误:类内部调用,AOP 不生效
@Service
public class CartServiceImpl {
public void methodA() {
this.methodB(); // 直接调用,不走代理
}
@SentinelResource("methodB")
public void methodB() { ... }
}
// 正确:注入自身代理
@Service
public class CartServiceImpl {
@Autowired
private CartServiceImpl self; // 注入代理
public void methodA() {
self.methodB(); // 走代理,规则生效
}
@SentinelResource("methodB")
public void methodB() { ... }
}Dashboard 不显示应用? 检查 transport.dashboard 配置、端口 8719 是否被占用、应用是否有实际请求(懒加载机制,首次请求后才注册)。
5.8 Sentinel vs Hystrix vs Resilience4j
| 特性 | Sentinel | Hystrix(已停更) | Resilience4j |
|---|---|---|---|
| 隔离方式 | 信号量 | 线程池/信号量 | 信号量 |
| 限流 | QPS/线程数/热点/系统自适应 | 不支持 | RateLimiter |
| 熔断策略 | 慢调用/异常比例/异常数 | 失败比例 | 慢调用/失败比例 |
| 监控 | 内置 Dashboard | 需整合 Turbine | 需整合 Micrometer |
| 动态规则 | Nacos/Apollo/文件 | 文件 | 文件 |
| Warm Up | ✅ | ❌ | ❌ |
| 热点限流 | ✅ | ❌ | ❌ |
| 实时监控 | 控制台 | 需集成 | 需集成 |
6. 链路追踪 — Micrometer Tracing
# Spring Boot 3.x(替代 Sleuth)
management:
tracing:
sampling:
probability: 1.0 # 采样率
zipkin:
tracing:
endpoint: http://localhost:9411/api/v2/spans// 自定义追踪
@Service
public class TraceService {
@Autowired
private Tracer tracer;
public void processOrder(Long orderId) {
// 创建自定义 Span
Span span = tracer.nextSpan().name("processOrder").start();
try (Tracer.SpanInScope scope = tracer.withSpan(span)) {
span.tag("orderId", String.valueOf(orderId));
// 业务逻辑
} finally {
span.end();
}
}
}核心概念
| 概念 | 说明 |
|---|---|
| Trace | 一次请求的全链路,由唯一的 Trace ID 标识 |
| Span | 一个服务调用的耗时单元,包含 Span ID 和 Parent Span ID |
| Span Context | 跨服务传递 Trace 信息的载体 |
| Sampling | 采样率控制,生产环境通常 0.01~0.1 |
自动埋点机制
Micrometer Tracing 通过以下方式自动实现跨服务传递 Trace ID:
HTTP 请求到达 Gateway
│
├── Gateway 生成 Trace ID(若没有)
├── 通过 HTTP Headers 传递:traceparent / tracestate
│
▼
trade-service 收到请求
│
├── 解析 traceparent header 提取 Trace ID
├── 创建新的 Span,设置 Parent Span ID
├── 记录请求耗时
│
▼
item-service 收到请求(Feign 内部自动传递)
│
├── OpenFeign 的 TracingInterceptor 自动注入 Header
├── 继续创建子 Span
└── 完成后上报所有 Span 到 Zipkin
最终在 Zipkin UI 上可以看到完整的调用链路:
Gateway → trade-service → item-service
总耗时 = Span1 + Span2 + Span3 的时间线叠加生产环境注意事项
| 关注点 | 建议 | 原因 |
|---|---|---|
| 采样率 | 0.01 ~ 0.1(1%~10%) | 全量采样(1.0)在高 QPS 下性能开销大 |
| Span 导出 | 异步 + 批量 | 使用 AsyncReporter 避免阻塞业务线程 |
| 存储 | Zipkin + Elasticsearch | 内存存储不持久,ES 可支撑海量数据 |
| 敏感信息 | 过滤 Span Tag | 避免将用户 ID、手机号等写入 Span 标签 |
7. 分布式事务 — Seata
Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务解决方案,提供 AT、TCC、Saga 三种模式,覆盖从短事务到长业务流的全部场景。
7.1 三种模式概览
| 模式 | 性能 | 一致性 | 隔离性 | 代码侵入 | 典型场景 |
|---|---|---|---|---|---|
| AT 模式 | 中 | 强一致性 | 强(全局锁 + 行锁) | 低(一个注解) | 简单 CRUD,短事务,推荐优先使用 |
| TCC 模式 | 高 | 强一致性 | 业务控制 | 高(三个接口) | 高并发,对性能要求极高 |
| Saga 模式 | 高 | 最终一致性 | 弱(无隔离) | 中(状态机/注解) | 长业务流程,编排或 choreography |
7.2 三大核心角色
| 角色 | 全称 | 部署位置 | 职责 |
|---|---|---|---|
| TC | Transaction Coordinator | 独立部署的 Seata Server | 维护全局事务和分支事务的状态,驱动提交/回滚 |
| TM | Transaction Manager | 嵌入发起全局事务的微服务 | 定义事务边界(@GlobalTransactional),向 TC 申请开启/提交/回滚 |
| RM | Resource Manager | 嵌入所有参与事务的微服务 | 执行本地 SQL,生成前后镜像,向 TC 注册分支事务 |
┌──────────────────────┐
│ TC │
│ Seata Server │
│ 协调全局事务提交/回滚 │
└──────────┬───────────┘
│
┌─────────────────────────┼─────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ TM(事务管理器) │ │ RM(资源管理器) │ │ RM(资源管理器) │
│ trade-service │ │ item-service │ │ user-service │
│ @GlobalTrans. │ │ 扣减库存 + undo │ │ 扣减余额 + undo │
└─────────────────┘ └─────────────────┘ └─────────────────┘7.3 AT 模式原理(两阶段提交)
Phase 1:执行并记录回滚日志
@GlobalTransactional 方法被调用
│
▼
RM 收到 SQL 执行请求
│
├── 1. Seata 代理 DataSource 拦截 Connection
├── 2. 解析 SQL,生成 **前置镜像** (Before Image)
│ └─ SELECT original_data FROM table WHERE conditions
├── 3. 执行业务 SQL(UPDATE / INSERT / DELETE)
├── 4. 生成 **后置镜像** (After Image)
│ └─ SELECT new_data FROM table WHERE conditions
├── 5. 写入 UNDO_LOG 表
│ └─ INSERT INTO undo_log (xid, branch_id, before_image, after_image)
└── 6. 提交本地事务
└─ 业务数据变更 + undo_log 在**同一个本地事务**中提交关键:业务 SQL 和 undo_log 在同一本地事务写入,保证要么都成功要么都失败。
Phase 2:全局提交或回滚
全局提交(正常返回):轻量级,只需异步清理 undo_log
TM 通知 TC → TC 通知 RM → RM 异步删除 undo_log全局回滚(异常时):基于镜像自动逆向补偿
TM 检测异常 → TC 通知 RM
│
├── 1. 根据 XID + BranchID 查询 undo_log
├── 2. **校验后置镜像** ← 核心!
│ └─ 当前数据 == After Image ?
│ ├─ 是 → 继续回滚
│ └─ 否 → **脏写检测!** → 报警 + 人工介入
├── 3. 根据前置镜像生成逆向 SQL
│ └─ 如 After: stock=9, Before: stock=10 → UPDATE stock=10
├── 4. 执行逆向 SQL,恢复数据
└── 5. 删除 undo_log7.4 After Image 校验(⭐ 面试重点)
为什么需要? 防止回滚时覆盖其他事务已提交的修改(脏写)。
场景还原:
① Seata事务A扣库存:stock 10 → 9(生成 Before=10, After=9)
② 事务A提交,释放全局锁
③ 事务B(非Seata)扣库存:stock 9 → 4
④ TC决定回滚事务A
⑤ 发现:当前值=4 ≠ After Image=9 → **脏写!** → 不自动回滚不校验的后果:
- 强行回滚到 stock=10 → 事务B的扣减被覆盖 → 库存超卖
- 或强行回滚余额 → 其他消费记录丢失 → 资金损失
两道防线:
| 防线 | 防护目标 | 作用 |
|---|---|---|
| 全局锁(第1道) | 全局事务 vs 全局事务 | 防止并发全局事务脏写 |
| After Image(第2道) | 全局事务 vs 所有事务 | 最后一道防线,发现脏写 |
7.5 全局锁与隔离性
写隔离
Seata 在 RC(读已提交)隔离级别 上额外提供写隔离:
- Phase 1 提交前,RM 向 TC 申请"全局锁"(锁住被修改的行)
- 获取到全局锁后才提交本地事务
- 全局事务完成后释放全局锁
读隔离
默认 RC(Read Committed),但通过全局锁机制保证写入的排他性。
如果需要读未提交的全局事务中间状态,可以通过 SELECT ... FOR UPDATE 主动申请全局锁检查:
-- 在本地事务中,SELECT FOR UPDATE 会触发全局锁检查
SELECT stock FROM item WHERE id = 1 FOR UPDATE;
-- 如果有全局事务正在修改该行,当前事务等待undo_log 表结构
CREATE TABLE `undo_log` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`branch_id` BIGINT(20) NOT NULL,
`xid` VARCHAR(100) NOT NULL,
`rollback_info` LONGBLOB NOT NULL, -- 序列化的前后镜像
`log_status` TINYINT(4) NOT NULL, -- 0=正常, 1=已回滚, 2=回滚失败
`log_created` DATETIME NOT NULL,
`log_modified` DATETIME NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`, `branch_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;每个参与 AT 模式的数据库都必须有 undo_log 表!缺失会导致:
Table 'xxx.undo_log' doesn't exist→ 全局事务提交失败
7.6 TCC 模式详解
TCC(Try-Confirm-Cancel)是补偿型分布式事务方案,由业务方自行实现三个阶段:
三阶段接口定义
public interface TccAction<T> {
/** Try:预留业务资源 */
boolean try(BusinessContext ctx, T param);
/** Confirm:确认执行业务(幂等) */
boolean confirm(BusinessContext ctx, T param);
/** Cancel:取消执行业务/释放预留资源(幂等) */
boolean cancel(BusinessContext ctx, T param);
}典型场景:账户余额扣减
@Service
public class AccountTccActionImpl implements AccountTccAction {
@Override
@TwoPhaseBusinessAction(name = "deductAccount", commitMethod = "confirm", rollbackMethod = "cancel")
public void try(@BusinessActionContextParameter(paramName = "userId") Long userId,
@BusinessActionContextParameter(paramName = "amount") Integer amount) {
// Try:冻结余额,非直接扣减
String sql = "UPDATE account SET frozen = frozen + ? WHERE id = ? AND balance - frozen >= ?";
// 如果余额不足,抛出异常触发 Cancel
}
@Override
public boolean confirm(BusinessActionContext context) {
// Confirm:真正扣减余额(将冻结资金扣除)
// 幂等:如果已执行,则直接返回 true
String sql = "UPDATE account SET balance = balance - frozen, frozen = 0 WHERE id = ?";
return true;
}
@Override
public boolean cancel(BusinessActionContext context) {
// Cancel:释放冻结资金
// 幂等:如果已执行,则直接返回 true
String sql = "UPDATE account SET frozen = frozen - ? WHERE id = ?";
return true;
}
}TCC 的三大问题(高频面试)
| 问题 | 场景 | 解决方案 |
|---|---|---|
| 空回滚 | Try 未执行却调用了 Cancel(如网络超时 Try 已成功但 TM 认为失败) | Cancel 方法中允许空回滚:判断是否有 Try 记录,没有则直接返回成功 |
| 幂等 | Confirm/Cancel 可能被重复调用(网络重试、TC 重试) | 每个事务记录执行状态,重复调用直接返回成功 |
| 悬挂 | Cancel 比 Try 先执行(网络延迟) | Try 执行前检查是否已有 Cancel 记录,若有则不执行 Try |
空回滚处理示例:
@Override
public boolean cancel(BusinessActionContext context) {
// 1. 查询分支事务记录
TccRecord record = tccRecordMapper.selectByXidAndBranchId(
context.getXid(), context.getBranchId()
);
// 2. 如果没有 Try 记录,说明是空回滚,直接返回成功
if (record == null) {
log.warn("空回滚:XID={}", context.getXid());
return true;
}
// 3. 有 Try 记录,正常释放资源
String sql = "UPDATE account SET frozen = frozen - ? WHERE id = ?";
// 4. 标记已回滚
record.setStatus(2);
return true;
}幂等处理示例:
@Override
public boolean confirm(BusinessActionContext context) {
// 1. 使用分布式锁或数据库唯一键防止重复
// 2. 检查当前分支事务状态
TccRecord record = tccRecordMapper.selectByXidAndBranchId(
context.getXid(), context.getBranchId()
);
if (record != null && record.getStatus() == 1) {
// 已确认过,直接返回
return true;
}
// 3. 执行业务逻辑
// ...
// 4. 更新状态为已确认
record.setStatus(1);
return true;
}AT vs TCC 核心区别
| 维度 | AT 模式 | TCC 模式 |
|---|---|---|
| 代码侵入 | 低(一个注解) | 高(三个接口:Try/Confirm/Cancel) |
| 回滚机制 | 自动(基于 SQL 镜像) | 手动实现 Cancel 方法 |
| 隔离性 | 强(全局锁) | 业务方自行保证 |
| 性能 | 中(有锁开销) | 高(无锁) |
| 空回滚 | 不存在 | 需要处理(Try 没执行却调了 Cancel) |
| 幂等 | 自动保证 | 需自己实现 Confirm/Cancel 幂等 |
| 悬挂 | 不存在 | 需要处理(Cancel 先于 Try 执行) |
| 适用场景 | CRUD 操作,简单业务 | 复杂业务,需自行控制资源预留 |
7.7 Saga 模式详解
Saga 模式适用于长业务流程(可能包含几十个步骤),通过正向补偿来实现最终一致性。
两种实现模式
| 模式 | 工作机制 | 优点 | 缺点 |
|---|---|---|---|
| Choreography( choreography) | 每个服务监听事件并执行本地事务,失败时发布补偿事件 | 去中心化,简单 | 业务流程隐含在事件中,难以追踪 |
| Orchestration(编排) | 中央协调器编排每一步,失败时按逆序调用补偿 | 可视化流程,易于管理 | 协调器单点 |
Orchestration 模式状态机
┌──────────┐
│ START │
└────┬─────┘
│
Step 1: 创建订单
│
┌────┴────┐
│ │
成功↙ ↘失败
│ │
Step 2: 扣库存 补偿1: 取消订单
│ │
成功↙ 最终失败
│
Step 3: 扣余额
│
┌───┴───┐
│ │
成功↙ ↘失败
│ │
完成 补偿2: 恢复库存
│
补偿1: 取消订单
│
最终失败// Seata Saga 注解方式
@Service
public class OrderSagaService {
@Saga(start = true) // 标记 Saga 起始点
public void createOrderSaga(OrderRequest request) {
// Step 1: 创建订单
orderService.create(request);
// Step 2: 扣库存(失败时自动调用补偿方法)
inventoryService.deduct(request.getItemId(), request.getQuantity());
// Step 3: 扣余额(失败时自动调用补偿方法)
accountService.debit(request.getUserId(), request.getAmount());
}
// 补偿方法
@Compensate(forAction = "createOrderSaga")
public void cancelOrder(OrderRequest request) {
orderService.cancel(request.getOrderId());
}
}Saga vs AT vs TCC
| 对比维度 | Saga | AT | TCC |
|---|---|---|---|
| 一致性 | 最终一致性 | 强一致性 | 强一致性 |
| 隔离性 | 无(业务自行保证) | 强(全局锁) | 业务控制 |
| 事务时长 | 长(分钟~小时) | 短(秒级) | 短(秒级) |
| 性能 | 高 | 中 | 高 |
| 代码侵入 | 中 | 低 | 高 |
| 典型场景 | 旅游预订、审批流程 | 订单-库存-余额 | 高并发扣减 |
7.8 Seata 配置与集成(Nacos + Spring Cloud)
Seata Server 独立部署(TC):
# 1. 下载 Seata Server
# 2. 修改 conf/registry.conf 注册到 Nacos
registry {
type = "nacos"
nacos {
application = "seata-server"
serverAddr = "192.168.32.91:8848"
group = "DEFAULT_GROUP"
}
}
config {
type = "nacos"
nacos {
serverAddr = "192.168.32.91:8848"
}
}
# 3. 启动 Seata Server
sh bin/seata-server.sh -p 8091 -h 192.168.32.91微服务配置:
seata:
registry: # TC 服务发现配置
type: nacos
nacos:
server-addr: 192.168.32.91:8848
group: DEFAULT_GROUP
application: seata-server # TC 在 Nacos 中的服务名
tx-service-group: hmall # 事务组名称
service:
vgroup-mapping:
hmall: "default" # 事务组 -> TC 集群映射
client:
tm:
default-global-transaction-timeout: 60000 # 全局事务超时(毫秒)完整代码示例:
@GlobalTransactional // Seata 全局事务
@Transactional(rollbackFor = Exception.class) // Spring 本地事务
public void createOrder(OrderFormDTO orderFormDTO) {
// 1. 保存订单(本地 DB)
save(order);
// 2. 保存订单详情(本地 DB)
detailService.saveBatch(details);
// 3. 远程调用 cart-service: 清理购物车
cartClient.removeByItemIds(itemIds);
// 4. 远程调用 item-service: 扣减库存
itemClient.deductStock(detailDTOS);
// 任一步骤失败,全部回滚
}@Transactional 和 @GlobalTransactional 为什么必须同时使用?
@Transactional管理本地多条 SQL 作为一个事务@GlobalTransactional管理跨服务的分布式事务边界- 没有
@Transactional,每条 SQL 自动提交,前后镜像不完整——Seata 拦截的是 Connection,如果第一条 SQL 就自动提交了,镜像无法在同一个事务中写入 undo_log
7.9 异常处理与常见问题
回滚触发方式
三种触发回滚的模式:
| 模式 | 代码示例 | 说明 |
|---|---|---|
| 直接抛出 | throw new BizIllegalException("...") | 业务校验失败 |
| Catch-重抛 | catch(e){ throw new RuntimeException("库存不足") } | 包装 Feign 异常 |
| 故意异常 | int i = 1/0 | 测试回滚 |
Seata 重试机制
| 场景 | 重试次数 | 策略 |
|---|---|---|
| 提交失败 | 5 次 | 指数退避重试 |
| 回滚失败 | 6 次 | 指数退避重试 |
最终重试耗尽仍未成功 → 写入异常表,人工介入处理。
运行时问题排查
| 问题现象 | 原因 | 解决方案 |
|---|---|---|
Table 'xxx.undo_log' doesn't exist | 数据库未创建 undo_log 表 | 每个 RM 数据库执行建表 DDL |
no available service found in cluster 'default' | Seata Server 未启动 | 启动 TC 或检查 Nacos 注册 |
| 全局事务不回滚 | 异常被 try-catch 吞掉 | 确保异常从 @GlobalTransactional 方法抛出 |
| XID 无法传递 | Feign 拦截器配置问题 | 检查 spring-cloud-starter-openfeign 依赖 |
could not find registry of type | registry.conf 配置错误 | 检查 Seata Server 的配置文件 |
| 全局事务超时 | 业务执行超过 60s | 调整 timeout 或拆分业务 |
8. 核心面试题
Sentinel 篇
1. Sentinel 的限流和熔断有几种模式?
限流模式:直接、关联、链路三种模式;熔断策略:慢调用比例、异常比例、异常数三种。 限流效果:快速失败、Warm Up、排队等待。
2. Sentinel 的滑动窗口原理是什么?为什么不用固定窗口?
固定窗口存在边界突刺问题——在窗口边界处可能短时间涌入两倍阈值的流量。滑动窗口将时间窗口划分为多个采样格子,逐格滑动,统计更加平滑精确。
3. @SentinelResource 中 blockHandler 和 fallback 的区别?
blockHandler:Sentinel 规则触发时调用(限流/熔断/系统保护),参数末尾加BlockExceptionfallback:业务异常时调用,参数末尾加Throwable- 两者可以共存,互不干扰
4. Sentinel 如何实现动态规则配置?
通过 Nacos(或 Apollo/文件)数据源,Sentinel 监听配置变更自动热加载规则。支持流量控制、熔断降级、系统保护等多种规则类型的动态下发。
5. Sentinel 的责任链模式是怎样的?
资源调用经过 NodeSelectorSlot → ClusterBuilderSlot → StatisticSlot → AuthoritySlot → SystemSlot → FlowSlot → DegradeSlot → ParamFlowSlot,每个 Slot 负责一个维度的检查,通过则继续,被 Block 则抛出 BlockException 触发 Fallback。
Seata 篇
6. Seata AT 模式和 TCC 模式的核心区别?TCC 需要解决什么问题?
- AT:自动镜像回滚、低侵入、强隔离(全局锁)
- TCC:手动 try/confirm/cancel、高性能、需处理空回滚/幂等/悬挂三大问题
- 选择依据:简单 CRUD 用 AT,高并发复杂业务用 TCC
7. Seata AT 模式为什么需要 After Image 校验?不校验会怎样?
- 防止回滚时覆盖其他事务已提交的修改(脏写)
- 不校验会导致强行回滚覆盖事务 B 的扣减 → 库存超卖或资金损失
- 这是全局锁之后的第二道防线,保护全局事务 vs 所有事务(包括非 Seata 事务)
8. @GlobalTransactional 和 @Transactional 为什么要同时使用?
@Transactional管理本地多条 SQL 的一致性(同一 Connection 事务)@GlobalTransactional管理跨服务的分布式事务边界- 没有
@Transactional,每条 SQL 自动提交,前后镜像不完整——Seata 无法在同一个事务中写入 undo_log
9. Seata Saga 模式和 AT 模式的核心区别?
维度 AT Saga 一致性 强一致 最终一致 隔离性 全局锁保证写隔离 无隔离,业务自行保证 事务时长 短(秒级) 长(分钟~小时) 补偿方式 自动根据镜像逆向 SQL 手动编写补偿逻辑 适用场景 短事务 CRUD 长业务流程(旅游预订、审批)
10. TCC 的空回滚、幂等、悬挂分别指什么?如何解决?
- 空回滚:Try 未执行却调了 Cancel → Cancel 中判断无 Try 记录则直接成功
- 幂等:Confirm/Cancel 被重复调用 → 记录状态,重复调用直接返回
- 悬挂:Cancel 先于 Try 执行 → Try 执行前检查是否有 Cancel 记录,有则跳过
11. Seata 全局锁如何保证写隔离?
- Phase 1 提交前,RM 向 TC 申请全局锁,锁住被修改的行
- 获取到全局锁后才提交本地事务
- 全局事务完成后释放
- 并发全局事务访问同一行时排队等待
链路追踪篇
12. Sleuth(或 Micrometer Tracing)如何实现跨服务追踪?
- 通过 HTTP Header 传递(
traceparent/tracestate)- 每个服务从 Header 提取 Trace ID,创建子 Span
- Feign/ RestTemplate 自动通过拦截器注入 Header
- 最终所有 Span 上报到 Zipkin 拼接完整调用链
13. 生产环境下 Tracing 的采样率如何设置?为什么?
建议 0.01 ~ 0.1(1%~10%),全量采样(1.0)在高 QPS 下的 Span 上报 IO 开销巨大。使用异步批量导出避免阻塞业务线程。
参考资源
- Sentinel 官方文档:https://sentinelguard.io/
- Seata 官方文档:https://seata.apache.org/
- 项目知识文件:**sentinel-service-protection**
- 返回 **Spring Cloud 索引**