Skip to content

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 APIGET:/api/itemsSpring MVC URL 映射自动注册
方法调用ItemClient.queryItemByIds()Feign 调用自动包装
代码片段SphU.entry("createOrder")手动定义资源
外部依赖数据库、缓存、MQ需手动声明
java
// 手动定义资源的方式
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 注解

java
// 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业务异常的回退方法
blockHandlerClassblockHandler 所在类(用于非本类方法)
fallbackClassfallback 所在类
exceptionsToTrace需要追踪的异常类(默认所有)
exceptionsToIgnore忽略的异常类(不触发 fallback)

blockHandler vs fallback 的区别

  • blockHandler:当 Sentinel 规则被触发(限流/熔断/系统保护)时调用,参数需与原始方法一致并在末尾加 BlockException
  • fallback:当业务方法抛出异常时调用,参数需与原始方法一致并在末尾加 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)

示例:异常比例熔断

json
// 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)系统负载超过阈值,且当前并发线程数 > 系统容量时触发
CPUCPU 使用率超过阈值时触发
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 实现了远程调用的容错:

java
// 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);
                // 降级:什么也不做(注意这只是兜底,业务方需要额外处理)
            }
        };
    }
}
java
// 在 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 集成

bash
# 启动 Sentinel Dashboard(在项目根目录执行)
java -jar sentinel/sentinel-dashboard.jar \
  --server.port=8090 \
  --sentinel.dashboard.auth.username=sentinel \
  --sentinel.dashboard.auth.password=sentinel
yaml
# 在每个微服务的 application-dev.yaml 中添加
spring:
  cloud:
    sentinel:
      transport:
        dashboard: localhost:8090  # Dashboard 地址
        port: 8719                 # 与 Dashboard 通信的端口(默认8719)
      eager: true                  # 应用启动时就连接 Dashboard(而不是第一次访问时)

同时,将 Sentinel 与 OpenFeign 集成:

yaml
# 开启 Feign 对 Sentinel 的支持
feign:
  sentinel:
    enabled: true

开启后,Sentinel 会自动包装所有 @FeignClient 的资源,在 Dashboard 上可以看到每个 Feign 调用的实时统计,并直接为其配置限流/熔断规则。

5.6 规则配置:Nacos 动态配置

生产环境推荐通过 Nacos 动态下发规则:

yaml
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 格式):

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
  }
]

代码方式配置(兜底):

java
@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=200Warm Up商品查询高频,Warm Up 防缓存雪崩
/orders (下单)QPS=20排队等待下单核心链路,排队削峰
/pay (支付)QPS=10排队等待支付涉及金额,稳定优先

熔断规则建议:

Feign 调用策略阈值熔断时长
ItemClient.queryItemByIds慢调用比例RT>500ms 比例>50%30秒
ItemClient.deductStock异常比例异常比例>20%10秒

常见问题:@SentinelResource 失效

java
// 错误:类内部调用,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

特性SentinelHystrix(已停更)Resilience4j
隔离方式信号量线程池/信号量信号量
限流QPS/线程数/热点/系统自适应不支持RateLimiter
熔断策略慢调用/异常比例/异常数失败比例慢调用/失败比例
监控内置 Dashboard需整合 Turbine需整合 Micrometer
动态规则Nacos/Apollo/文件文件文件
Warm Up
热点限流
实时监控控制台需集成需集成

6. 链路追踪 — Micrometer Tracing

yaml
# Spring Boot 3.x(替代 Sleuth)
management:
  tracing:
    sampling:
      probability: 1.0  # 采样率
  zipkin:
    tracing:
      endpoint: http://localhost:9411/api/v2/spans
java
// 自定义追踪
@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 三大核心角色

角色全称部署位置职责
TCTransaction Coordinator独立部署的 Seata Server维护全局事务和分支事务的状态,驱动提交/回滚
TMTransaction Manager嵌入发起全局事务的微服务定义事务边界(@GlobalTransactional),向 TC 申请开启/提交/回滚
RMResource 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_log

7.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(读已提交)隔离级别 上额外提供写隔离:

  1. Phase 1 提交前,RM 向 TC 申请"全局锁"(锁住被修改的行)
  2. 获取到全局锁后才提交本地事务
  3. 全局事务完成后释放全局锁

读隔离

默认 RC(Read Committed),但通过全局锁机制保证写入的排他性。

如果需要读未提交的全局事务中间状态,可以通过 SELECT ... FOR UPDATE 主动申请全局锁检查:

sql
-- 在本地事务中,SELECT FOR UPDATE 会触发全局锁检查
SELECT stock FROM item WHERE id = 1 FOR UPDATE;
-- 如果有全局事务正在修改该行,当前事务等待

undo_log 表结构

sql
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)是补偿型分布式事务方案,由业务方自行实现三个阶段:

三阶段接口定义

java
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);
}

典型场景:账户余额扣减

java
@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

空回滚处理示例:

java
@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;
}

幂等处理示例:

java
@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: 取消订单

          最终失败
java
// 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

对比维度SagaATTCC
一致性最终一致性强一致性强一致性
隔离性无(业务自行保证)强(全局锁)业务控制
事务时长长(分钟~小时)短(秒级)短(秒级)
性能
代码侵入
典型场景旅游预订、审批流程订单-库存-余额高并发扣减

7.8 Seata 配置与集成(Nacos + Spring Cloud)

Seata Server 独立部署(TC):

bash
# 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

微服务配置:

yaml
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  # 全局事务超时(毫秒)

完整代码示例:

java
@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 typeregistry.conf 配置错误检查 Seata Server 的配置文件
全局事务超时业务执行超过 60s调整 timeout 或拆分业务

8. 核心面试题

Sentinel 篇

1. Sentinel 的限流和熔断有几种模式?

限流模式:直接、关联、链路三种模式;熔断策略:慢调用比例、异常比例、异常数三种。 限流效果:快速失败、Warm Up、排队等待。

2. Sentinel 的滑动窗口原理是什么?为什么不用固定窗口?

固定窗口存在边界突刺问题——在窗口边界处可能短时间涌入两倍阈值的流量。滑动窗口将时间窗口划分为多个采样格子,逐格滑动,统计更加平滑精确。

3. @SentinelResource 中 blockHandler 和 fallback 的区别?

  • blockHandler:Sentinel 规则触发时调用(限流/熔断/系统保护),参数末尾加 BlockException
  • fallback:业务异常时调用,参数末尾加 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 模式的核心区别?

维度ATSaga
一致性强一致最终一致
隔离性全局锁保证写隔离无隔离,业务自行保证
事务时长短(秒级)长(分钟~小时)
补偿方式自动根据镜像逆向 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 开销巨大。使用异步批量导出避免阻塞业务线程。


参考资源

Knowledge4J — Java 知识库