本地事务执行流程流程
数据库undo.log的工作原理
在操作数据之前会先将数据备份到Undo.log中,然后进行修改数据,如果出现错误或者用户执行
ROLLBACK语句,系统可以利用Undo.log中的备份将数据恢复到事务开始之前的状态。
执行流程
假设有A、B两个数据,值为 1,2
1、事务开始
2、记录A=1到undo.log
3、修改A=3
4、记录B=2到undo.log
5、修改B=4
6、将undo.log写入磁盘
7、将数据写入到磁盘
8、事务提交
在事务提交之前事务备份和修改先在内存中执行,效率高,然后写入磁盘持久化
事务如何保证持久化呢
先写入磁盘持久化,再提交事务,也就是说提交事务之前已一定会写入磁盘
数据库Redo.log(和undo.log相反)undo.log是记录旧数据,redo.log是记录新数据
假设有A、B两个数据,值为 1,2
1、事务开始
2、记录A=1到undo.log buffer(内存缓冲区)
3、修改A=3
4、记录A=3到redo.log buffer
5、记录B=2到undo.log buffer
6、修改B=4
7、记录B=4到redo.log buffer
8、将undo.log写入磁盘
9、将redo.log写入磁盘
10、事务提交
undo.log是写入到redo.log中的 所以说先记录旧数据在记录新数据
undo.log主要是记录事务前的数据(不可替代)
磁盘写入数据方式,简图
磁盘写入方式分为: 随机写和顺序写
redo.log的顺序写(效率高)
数据库数据写入磁盘是随机写(需要先寻址(耗费大量时间))
什么是CAP
-
Consistency(一致性)
-
Availability(可用性)
-
Partition tolerance (分区容错性)
1、先看 Partition tolerance,中文叫做"分区容错"。
大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。分区容错的意思是,区间通信可能失败。比如,一台服务器放在上海,另一台服务器放在北京,这就是两个区,它们之间可能因网络问题无法通信。
上图中,G1 和 G2 是两台跨区的服务器。G1 向 G2 发送一条消息,G2 可能无法收到。系统设计的时候,必须考虑到这种情况。
一般来说,分布式系统,分区容错无法避免,因此可以认为 CAP 的 P 总是成立。根据CAP 定理,剩下的 C 和 A 无法同时做到。
2.Consistency
写操作之后的读操作,必须返回该值。举例来说,某条记录是 v0,用户向 G1 发起一个写操作,将其改为 v1。
接下来,用户的读操作就会得到 v1。这就叫一致性。
问题是,用户有可能向 G2 发起读操作,由于 G2 的值没有发生变化,因此返回的是 v0。G1 和 G2 读操作的结果不一致,这就不满足一致性了。
为了让 G2 也能变为 v1,就要在 G1 写操作的时候,让 G1 向 G2 发送一条消息,要求 G2 也改成 v1。
这样的话,用户向 G2 发起读操作,也能得到 v1。
3.Availability
Availability 中文叫做"可用性",意思是只要收到用户的请求,服务器就必须给出回应(对和错不论)。
用户可以选择向 G1 或 G2 发起读操作。不管是哪台服务器,只要收到请求,就必须告诉用户,到底是 v0 还是 v1,否则就不满足可用性。
4.Consistency 和 Availability 的矛盾
一致性和可用性,为什么不可能同时成立?
答案很简单,因为可能通信失败(即出现分区容错)。
如果保证 G2 的一致性,那么 G1 必须在写操作时,锁定 G2 的读操作和写操作。只有数据同步后,才能重新开放读写。锁定期间,G2 不能读写,没有可用性不。
如果保证 G2 的可用性,那么势必不能锁定 G2,所以一致性不成立。
综上所述,G2 无法同时做到一致性和可用性。系统设计时只能选择一个目标。如果追求一致性,那么无法保证所有节点的可用性;如果追求所有节点的可用性,那就没法做到一致性。
Base理论
BASE是三个单词的缩写:
-
Basically Available(基本可用)
-
Soft state(软状态)
-
Eventually consistent(最终一致性)(G1-G2数据同步需要隔一段时间(比如:1毫秒))
一致性不是立刻一 致性,是隔一段时间后最终一直
基本可用是基本可以使用(不是完全不可用)隔一段时间后就可以使用了。
由上面的两种思想,延伸出了很多的分布式事务解决方案:
-
XA
-
TCC
-
可靠消息最终一致
-
AT
TCC
它本质是一种补偿的思路。事务运行过程包括三个方法,
-
Try:资源的检测和预留;
-
Confirm:执行的业务操作提交;要求 Try 成功 Confirm 一定要能成功;
-
Cancel:预留资源释放。
执行分两个阶段:
-
准备阶段(try):资源的检测和预留;
-
执行阶段(confirm/cancel):根据上一步结果,判断下面的执行方法。如果上一步中所有事务参与者都成功,则这里执行confirm。反之,执行cancel
-
优势
TCC执行的每一个阶段都会提交本地事务并释放锁,并不需要等待其它事务的执行结果。而如果其它事务执行失败,最后不是回滚,而是执行补偿操作。这样就避免了资源的长期锁定和阻塞等待,执行效率比较高,属于性能比较好的分布式事务方式。
-
缺点
-
代码侵入:需要人为编写代码实现try、confirm、cancel,代码侵入较多
-
开发成本高:一个业务需要拆分成3个步骤,分别编写业务实现,业务编写比较复杂
-
安全性考虑:cancel动作如果执行失败,资源就无法释放,需要引入重试机制,而重试可能导致重复执行,还要考虑重试时的幂等问题
-
AT模式
2019年 1 月份,Seata 开源了 AT 模式。AT 模式是一种无侵入的分布式事务解决方案。可以看做是对TCC或者二阶段提交模型的一种优化,解决了TCC模式中的代码侵入、编码复杂等问题。
在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
可以参考Seata的官方文档。
2.7.1.基本原理
先来看一张流程图:
有没有感觉跟TCC的执行很像,都是分两个阶段:
-
一阶段:执行本地事务,并返回执行结果
-
二阶段:根据一阶段的结果,判断二阶段做法:提交或回滚
但AT模式底层做的事情可完全不同,而且第二阶段根本不需要我们编写,全部有Seata自己实现了。也就是说:我们写的代码与本地事务时代码一样,无需手动处理分布式事务。
那么,AT模式如何实现无代码侵入,如何帮我们自动实现二阶段代码的呢?
阶段一流程
阶段二执行流程
提交操作:
删除undo.log和redo.log等日志
回滚操作:
查询undo.log,通过undo.log进行回滚
回滚前 那redo.log 和数据库数据进行对比,看数据是否一致,如果一致直接回滚,不一致说明有其他人对数据进行了修改,此时需要人工干预,出现了脏数据。
这里的before image
和after image
类似于数据库的undo和redo日志,但其实是用数据库模拟的。
一阶段
在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL
”要更新的业务数据,在业务数据被更新前,将其保存成“before image
”,然后执行“业务 SQL
”更新业务数据,在业务数据更新之后,再将其保存成“after image
”,最后获取全局行锁,提交事务。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
二阶段提交
二阶段如果是提交的话,因为“业务 SQL
”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL
”,还原业务数据。回滚方式便是用“before image
”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image
”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写
,出现脏写就需要转人工处理。
不过因为有全局锁机制,所以可以降低出现脏写
的概率。
AT 模式的一阶段、二阶段提交和回滚均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。
2.7.2.详细架构和流程
Seata中的几个基本概念:
-
TC(Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚(TM之间的协调者)。
-
TM(Transaction Manager) - 事务管理器
定义全局事务的范围:开始全局事务、提交或回滚全局事务。
-
RM(Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
我们看下面的一个架构图
-
TM:业务模块中全局事务的开启者
-
向TC开启一个全局事务
-
调用其它微服务
-
-
RM:业务模块执行者中,包含RM部分,负责向TC汇报事务执行状态
-
执行本地事务
-
向TC注册分支事务,并提交本地事务执行结果
-
-
TM:结束对微服务的调用,通知TC,全局事务执行完毕,事务一阶段结束
-
TC:汇总各个分支事务执行结果,决定分布式事务是提交还是回滚;
-
TC 通知所有 RM 提交/回滚 资源,事务二阶段结束。
一阶段:
-
TM开启全局事务,并向TC声明全局事务,包括全局事务XID信息
-
TM所在服务调用其它微服务
-
微服务,主要有RM来执行
-
查询
before_image
-
执行本地事务
-
查询
after_image
-
生成
undo_log
并写入数据库 -
向TC注册分支事务,告知事务执行结果
-
获取全局锁(阻止其它全局事务并发修改当前数据)
-
释放本地锁(不影响其它业务对数据的操作)
-
-
待所有业务执行完毕,事务发起者(TM)会尝试向TC提交全局事务
二阶段:
-
TC统计分支事务执行情况,根据结果判断下一步行为
-
分支都成功:通知分支事务,提交事务
-
有分支执行失败:通知执行成功的分支事务,回滚数据
-
-
分支事务的RM
-
提交事务:直接清空
before_image
和after_image
信息,释放全局锁 -
回滚事务:
-
校验after_image,判断是否有脏写
-
如果没有脏写,回滚数据到
before_image
,清除before_image
和after_image
-
如果有脏写,请求人工介入
-
-