1事务
一组操作构成的可靠、独立的工作单元。
1.1本地事务
原子性(atomicity):一个事务是一个不可分割的工作单位,事务中包括的操作要么都做,要么都不做。
一致性(consistency):事务必须是使数据库从一个一致性状态变到另一个一致性状态。一致性与原子性是密切相关的。
隔离性(isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
持久性(durability):持久性也称永久性(permanence),指一个事务一旦提交,它对数据库中数据的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。
事务的作用范围只对本数据库有效。
1.2分布式事务(全局事务)
传统单体服务经过微服务拆分和数据库分库,导致原本是一个事务的接口被拆分成多个不同微服务的接口,本地事务只作用于本地数据库无法满足微服务需求。
所以,需要一个全局事务来对整个业务流程进行管理。下面举例一个业务场景:
1、单体服务
2、微服务
2分布式事务理论基础
2.1CAP原则
CAP原则又称CAP定理,指的是在一个分布式系统中,一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)。CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。
2.1.1一致性(C)
所有节点在同一时间的数据完全一致。
例:下面是主从数据库,客户端从从库读到的数据一定是最新的,不可以返回就的数据,所以对db2加锁,同步时不可读。
1、强一致性:任意时刻,所有节点数据必须一致。
理论支撑:两阶段提交、三阶段提交、Paxos算法
2、弱一致性:数据写入后,不承诺可以立即读到最新写入的值。
例如:游戏中的玩家排名
3、最终一致性:不保证在任意时刻,任意节点的同一份数据都是相同的。但是在一段时间后,节点间的数据最终会一致。
理论支撑:base理论
例如:DNS服务器,当一个域名更改绑定ip时,由于距离等因素的不同,访问者解析出来的ip可能还是旧的,但过一段时间后全世界所有电脑解析该域名对应的ip都会是最新的。
2.1.2可用性(A)
服务一直可用,而且是正常响应时间。
例:无论从数据库是否在同步,都不能影响客户端读取数据,哪怕返回旧的数据也要及时提供服务。
2.1.3分区容忍性(P)
在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务。
例:一个分区挂了不影响整个服务,当读分区挂了不应该影响客户端继续写数据,当写分区挂了不应该影响客户端读数据
2.2CAP组合
2.2.1AP
放弃一致性,最求可用性和分区容忍性,但最终会保证一致性。
比如:订单退款,今日退款成功明日到账。
2.2.2CP
放弃可用性,追求一致性和分区容忍性。Zookeeper追求的就是强一致性。
比如:跨行转账,一次转账必须等待双方银行数据完成才结束。
2.2.3AC
放弃分区容忍性,不进行分区,不考虑网络不通或节点挂掉的问题。那就不是一个标准分布式系统了。
2.3BASE理论
一、强一致性和最终一致性
1、强一致性:任何时间点每个节点的数据都保持一致。
2、最终一致性:允许在一段时间内每个节点的数据不一致,但最终数据必须一致。
二、BASE以下三个短语的缩写:
1、基本可用 Basically Available:当出现故障部分不可用但是核心功能可用。(例如:电商网站支付挂了,但商品依然可浏览)。
2、软状态 Soft State:允许中间状态(例如:订单状态的支付中、数据同步中)
3、最终一致 Eventually Consistent:所有节点数据最终达到一致
BASE是对AP的扩展,通过牺牲强一致性来获取可用性。当出现故障时保证核心功能可用,允许在一段时间内每个节点的数据不一致,但最终数据必须一致。满足BASE理论的事务,我们称之为“柔性事务”。
3协议和算法
3.1概述
3.1.1DTP模型
即分布式处理模型一种标准,是X/Open 组织定义的一套分布式事务的行业标准。(X/Open由IBM、Oracle、华为、PHILIPS等行业巨头共同创建,为了能使他们的产品互通而制定一些标准)。
X/Open DTP 定义了三个组件: AP,TM,RM
1、AP(Application Program):也就是应用程序,可以理解为使用DTP的程序
2、RM(Resource Manager):资源管理器,这里可以理解为一个DBMS系统,或者消息服务器管理系统,应用程序通过资源管理器对资源进行控制。资源必须实现XA定义的接口(XA就是X/Open DTP定义的交易中间件与数据库之间的接口规范)。
3、TM(Transaction Manager):事务管理器,负责协调和管理事务,提供给AP应用程序编程接口以及管理资源管理器
其中,AP 可以和TM 以及 RM 通信,TM 和 RM 互相之间可以通信,DTP模型里面定义了XA接口,TM 和 RM 通过XA接口进行双向通信,例如:TM通知RM提交事务或者回滚事务,RM把提交结果通知给TM。AP和RM之间则通过RM提供的Native API 进行资源控制,这个没有进行约API和规范,各个厂商自己实现自己的资源控制,比如Oracle自己的数据库驱动程序。
3.1.2两阶段提交协议(2PC:Two-Phase Commit)
两阶段提交协议的目标在于为分布式系统保证数据的一致性,许多分布式系统采用该协议提供对分布式事务的支持。顾名思义,该协议将一个分布式的事务过程拆分成两个阶段: 准备(Prepare) 和 事务提交(Commit) 。为了让整个数据库集群能够正常的运行,该协议指定了一个 协调者 单点,用于协调整个数据库集群各节点的运行。为了简化描述,我们将数据库集群中的各个节点称为 参与者 ,三阶段提交协议中同样包含协调者和参与者这两个角色定义。两阶段提交只有协调者有超时机制,参与者没有超时机制。
第一阶段:准备
该阶段的主要目的在于打探数据库集群中的各个参与者是否能够正常的执行事务,具体步骤如下:
1.协调者向所有的参与者发送事务执行请求,并等待参与者反馈事务执行结果;
2.事务参与者收到请求之后,执行事务但不提交(全局事务不提交,本地事务会提交),并记录事务日志(Undo和Redo日志,即提交前和提交后的数据日志);
3.参与者将自己事务执行情况反馈给协调者,同时阻塞等待协调者的后续指令。
第二阶段:事务提交
在经过第一阶段协调者的询盘之后,各个参与者会回复自己事务的执行情况,这时候存在 3 种可能性:
1.所有的参与者都回复能够正常执行事务。
2.一个或多个参与者回复事务执行失败。
3.协调者等待超时。
1)事务提交
对于第 1 种情况,协调者将向所有的参与者发出提交事务的通知,具体步骤如下:
1.协调者向各个参与者发送 commit 通知,请求提交事务;
2.参与者收到事务提交通知之后执行 commit 操作,然后释放占有的资源;
3.参与者向协调者返回事务 commit 结果信息。
2)事务回滚
对于第 2 和第 3 种情况,协调者均认为参与者无法成功执行事务,为了整个集群数据的一致性,所以要向各个参与者发送事务回滚通知,具体步骤如下:
1.协调者向各个参与者发送事务 rollback 通知,请求回滚事务;
2.参与者收到事务回滚通知之后执行 rollback 操作,然后释放占有的资源;
3.参与者向协调者返回事务 rollback 结果信息。
两阶段提交协议解决的是分布式数据库数据强一致性问题,实际应用中更多的是用来解决事务操作的原子性,下图描绘了协调者与参与者的状态转换。
站在协调者的角度,在发起投票之后就进入了 WAIT 等待状态,等待所有参与者回复各自事务执行状态,并在收到所有参与者的回复后决策下一步是发送 commit 或 rollback 信息。站在参与者的角度,当回复完协调者的投票请求之后便进入 READY 就绪状态(能够正常执行事务),接下去就是等待协调者最终的决策通知,一旦收到通知便可依据决策执行 commit 或 rollback 操作。
缺点:
1)单点问题
协调者在整个两阶段提交过程中扮演着举足轻重的作用,一旦协调者所在服务器宕机,就会影响整个数据库集群的正常运行。比如在第二阶段中,如果协调者因为故障不能正常发送事务提交或回滚通知,那么参与者们将一直处于阻塞状态,整个数据库集群将无法提供服务。
2)同步阻塞
两阶段提交执行过程中,所有的参与者都需要听从协调者的统一调度,期间处于阻塞状态而不能从事其他操作,这样效率极其低下。
3)数据不一致性
两阶段提交协议虽然是分布式数据强一致性所设计,但仍然存在数据不一致性的可能性。比如在第二阶段中,假设协调者发出了事务 commit 通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了commit 操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。
解决办法:
针对上述问题可以引入 超时机制 和 互询机制 在很大程度上予以解决。
对于协调者来说如果在指定时间内没有收到所有参与者的应答,则可以自动退出 WAIT 状态,并向所有参与者发送 rollback 通知。对于参与者来说如果位于 READY 状态,但是在指定时间内没有收到协调者的第二阶段通知,则不能武断地执行 rollback 操作,因为协调者可能发送的是 commit 通知,这个时候执行 rollback 就会导致数据不一致。
此时,我们可以介入互询机制,让参与者 A 去询问其他参与者 B 的执行情况。如果 B 执行了 rollback 或 commit 操作,则 A 可以大胆的与 B 执行相同的操作;如果 B 此时还没有到达 READY 状态,则可以推断出协调者发出的肯定是 rollback 通知;如果 B 同样位于 READY 状态,则 A 可以继续询问另外的参与者。只有当所有的参与者都位于 READY 状态时,此时两阶段提交协议无法处理,将陷入长时间的阻塞状态。
二阶段提交无法解决的问题
协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。其余参与者通过互询机制无法得到可靠的状态,所以这条事务的状态是不确定的,没人知道事务是否被已经提交。
二阶段提交存在着诸如同步阻塞、单点问题、脑裂等缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。
3.1.3三阶段提交协议(3PC:Three-Phase Commit)
针对两阶段提交存在的问题,三阶段提交协议通过引入一个 预询盘 阶段,以及超时策略(协调者和参与者都有超时机制)来减少整个集群的阻塞时间,提升系统性能。三阶段提交的三个阶段分别为:预询盘(can_commit)、预提交(pre_commit),以及事务提交(do_commit)。
第一阶段:预询盘(can_commit)
该阶段协调者会去询问各个参与者是否能够正常执行事务,参与者根据自身情况回复一个预估值,相对于真正的执行事务,这个过程是轻量的,具体步骤如下:
1.协调者向各个参与者发送事务询问通知,询问是否可以执行事务操作,并等待回复;
2.各个参与者依据自身状况回复一个预估值,如果预估自己能够正常执行事务就返回确定信息,并进入预备状态,否则返回否定信息。
第二阶段:预提交(pre_commit)
本阶段协调者会根据第一阶段的询盘结果采取相应操作,询盘结果主要有 3 种:
1.所有的参与者都返回确定信息。
2.一个或多个参与者返回否定信息。
3.协调者等待超时。
针对第 1 种情况,协调者会向所有参与者发送事务执行请求,具体步骤如下:
1.协调者向所有的事务参与者发送事务执行通知;
2.参与者收到通知后执行事务但不提交;
3.参与者将事务执行情况返回给客户端。
在上述步骤中,如果参与者等待超时,则会中断事务。 针对第 2 和第 3 种情况,协调者认为事务无法正常执行,于是向各个参与者发出 abort 通知,请求退出预备状态,具体步骤如下:
1.协调者向所有事务参与者发送 abort 通知;
2.参与者收到通知后中断事务,本次事务结束,不再执行第三阶段。
第三阶段:事务提交(do_commit)
如果第二阶段事务未中断,那么本阶段协调者将会依据事务执行返回的结果来决定提交或回滚事务,分为 3 种情况:
1.所有的参与者都能正常执行事务。
2.一个或多个参与者执行事务失败。
3.协调者等待超时。
1)事务提交
针对第 1 种情况,协调者向各个参与者发起事务提交请求,具体步骤如下:
1.协调者向所有参与者发送事务 commit 通知;
2.所有参与者在收到通知之后执行 commit 操作,并释放占有的资源;
3.参与者向协调者反馈事务提交结果。
2)事务回滚
针对第 2 和第 3 种情况,协调者认为事务无法成功执行,于是向各个参与者发送事务回滚请求,具体步骤如下:
1.协调者向所有参与者发送事务 rollback 通知;
2.所有参与者在收到通知之后执行 rollback 操作,并释放占有的资源;
3.参与者向协调者反馈事务回滚结果。
三阶段提交无法解决问题
与两阶段提交不同的是,三段提交对于协调者(Coordinator)和参与者(Cohort)都设置了超时机制,在本阶段如果因为协调者或网络问题,导致参与者迟迟不能收到来自协调者的 commit 或 rollback 请求,那么参与者将不会如两阶段提交中那样陷入阻塞,而是等待超时后继续 commit。
例如:协调者发出的是abort请求,但只有一个参与者收到,其他参与者等待超时后仍然会执行commit操作。
相对于两阶段提交虽然降低了同步阻塞,但仍然无法完全避免数据的不一致。
两阶段提交协议中所存在的长时间阻塞状态发生的概率还是非常低的,所以虽然三阶段提交协议相对于两阶段提交协议对于数据强一致性更有保障,但是因为效率问题,两阶段提交协议在实际系统中反而更加受宠。
3.2模式
3.2.1XA模式
XA就是X/Open DTP定义的交易中间件与数据库之间的接口规范,基于数据库的XA协议来实现2PC又称为XA方案。
角色划分:
1、AP:应用程序
2、RM:资源管理器,可理解为事务参与者,一般情况下指一个数据库实例。
3、TM:事务管理器,负责协调和管理事务,事务管理器控制着全局事务,管理事务生命周期并协调各个RM。
4、DTP模型定义TM和RM之间通讯的接口规范叫XA协议,可连接为数据库提供的2PC接口协议。
5、角色之间的交互方式:
1)TM(事务管理器)向AP(应用程序)提供应用程序编程接口,AP通过TM提交及回滚事务。
2)TM交易中间件通过XA接口来通知RM数据库事务的开始、结束以及提交、回滚等。
第一阶段:
应用程序调用事务管理器,TM会通知数据库进行预提交操作(各个数据库中本地事务未提交),此时数据库资源锁定(行锁)。
第二阶段:
如果回复都成功,则TM通知各个数据库可以commit数据了,资源锁释放。
如果回复有一个失败,则TM通知各个数据库rollback数据,资源锁释放。
优点:
1、强一致性
缺点:
1、数据库需支持XA协议,不支持XA协议则无法控制。
2、资源锁(行锁)需要等待两个阶段都提交或回滚后才会释放,效率非常低。
框架解决方案:
1、JTA(Java Transaction API) :java原生对XA的具体实现
2、Atomikos:基于JTA的框架实现
3.2.2AT模式
AT 模式是基于2PC协议的一种无侵入分布式事务解决方案。这里以seata为例说明
一阶段:
在一阶段,Seata 会拦截“业务 SQL”,首先解析 SQL 语义,找到“业务 SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务 SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。
二阶段提交:
二阶段如果是提交的话,因为“业务 SQL”在一阶段已经提交至数据库, 所以 Seata 框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。
二阶段回滚:
二阶段如果是回滚的话,Seata 就需要回滚一阶段已经执行的“业务 SQL”,还原业务数据。回滚方式便是用“before image”还原业务数据;但在还原前要首先要校验脏写,对比“数据库当前业务数据”和 “after image”,如果两份数据完全一致就说明没有脏写,可以还原业务数据,如果不一致就说明有脏写,出现脏写就需要转人工处理。
优点:
1、模式是无侵入的分布式事务解决方案,适用于不希望对业务进行改造的场景,几乎0学习成本。
2、无需整个过程占用资源锁。
缺点:
1、性能一般。
2、可能会有一致性问题。
框架解决方案:
1、Seata
3.2.3TCC模式
TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。
分布式事务成功:
分布式事务失败:
一阶段Try:
资源的检测和预留;
二阶段Confirm:
执行的业务操作提交;要求 Try 成功 Confirm 必须成功(否则无法回滚,只能人工干预,这也是很多同学的理解误区,comfirm执行失败是无法回滚的,切记),失败了需要进行重试;
二阶段Cancel:
预留资源释放,即对try阶段的数据回滚;
注意事项:
1、业务模型分两阶段设计:
用户接入 TCC ,最重要的是考虑如何将自己的业务模型拆成两阶段来实现。
以“扣钱”场景为例,在接入 TCC 前,对 A 账户的扣钱,只需一条更新账户余额的 SQL 便能完成;但是在接入 TCC 之后,用户就需要考虑如何将原来一步就能完成的扣钱操作,拆成两阶段,实现成三个方法,并且保证一阶段 Try 成功的话 二阶段 Confirm 一定能成功。
如上图所示,Try 方法作为一阶段准备方法,需要做资源的检查和预留。在扣钱场景下,Try 要做的事情是就是检查账户余额是否充足,预留转账资金,预留的方式就是冻结 A 账户的 转账资金。Try 方法执行之后,账号 A 余额虽然还是 100,但是其中 30 元已经被冻结了,不能被其他事务使用。
二阶段 Confirm 方法执行真正的扣钱操作。Confirm 会使用 Try 阶段冻结的资金,执行账号扣款。Confirm 方法执行之后,账号 A 在一阶段中冻结的 30 元已经被扣除,账号 A 余额变成 70 元 。
如果二阶段是回滚的话,就需要在 Cancel 方法内释放一阶段 Try 冻结的 30 元,使账号 A 的回到初始状态,100 元全部可用。
用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。
2、允许空回滚:
Cancel 接口设计时需要允许空回滚。在 Try 接口因为丢包时没有收到,事务管理器会触发回滚,这时会触发 Cancel 接口,这时 Cancel 执行时发现没有对应的事务 xid 或主键时,需要返回回滚成功。让事务服务管理器认为已回滚,否则会不断重试,而 Cancel 又没有对应的业务数据可以进行回滚。
3、防悬挂控制:
悬挂的意思是:Cancel 比 Try 接口先执行,出现的原因是 Try 由于网络拥堵而超时,事务管理器生成回滚,触发 Cancel 接口,而最终又收到了 Try 接口调用,但是 Cancel 比 Try 先到。按照前面允许空回滚的逻辑,回滚会返回成功,事务管理器认为事务已回滚成功,则此时的 Try 接口不应该执行,否则会产生数据不一致,所以我们在 Cancel 空回滚返回成功之前先记录该条事务 xid 或业务主键,标识这条记录已经回滚过,Try 接口先检查这条事务xid或业务主键如果已经标记为回滚成功过,则不执行 Try 的业务操作。
4、TCC 幂等控制:
幂等性的意思是:对同一个系统,使用同样的条件,一次请求和重复的多次请求对系统资源的影响是一致的。因为网络抖动或拥堵可能会超时,事务管理器会对资源进行重试操作,所以很可能一个业务操作会被重复调用,为了不因为重复调用而多次占用资源,需要对服务设计时进行幂等控制,通常我们可以用事务 xid 或业务主键判重来控制。
优点:
1、具体业务来实现控制资源锁的粒度变小,不会锁定整个资源,性能提升。
2、基于 Confirm 和 Cancel 的幂等性,保证事务最终完成确认或者取消,保证数据的一致性。
缺点:
1、Try、Confirm和Cancel操作功能需业务提供,开发成本高,业务侵入高。
框架解决方案:
1、tcc-transaction
2、Hmily
3、ByteTcc
4、EasyTransaction
5、Seata
3.2.4Saga模式
Saga 理论出自 Hector & Kenneth 1987发表的论文 Sagas。
saga模式的实现,是长事务解决方案。
Saga 是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。
如图:T1T3都是正向的业务流程,都对应着一个冲正逆向操作C1C3
Saga的执行顺序有两种:
T1, T2, T3, …, Tn
T1, T2, …, Tj, Cj,…, C2, C1,其中0 < j < n
Saga定义了两种恢复策略:
backward recovery,向后恢复,补偿所有已完成的事务,如果任一子事务失败。即上面提到的第二种执行顺序,其中j是发生错误的sub-transaction,这种做法的效果是撤销掉之前所有成功的sub-transation,使得整个Saga的执行结果撤销。
forward recovery,向前恢复,重试失败的事务,假设每个子事务最终都会成功。适用于必须要成功的场景,执行顺序是类似于这样的:T1, T2, …, Tj(失败), Tj(重试),…, Tn,其中j是发生错误的sub-transaction。该情况下不需要Ci。
显然,向前恢复没有必要提供补偿事务,如果你的业务中,子事务(最终)总会成功,或补偿事务难以定义或不可能,向前恢复更符合你的需求。
理论上补偿事务永不失败,然而,在分布式世界中,服务器可能会宕机,网络可能会失败,甚至数据中心也可能会停电。在这种情况下我们能做些什么? 最后的手段是提供回退措施,比如人工干预。
Saga 模式使用场景
Saga 模式适用于业务流程长且需要保证事务最终一致性的业务系统,Saga 模式一阶段就会提交本地事务,无锁、长流程情况下可以保证性能。
事务参与者可能是其它公司的服务或者是遗留系统的服务,无法进行改造和提供 TCC 要求的接口,可以使用 Saga 模式。
与TCC实践经验相同的是,Saga 模式中,每个事务参与者的冲正、逆向操作,需要支持:
1、空补偿:逆向操作早于正向操作时;
2、防悬挂控制:空补偿后要拒绝正向操作
3、幂等
优点:
1、一阶段提交本地数据库事务,无锁,高性能;
2、参与者可以采用事务驱动异步执行,高吞吐;
3、补偿服务即正向服务的“反向”,易于理解,易于实现;
缺点:
1、Saga 模式由于一阶段已经提交本地数据库事务,且没有进行“预留”动作,所以不能保证隔离性。
2、编程模型太复杂。例如,开发人员必须设计能够回滚的补偿事务