基于支持本地 ACID 事务的关系型数据库。
Java 应用,通过 JDBC 访问数据库。
AT模式其实就是二阶段提交协议的演变:
一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:
提交异步化,非常快速地完成。
回滚通过一阶段的回滚日志进行反向补偿。
事务管理者(TM)
资源管理者(RM)
事务协调者(TC)
项目启动的时候会注册一个拦截器 拦截标注了@GlobalTransactional注解请求的方法
进行分布式事务的处理
两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。
tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁 。
1.TM会创建一个Transactional对象 然后根据事务传播机制进行处理之后会进行全局事务的注册
2.TM会基于Netty给TC发送请求进行全局事务的注册
3.TM将TC返回的XID给绑定到事务中
1.TC创建GlobalSession全局会话
2.更改全局事务状态为Begin
3.添加全局事务表(global_table)信息
4.返回XID给TM
1.项目启动的时候会为数据源做一层增强当业务代码执行的时候会走到PreparedStatementProxy中然后交给执行器去执行
2.本地事务开启
3.创建前置镜像 以便在事务提交时使用这个镜像来比较和确认事务是否能够成功提交。
4.业务SQL执行
5.创建后置镜像 用来保障是否能够回滚
6.创建undo_log但是没有提交
7.RM向TC发送分支事务提交请求
8.TC接收到请求之后进行分支事务的注册
9.获取全局锁 这里其实就是往 lock_table 里面去插入一条数据 如果能获取成功表示没有被别的事务占有 否则就是被别的事务占有需要等待 如果超过一定次数就回滚本地事务释放本地锁
10.插入分支事务记录 brach_table
11.保存undo_log记录 往undo_log表里插入数据 结束所有操作之后返回响应给RM
12.RM释放本地事务
13.如果上述所有操作完成 一阶段事务提交完成 如果有问题则进行回滚
tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。
当一阶段提交都没问题之后会执行二阶段的提交
1.TM发起全局事务提交请求
2.TC处理二阶段提交请求 然后会循环所有的分支事务 往分支事务上发送请求
3.RM接收TC分支事务提交的请求进行分支事务的提交
4.RM删除undo_log数据
5.然后反馈二阶段提交完成给TC
6.TC删除brach_table 分支事务表数据 然后将分支事务状态改为二阶段分支事务已提交
7.释放全局锁 下一个事务拿到全局锁进行分支事务的提交
8.当所有分支事务全部提交完成之后会删除全局事务表的数据
9.将响应结果返回给TM 如果是成功的话则进行全局事务的解绑
如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。
此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。
1.当一阶段提交出现问题之后会触发二阶段的回滚
2.TM向TC发送事务回滚的请求
3.TC接收到请求之后遍历所有的分支事务往RM发送分支事务回滚请求
4.RM接收到回滚请求之后通过XID和brachID去查询undo_log数据然后对比前置后置镜像和undo_log回滚的数据是否一致 如果一致 删除undo_log 如果不一致抛异常
5.RM处理完之后 反馈给TC TC把分支事务状态改为二阶段已回滚
6.删除brach_table 分支事务表信息
7.释放全局锁 让下个分支事务执行
8.当所有分支事务全部回滚完之后会将全局事务表数据给删除
自此二阶段回滚完毕
https://seata.io/zh-cn/docs/dev/mode/at-mode