一.分布式事务基本概念
分布式事务解决的是跨服务、跨数据库操作的一致性问题
在CAP理论下,通常需要在强一致性和高可用性之间做权衡,BASE理论则强调的是基本可用、软状态和最终一致性。
事务参与角色:
- 事务协调者:协调所有参与者,管理全局事务
- 事务发起者:发起全局事务,接收TC协调
- 事务参与者:参与全局事务。
二.分布式事务解决方案
1. XA协议(数据库协议)
实现原理:
事务协调者先让所有参与者各自本地操作,根据所有参与者的操作结果,再统一提交或者回滚。
- 么个事务参与者,先执行本地事务,执行完成后不提交,通知事务协调者
- 事务协调者汇总事务执行状态,根据结果进行处理
- 所有参与者执行成功,事务协调者会通知所有参与者一起提交事务。
- 如果有一个参与者没有执行成功,协调者会通知所有参与者回滚事务。
特点:强一致性,但是回阻塞,性能低,适用于金融核心等并发场景。
2. TCC(Try-Confirm-Cancel)
对应有三个接口
- Try 预留或者锁定资源
- Confirm 确认执行
- Cancel 释放资源并你想补偿
分为两阶段提交
- 准备阶段:TC调用所有服务的Try接口
- 提交阶段:
- TC根据结果,如果所有成功,则调用所有服务的Confirm接口
- 如果存在失败,则调用所有服务的Cancel接口
特点:高性能、无数据库长时间的事务等待,业务侵入强
3. Seata AT模式
基于数据源代理拦截 SQL,自动生成 undo_log,按全局事务状态提交或回滚
- AT 自动事务处理,Seata 创新的一种非侵入式的分布式事务解决方案
- 实现方式
- 在各个事务的数据库中建表 undo_log表
- 第一阶段: 直接执行业务,seata代理数据源(DataSource),拦截sql执行,提取sql执行前后的数据镜像,将前后数据镜像转化成一条sql,加入到当前本地事务,执行提交,插入到undo_log表中
- 第二阶段:
- 如果所有参与者都处理成功,TC 协调所有参与者直接删除undo_log记录
- 如果存在参与者处理失败,TC协调所有的参与者回滚,参与者从undo_log表中提取前后镜像,计算得到逆向补偿sql,执行sql实现补偿
特点:无侵入、易操作,适合微服务下关系型数据库场景
4. Saga
拆成多个本地事务,有一个事务失败失败时执行补偿。
特点:适合长事务,无长时间锁,但补偿逻辑复杂
三.Seata AT 模式全局事务执行过程
Seata AT 模式依赖三个角色(前面提到过):
TC:事务协调者,维护全局事务状态,驱动提交或回滚
TM:事务发起者,开启全局事务,决定提交或回滚
RM:事务参与者,管理分支事务,生成 undo_log,执行本地事务和二阶段操作
执行流程如下:
- TM 开启全局事务,TC 生成 XID。
- XID 传递到下游服务,各服务的 RM 识别到 XID 后注册分支事务。
- 一阶段执行本地事务:RM 拦截 SQL,查询前镜像,执行业务 SQL,查询后镜像,生成 undo_log,将业务数据和 undo_log 在同一个本地事务中提交。
- 提交前获取全局锁:RM 在本地事务提交前向 TC 申请全局锁,获取成功后提交本地事务,释放本地数据库锁,获取失败则回滚本地事务并重试或超时放弃。
- 二阶段提交:若全局事务提交,TC 通知各 RM 异步删除 undo_log,过程很快。
- 二阶段回滚:若全局事务回滚,TC 通知 RM 根据 undo_log 的前镜像生成反向 SQL;回滚前会校验当前数据是否与后镜像一致,一致则回滚,不一致则说明存在外部脏写,可能需要人工处理。
四.Seata AT 模式如何实现写隔离?
AT 模式的写隔离不是靠长时间持有数据库锁实现的,而是通过全局锁和本地锁配合完成的。
当两个全局事务修改同一行数据时:
- 事务 A 先执行,获取数据库行锁,更新数据,生成前后镜像。
- 事务 A 在本地事务提交前,向 TC 申请该行数据的全局锁。
- 申请成功后,事务 A 提交本地事务,释放数据库行锁,但继续持有全局锁。
- 事务 B 随后执行,也能获取数据库行锁并更新数据,但在提交前申请全局锁时会发现该行已被事务 A 持有。
- 事务 B 只能等待全局锁释放;如果事务 A 全局提交,B 继续提交;如果事务 A 全局回滚,A 会根据 undo_log 反向补偿,B 最终超时或等待后重新执行。
这样就避免了“事务 A 已提交本地事务但全局事务尚未结束”时,事务 B 基于中间状态数据继续修改造成的脏写。
五.全局锁与本地锁的区别
- 本地锁:数据库自身的行锁,由数据库管理,在本地事务执行期间持有,事务提交或回滚后立即释放,只能保证单个数据库本地事务的隔离性。
- 全局锁:Seata TC 维护的逻辑锁,由 RM 在本地事务提交前申请,持有到全局事务二阶段结束才释放,用于保证跨全局事务的写隔离。
六.缓存与数据库不同步如何解决?
缓存与数据库不一致的根本原因是两者无法原子更新,且存在并发读写、网络延迟和缓存失效时序问题。通常以数据库为唯一可信数据源,追求最终一致性。
常用方案:
Cache-Aside 旁路缓存
读:先查缓存,未命中再查数据库并回填缓存。
写:先更新数据库,再删除缓存。
这是最常用方案,实现简单,但存在短暂不一致窗口。延迟双删
更新数据库后删除缓存,再延迟一段时间再次删除缓存,用于清除并发读请求回填的旧数据。延迟时间需大于读库回填缓存的耗时及可能的主从同步延迟。消息队列补偿
数据库更新成功后发送 MQ 消息,由消费者异步删除缓存。支持重试和死信兜底,适合分布式系统,但增加了链路复杂度。Binlog 监听
通过 Canal 等工具订阅 MySQL Binlog,解析数据变更后异步删除或更新缓存。业务代码侵入小,适合微服务架构和数据异构同步。兜底策略
给缓存设置合理 TTL,即使删除失败也能自动过期刷新;核心强一致场景可减少缓存依赖,或直接读主库。
工程实践中,“先更新数据库,再删除缓存 + 合理 TTL + MQ/Binlog 兜底”是较常见的组合;如果一致性要求很高,再叠加延迟双删或版本号控制。