分布式事务回顾总结 一、基本概念分布式项目中每个服务使用不同的数据库导致事务失效每个服务只能控制自己数据库的事务二、解决方案1.XA协议每个服务处理自己的事务完成后通知事务协调服务然后不提交等待事务协调服务通知是否需要提交还是回滚优点直白的解决了分布式事务问题缺点有的服务执行事务的速度很快有些很慢导致处理的快的服务需要事务挂起等待资源空耗不满足在分布式项目的高并发背景性能低下2.TCC协议Try-Confirm-Cancel每个服务将各自的事务拆分成三个接口Try尝试接口Confirm确认接口Cancel取消接口事务调节服务TC分成两个阶段准备阶段和提交阶段TC服务先在准备阶段调用每个服务的Try接口Try接口相当于直接执行本服务的事务如果执行成功则向TC发送success消息如果失败则向TC发送fail消息TC收到所有消息后判断是否有fail消息如果有说明事务需要回滚则调用每个服务的Cancel接口将数据回滚如果所有消息都是success则调用每个服务的Confirm接口全部提交事务优点没有事务挂起没有性能问题灵活度很高缺点工作量和复杂度增加3.Seata AT模式是Apache提供的一个专门用于解决分布式事务问题的项目实现方式在所有服务中建立一张undo_log表第一阶段开始执行业务后seata代理数据源DataSource)拦截sql执行提取sql执行前后的数据镜像将前后数据镜像转化成一条sql加入到当前本地事务执行提交插入到undo_log表中相当于逆向补偿的sql第二阶段如果所有参与者都处理成功TC 协调所有参与者直接删除undo_log记录如果存在参与者处理失败TC协调所有的参与者回滚参与者从undo_log表中提取前后镜像计算得到逆向补偿sql执行sql实现补偿优点实现分布式事务没有事务挂起性能高操作简单工作量很小缺点每个数据库要建一张表灵活度不高;无法处理复杂事务4.SegaSaga 模式是 SEATA 提供的长事务解决方案在 Saga 模式中业务流程中每个参与者都提交本地事务当出现某一个参与者失败则补偿前面已经成功的参与者一阶段正向服务和二阶段补偿服务都由业务开发实现。一、Seata AT 模式全局事务执行过程Seata AT 模式是对传统两阶段提交协议的优化核心思想是将业务数据更新和回滚日志undo_log放在同一个本地事务中提交从而减少锁持有时间-1。一阶段Phase 1执行与提交解析 SQL拦截业务 SQL解析出类型UPDATE/INSERT/DELETE、表名、条件等信息-1。查询前镜像Before Image根据条件查询更新前的数据快照-1。执行业务 SQL执行实际的更新操作-1。查询后镜像After Image根据主键查询更新后的数据快照-1。插入回滚日志将前后镜像及业务 SQL 信息组成一条 undo_log 记录插入到UNDO_LOG表中-1。注册分支并获取全局锁在本地事务提交前向 TC事务协调器注册分支事务并申请该记录的全局锁。只有拿到全局锁才能提交本地事务-1。提交本地事务业务数据与 undo_log 在同一本地事务中原子提交随后释放本地锁和连接资源-1。二阶段Phase 2异步提交或回滚提交TC 通知各 RM 异步清理 undo_log非常快速-1。回滚根据一阶段的 undo_log 生成反向 SQL 进行补偿恢复前镜像数据-1。二、AT 模式的写隔离机制全局锁 vs 本地锁写隔离的核心逻辑在一阶段本地事务提交前必须确保拿到全局锁拿不到全局锁则不能提交本地事务且获取全局锁的尝试有超时限制超时后回滚本地事务并释放本地锁-1-20。全局锁与本地锁的区别维度本地锁全局锁管理者数据库自身如 MySQL 行锁Seata TC事务协调器作用范围单个本地事务内的数据排他跨多个服务的全局事务间的写隔离生命周期本地事务提交后立即释放全局事务结束前一直持有目的保证本地事务的 ACID防止多个全局事务间的脏写写隔离的流程示例两个全局事务 tx1 和 tx2 先后更新同一条记录初始值 1000-1-20tx1 开启本地事务拿到本地锁更新m 1000 - 100 900。tx1 提交前申请全局锁成功后提交本地事务释放本地锁但全局锁仍持有。tx2 开启本地事务拿到本地锁此时 tx1 本地锁已释放更新m 900 - 100 800。tx2 提交前尝试获取全局锁但被 tx1 持有进入重试等待。tx1 二阶段提交完成释放全局锁tx2 拿到全局锁提交本地事务。若 tx1 二阶段回滚则需要重新获取本地锁进行反向补偿。此时若 tx2 仍持有本地锁等待全局锁tx1 回滚会重试直到 tx2 全局锁等待超时、回滚并释放本地锁tx1 最终回滚成功。因为全局锁在 tx1 结束前一直被持有整个过程不会发生脏写问题-1。读隔离方面Seata AT 模式默认全局隔离级别为读未提交。若需读已提交可通过SELECT FOR UPDATE语句代理实现——该语句会申请全局锁若被其他事务持有则释放本地锁并重试查询被阻塞直到拿到全局锁从而读取到已提交的数据-1。三、缓存与数据库不同步的解决方案缓存与数据库是两个独立存储无法原子性更新只能通过合理的更新策略保证最终一致性。核心原则是写操作只删除缓存而不更新缓存让缓存作为派生数据在下次读取时懒加载重建-12。方案对比方案实现思路优点缺点适用场景先更新数据库再删除缓存写请求先更新 DB再删除 Redis实现简单不一致概率极低极端情况下删缓存失败仍会脏90% 以上业务场景-23延迟双删先删缓存 → 更新 DB → 延迟数百毫秒 → 再删缓存规避并发读回填旧值的窗口延迟时间难精确设定写性能降低并发较高的读写场景-消息队列异步删除更新 DB 后发 MQ 消息消费者负责删缓存有重试机制解决删除失败问题引入 MQ 复杂度有延迟对一致性要求较高的业务-23Binlog 订阅异步同步通过 Canal/Maxwell 监听 MySQL binlog触发缓存删除业务代码无侵入一致性最高架构最复杂运维成本高大型互联网高并发核心业务-23推荐组合策略工业界常用的是「先更库再删缓存 延迟双删 Binlog 兜底」组合路线