拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

分布式事务总结

分布式事务总结

前提:为什么会出现分布式事务?

在以往的单体项目只有一个数据库,而微服务中有多个数据库。

因为本地事务管不了跨服务、跨数据库的操作。所以,出现分布式事务解决之问题

一.分布式事务基本概念

1.参与者角色。

2.二阶段提交(2pc)

3.全局事务&分支事务

(一)参与者角色

  • TC:事务协调者。协调所有参与者,管理全局事务提交或回滚
  • TM:事务发起者。发起全局事务,并接受 TC 协调
  • RM:事务参与者。参与全局事务,管理本地资源,如本地数据库事务

(二)二阶段提交(2pc)

  • 第一阶段:
    • TC 通知所有参与者准备提交,参与者执行本地事务但不提交,将准备结果通知 TC
  • 第二阶段:
    • 如果所有参与者都成功,TC 协调所有参与者提交事务
    • 如果存在参与者准备失败,TC 协调所有参与者回滚事务

(三)全局事务&分支事务

  • 全局事务:又称分布式事务,包含所有分支事务
  • 分支事务:又称本地事务,是某个服务中的本地数据库事务

二.常见的解决方案

1.XA 协议

2.TCC

3.Seata AT 模式

4.Saga

(一)XA 协议

XA 是数据库层面的分布式事务协议,基于 2PC

1.原理
  • 步骤一:每个事务的参与者执行本地事务操作,执行完之后通知事务的协调者,但是不提交
  • 步骤二:协调者汇总所有参与者状态:
    • 全部成功,通知所有参与者提交
    • 任意失败,通知所有参与者回滚
2.优点

解决分布式事务的问题

3.缺点

存在事务挂起,资源空耗,性能低下

(二)TCC

TCC是将业务接口拆成三个接口。分别为:Try、Confirm、Cancel

1.三个阶段

三个阶段对应三个接口

  • Try:尝试,资源锁定。例如转账时冻结资金
  • Confirm:确认,真正执行业务。例如解冻并完成转账
  • Cancel:取消,逆向补偿。例如解冻资金
2.两阶段
  • 准备阶段:TC调用所有服务的Try接口
  • 提交阶段:(TC根据结果调用接口)
    • 全部成功,调用 Confirm
    • 存在失败,调用 Cancel
3.优点
  1. 能实现分布式事务
  2. 没有事务挂起,性能较好
  3. 灵活度高
4.缺点
  1. 业务接口一拆三,工作量和复杂度高。
  2. 需要进行业务改造,工作量大,实现复杂

(三)Seata AT 模式

AT 是 Seata 创新的一种非侵入式分布式事务解决方案。

1.原理
  • 前提:在各个事务的数据库中建表 undo_log表
  • 第一阶段:
    • 直接执行业务,seata代理数据源(DataSource),拦截sql执行,
    • 提取sql执行前后的数据镜像,将前后数据镜像转化成一条sql,
    • 加入到当前本地事务,执行提交,插入到undo_log表中
  • 第二阶段:TC 协调所有参与者
    • 全部成功:直接删除undo_log。
    • 存在失败:根据undo_log中的前后镜像生成逆向补偿 SQL,执行回滚。

(四)Saga

Saga 是长事务解决方案。

  • 一阶段:正向服务。
  • 二阶段:补偿服务。

三.seata的AT模式全局事务执行过程

第一阶段:全局事务开启与分支事务执行

  1. TM向TC开启全局事务,TC生成全局唯一XID并返回。
  2. TM调用下游服务,透传XID。
  3. 下游RM获取XID,向TC注册分支事务。
  4. RM执行SQL前,生成前置镜像(Before Image)。
  5. RM执行SQL,生成后置镜像(After Image),并将两者记录至undo_log。
  6. 申请全局锁(写隔离核心):RM提交本地事务前,向TC申请该行数据的全局锁。
    • 成功:继续执行。
    • 失败:重试,超时则本地回滚并抛异常。
  1. 获取全局锁后,RM提交本地事务并释放本地锁,但继续保持持有全局锁。

第二阶段:全局事务结束与锁释放

  1. TM根据执行情况,向TC发起全局提交或回滚。
  2. 全局提交:TC通知RM清理undo_log,释放全局锁。
  3. 全局回滚:TC通知RM利用undo_log反向补偿回滚,完成后释放全局锁。

核心逻辑:本地事务提交前必须拿到全局锁,且在全局事务彻底结束前不释放,以此防止分布式环境下的脏写。

四.seata的AT模式如何实现写隔离?

  1. 获取本地锁:分支事务开始时,会先获取数据库的本地锁,执行数据更新操作。
  2. 申请全局锁:在本地事务提交前,必须向 TC 申请该数据记录的全局锁。
  3. 等待与重试:如果全局锁已被其他全局事务持有,当前事务会释放本地锁并回滚,然后通过循环不断重试,尝试重新获取本地锁和全局锁,直到超过预设的超时时间。
  4. 提交与释放:成功获取全局锁后,本地事务才会提交,并释放本地锁。全局锁会一直持有到整个全局事务完成(二阶段结束)后才释放。

五.全局锁与本地锁的区别

  • 管理方:全局锁由 Seata TC 管理;本地锁由本地数据库管理。
  • 范围:全局锁跨服务;本地锁只在单个服务内。
  • 粒度:全局锁锁数据行;本地锁锁连接或行。
  • 释放时机:全局锁等整个全局事务结束才释放;本地锁本地事务一提交或回滚就释放。
  • 目的:全局锁防止全局事务之间脏写;本地锁保证本地事务 ACID。

核心区别:全局锁管分布式写隔离,本地锁管单机事务一致性。

六.缓存与数据库不同步如何解决?

  1. Cache Aside(最常用)
  • 读:先读缓存,未命中读数据库,再回填缓存,并设过期时间。
  • 写:先更新数据库,再删除缓存。
  • 不要更新缓存,直接删,避免并发写覆盖。
  1. 延迟双删
  • 更新数据库后删一次缓存,延迟几百毫秒再删一次。
  • 用来应对并发读回填旧数据、主从延迟等问题。
  1. 订阅 binlog + MQ(高可靠)
  • 用 Canal 等监听 MySQL binlog,发消息到 MQ,消费者再删缓存。
  • 业务解耦,失败可重试,保证最终一致。
  1. 分布式锁 / 版本号
  • 同一 key 的读写串行化,或缓存带版本号做 CAS。
  • 适合强一致场景,但性能差。
  1. 过期时间兜底
  • 所有缓存都设 TTL,即使短期不一致,最终也会恢复。
返回列表