Lecture10-SpringCloud-Seata分布式事务

文章目录

    [toc]

    单词词性音标含义
    transactionn./trænˈzækʃn/事务(一组要么全部成功、要么全部失败的操作)
    distributedadj./dɪˈstrɪbjuːtɪd/分布式的(跨多个服务/数据库,不再局限于单库单表)
    consistencyn./kənˈsɪstənsi/一致性(所有参与方数据是否都保持一致)
    compensatev./ˈkɒmpenseɪt/补偿(回滚或反向操作,把已提交的部分恢复一致)
    globaladj./ˈɡləʊbl/全局的(这里的"全局事务"指跨多个本地事务的整体事务)

    Lecture10-SpringCloud-Seata分布式事务

    目标:定性理解为什么微服务会面临"分布式事务"问题,掌握 CAP/BASE 基本思想,了解 Seata 的 AT/TCC/XA/SAGA 四种模式与适用场景。本节不搭建、不实操、不演示。

    一、理论概念

    1、单库事务 vs 分布式事务

    单库事务(本班已学):一个数据库内,@Transactional 保证 ACID(原子/一致/隔离/持久)。底层执行时,能够保证使用同一个数据库连接:Connection 对象

    下单 = 扣库存 + 写订单表   (同一个数据库,一个事务搞定)
    

    分布式事务:服务拆了、数据库也拆了,"扣库存"在库存库、"写订单"在订单库,不在同一个事务里了:

    order-service  扣库存(库存库)┐  各自独立事务,如何保证一起成功/一起失败?
    product-service 写订单(订单库)┘
    

    如果扣库存成功、写订单失败,就会出现"钱扣了、单没下成"的不一致。这就是分布式事务要解决的问题。

    由于它们分属于不同微服务、不同数据库,所以无法纳入同一个传统数据库事务来完成;

    把多个操作纳入一个逻辑上的整体这个需求仍然是刚性存在的,所以呼唤新的技术手段!


    2、两个基础理论:CAP 与 BASE

    1、CAP 定理

    分布式系统在**强一致性(C)、可用性(A)、分区容忍性(P)**三选二,P(分区容忍性)必须保留,实际是在 C 和 A 之间取舍。

    • 强一致性(C):通过加锁保证数据的计算哪怕是在高并发情况下仍然正确
    • 可用性(A):系统对外有较好的性能表现,强调并发请求高效处理,天然排斥加锁
    • 分区容错性(P):整个项目即使局部故障,也仍然保证整个项目整体上是可用的
    组合含义代表
    CP强一致,网络分区时牺牲部分可用ZooKeeper、etcd
    AP高可用,分区时允许暂时不一致Eureka、多数业务系统
    单点 CA不分区才能同时满足单机数据库

    微服务注册中心(Nacos 等)多是 AP(先保证可用,节点间最终一致)。业务系统则普遍采用"最终一致"而非强一致。


    2、BASE 思想

    BASE = Basically Available(基本可用)+ Soft state(软状态)+ Eventually consistent(最终一致)。

    核心:不强求"任何时刻都一致",而是保证最终一致——中间允许短暂不一致,通过补偿/重试等机制最终对齐。

    一句话:CAP 告诉你"必须取舍",BASE 告诉你"选最终一致也能用"。 Seata 就是实现"最终一致"的工具。


    3、分布式事务的经典解法:2PC / 3PC / XA / TCC

    在讲 Seata 之前,先认识几个"前辈"——分布式事务领域几十年沉淀下来的经典协议。Seata 的四种模式,本质上都是这些思路的工程化版本。

    1、两阶段提交(2PC, Two-Phase Commit)

    最经典的分布式事务协议,把"提交"拆成两步:

    阶段一:准备(Prepare)
      协调者问所有参与者:"你能提交吗?"
      各参与者执行本地事务,但先不真正提交,锁住资源,回复 Yes / No
    
    阶段二:提交或回滚
      全部 Yes → 协调者通知所有参与者正式提交
      任一  No → 协调者通知所有参与者回滚
    
    协调者 ──Prepare──> 参与者A(执行+锁资源)──Yes──>
           ──Prepare──> 参与者B(执行+锁资源)──Yes──>
           ──Commit──> 参与者A、B(正式提交)
    

    三个天生的问题:

    问题说明
    同步阻塞阶段一到阶段二之间,资源被锁住,其他请求只能等,整体执行效率低
    协调者单点故障协调者挂了,参与者一直持有锁、阻塞等待
    数据不一致阶段二网络分区,部分参与者收到 commit、部分没收到

    2、三阶段提交(3PC, Three-Phase Commit)

    针对 2PC 的阻塞问题做改进:

    • 把"准备"拆成 CanCommit → PreCommit → DoCommit 三步
    • 参与者加了超时机制:等不到协调者指令,超时后自动提交(减少永久阻塞)
    CanCommit(问能不能)→ PreCommit(锁资源、记日志)→ DoCommit(正式提交)
    

    3PC 改善了阻塞,但网络分区时仍可能不一致,工程上很少真用——它更多是个理论演进的中间产物。


    3、XA 协议

    XA 是 X/Open 组织定义的数据库层面的分布式事务工业标准。

    • 关系:2PC 是"思想",XA 是这个思想的标准实现规范
    • MySQL、Oracle、PostgreSQL 都原生支持 XA
    • Java 里的 JTA(Java Transaction API)就是对接 XA 的接口
    Java 应用 ──JTA──> XA 协议 ──> MySQL 1
                              ──> MySQL 2
    

    Seata 的 XA 模式,本质就是让 Seata 直接调用数据库原生的 XA 接口,不自己造轮子。


    4、TCC(Try-Confirm-Cancel)

    把两阶段从"数据库层"上移到"业务层",由业务自己写三个方法:

    阶段做什么例子(转账)
    Try尝试执行,预留资源把 A 账户的 100 元"冻结"(不真扣)
    Confirm确认执行,真正提交冻结的 100 元真正扣掉,加到 B 账户
    Cancel取消执行,释放预留解冻,把 100 元退回 A 账户

    和 2PC 的关键区别:

    2PC / XATCC
    锁在哪数据库行锁(真锁)业务层"冻结"(逻辑上的锁)
    谁写代码数据库自己处理业务方手写 Try/Confirm/Cancel 三个方法
    侵入性低高
    性能锁持有期间阻塞不锁数据库行,并发好

    5、四者关系一图流

    层次锁资源阻塞侵入性实际用在哪
    2PC协议思想数据库行锁严重低理论基础
    3PC2PC 改进数据库行锁改善低几乎不用
    XA数据库标准数据库行锁严重低Seata XA 模式
    TCC业务层业务冻结不锁高Seata TCC 模式

    过渡到 Seata:Seata 的 AT 模式其实是 2PC 的"改良版"——它用 UNDO_LOG 代替真锁,阶段一就提交本地事务(不阻塞别人),失败时再按日志反向“回滚“。这就是为什么 AT 性能比 XA 好、又比 TCC 侵入性低。


    二、具体产品

    1、Seata 是什么

    Seata(Simple Extensible Autonomous Transaction Architecture)是阿里巴巴开源的分布式事务框架。核心角色:

                ┌──────────────┐
                │  TC 事务协调器 │  负责全局事务的提交/回滚决策
                └──────┬───────┘
            ┌──────────┼──────────┐
            ▼          ▼          ▼
       ┌────────┐ ┌────────┐ ┌────────┐
       │ TM 事务 │ │ RM 资源 │ │ RM 资源 │
       │ 管理器  │ │ 管理器  │ │ 管理器  │
       │(发起全局│ │(负责某库│ │(负责某库│
       │ 事务)   │ │ 的分支) │ │ 的分支) │
       └────────┘ └────────┘ └────────┘
    
    • TM(事务管理器):发起全局事务、决定提交/回滚。
    • TC(事务协调器):全局协调,维护全局事务状态。
    • RM(资源管理器):管理各分支(各服务自己的本地事务)。

    TC 和 TM 怎么区分?

    最容易混的两个角色,一句话先记住:TM 是"做决定的人",TC 是"记账本 + 传令兵"。

    类比银行转账:

    角色类比在哪
    TM你(在 App 上点"转账")你的微服务代码里(@GlobalTransactional 注解的方法)
    TC银行总行的中央记账系统独立部署的 Seata Server
    RM各分行的柜台每个微服务自己

    一次完整流程:

    你点"转账 100 元"
      ├─ TM:"TC,帮我开一个全局事务,ID=X123"
      │      TC:记下了,X123 开始
      ├─ TM 调 A 扣款 → RM-A 执行 → TC:记下分支 A
      ├─ TM 调 B 加钱 → RM-B 执行 → TC:记下分支 B
      └─ TM:"TC,X123 提交!"
         TC:查账本——A、B 都 OK,通知 A 提交、B 提交
    

    关键区别:

    TMTC
    干什么发起全局事务,决定提交还是回滚记录全局事务状态,通知各分支执行
    在哪跑你的微服务里独立部署的 Seata Server
    有几个很多个一个(或一个集群)
    谁写代码你写(业务代码)不写(用现成的 Seata Server)

    为什么 TM 不自己通知 RM,非要 TC 居中?

    因为 TM 做完决定就可能挂了。比如:TM 告诉 RM-A"扣成功",正准备告诉 RM-B 时进程崩了——RM-A 扣了、RM-B 没加上,数据就不一致了。

    TC 的存在就是为了解决这个问题:TC 把"决策结果"持久化在自己这里,TM 挂了也没关系,TC 重启后接着把剩下的 RM 通知完。这就是为什么 TC 必须独立部署、必须有持久化。

    常见误区正解
    TM = 协调者错,TC 才是协调者。TM 只是"发起者 + 决策者"
    TM 负责回滚错,TM 只做决定"要不要回滚",真正通知各 RM 回滚的是 TC
    TC 是某个微服务错,TC 是独立的 Seata Server,不跑业务代码

    2、四种模式对比(概念)

    模式思路侵入性适用
    AT业务无感,框架自动生成反向回滚(UNDO_LOG)低最常用,适合多数业务
    TCC业务方手写 Try / Confirm / Cancel 三个方法高高一致性、性能敏感场景
    XA数据库原生 XA 协议,强一致中强一致要求、性能可接受场景
    SAGA长事务拆成子事务,失败逐级逆向补偿中长流程、跨长时间(如聚合支付)

    3、AT 模式详解(重点)

    AT 模式是 Seata 最常用方案,业务代码几乎不用改("无侵入"):

    阶段一(本地执行 + 记录回滚日志):
      各服务各自提交本地事务,同时把"修改前数据"记进 UNDO_LOG
      此时数据已变,但先不告诉别人最终结果(软状态)
    
    阶段二(协调器统一决策):
      全部成功 → 各分支宣告操作完成,删除 UNDO_LOG
      任一失败 → 各分支按 UNDO_LOG 执行逆操作,把数据修改成事务执行之前的样子(补偿)
    

    seata-at

    业务里只需在最外层方法加一个注解(伪代码示意):

    // 伪代码:最外层开启全局事务
    @GlobalTransactional
    public void createOrder(Long userId, Long productId) {
        inventoryService.deduct(productId);   // 扣库存(分支一)
        orderService.save(order);             // 写订单(分支二)
        // 任一步失败,Seata 自动回滚另一个已提交的分支
    }
    

    理解要点:业务只管"正常写",Seata 靠 UNDO_LOG 在失败时自动反向回滚——这是 AT 模式"侵入性低"的原因。


    三、总结

    1、什么时候用分布式事务

    场景建议
    跨库/跨服务强一致(钱、库存、订单)用 Seata(AT 为主)
    能容忍最终一致、异步化优先用"本地消息表 + 消息队列"实现最终一致(更轻量)
    可重试的写操作先考虑"重试 + 幂等"能不能解决,能就不必上分布式事务

    原则:分布式事务是"最后手段"。能靠最终一致/异步/幂等解决,就不要引入它的复杂度和性能开销。


    2、课堂练习(概念问答)

    1. 用一句话说明"为什么拆了服务就会遇到分布式事务问题"。
    2. CAP 里为什么 P 必须保留?业务系统通常选 C 还是 A?
    3. 2PC 有哪三个天生问题?TCC 用什么办法绕过"数据库行锁",代价是什么?
    4. AT 模式为什么业务代码几乎不用改?(提示:UNDO_LOG 自动回滚)
    5. 思考:你的项目是"扣库存 + 下单",你会选 Seata 还是"本地消息 + 消息队列"?为什么?

    3、本讲小结

    • 服务/库拆分后,"跨库跨服务保持一致"就是分布式事务问题。
    • 理论:CAP 要取舍,BASE 走最终一致。
    • 经典解法演进:2PC(两阶段锁资源)→ 3PC(加超时改善阻塞)→ XA(数据库标准)→ TCC(业务层 Try/Confirm/Cancel)。Seata 四模式是这些思路的工程化。
    • Seata 四模式:AT(无侵入、最常用)/ TCC(高一致)/ XA(强一致)/ SAGA(长流程)。
    • AT 两阶段:先本地执行 + 记 UNDO_LOG,再由协调器统一提交或反向回滚。
    • 原则:能异步最终一致就尽量别上分布式事务。

    4、本节验证

    • 理解单库事务 vs 分布式事务的区别 ✅
    • 掌握 CAP 定理与 BASE 思想 ✅
    • 了解 2PC / 3PC / XA / TCC 的基本思路与相互关系 ✅
    • 理解 Seata 四种模式(AT / TCC / XA / SAGA)的思路与适用场景 ✅
    • 会判断什么时候需要分布式事务 ✅
    end