分布式事务解决方案以及常用框架
解决方案
-
两阶段提交(2PC)
两阶段提交又称2PC,2PC是一个非常经典的强一致、中心化的原子提交协议。这里所说的中心化是指协议中有两类节点:一个是中心化协调者节点(coordinator)和N个参与者节点(partcipant)。
两个阶段:第一阶段:投票阶段 和第二阶段:提交/执行阶段。
举:订单服务A,需要调用支付服务B去支付,支付成功则处理购物订单为待发货状态,否则就需要将购物订单处理为失败状态。
那么看2PC阶段是如何处理的
第一阶段:投票阶段
第一阶段主要分为3步:
1)事务询问
协调者 向所有的 参与者 发送事务预处理请求,称之为Prepare,并开始等待各 参与者 的响应。
2)执行本地事务
各个 参与者 节点执行本地事务操作,但在执行完成后并不会真正提交数据库本地事务,而是先向 协调者 报告说:“我这边可以处理了/我这边不能处理”。.
3)各参与者向协调者反馈事务询问的响应
如果 参与者 成功执行了事务操作,那么就反馈给协调者 Yes 响应,表示事务可以执行,如果没有 参与者 成功执行事务,那么就反馈给协调者 No 响应,表示事务不可以执行。
第一阶段执行完后,会有两种可能。1、所有都返回Yes. 2、有一个或者多个返回No。第二阶段:提交/执行阶段(成功流程)
第二阶段主要分为两步
1)所有的参与者反馈给协调者的信息都是Yes,那么就会执行事务提交。协调者向所有参与者节点发出Commit请求。
2)事务提交
参与者收到Commit请求之后,就会正式执行本地事务Commit操作,并在完成提交之后释放整个事务执行期间占用的事务资源。第二阶段:提交/执行阶段(异常流程)
异常条件:任何一个参与者向协调者反馈了No响应,或者等待超时之后,协调者尚未收到所有参与者的反馈响应。
异常流程第二阶段也分为两步
1)发送回滚请求
协调者向所有参与者节点发出RoollBack请求.
2)事务回滚
参与者 接收到RoollBack请求后,会回滚本地事务。2PC缺点
通过上面的演示,很容易想到2pc所带来的缺陷
1)性能问题
无论是在第一阶段的过程中,还是在第二阶段,所有的参与者资源和协调者资源都是被锁住的,只有当所有节点准备完毕,事务 协调者 才会通知进行全局提交,
参与者 进行本地事务提交后才会释放资源。这样的过程会比较漫长,对性能影响比较大。
2)单节点故障
由于协调者的重要性,一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于
锁定事务资源的状态中,而无法继续完成事务操作。(虽然协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)2PC出现单点问题的三种情况
(1)协调者正常,参与者宕机
由于协调者无法收集到所有参与者的反馈,会陷入阻塞情况。
解决方案:引入超时机制,如果协调者在超过指定的时间还没有收到参与者的反馈,事务就失败,向所有节点发送终止事务请求。
(2)协调者宕机,参与者正常
无论处于哪个阶段,由于协调者宕机,无法发送提交请求,所有处于执行了操作但是未提交状态的参与者都会陷入阻塞情况.
解决方案:引入协调者备份,同时协调者需记录操作日志.当检测到协调者宕机一段时间后,协调者备份取代协调者,并读取操作日志,向所有参与者询问状态。
(3)协调者和参与者都宕机
1、发生在第一阶段:因为第一阶段,所有参与者都没有真正执行commit,所以只需重新在剩余的参与者中重新选出一个协调者,新的协调者在重新执行第一阶段和第二阶段就可以了。
2、发生在第二阶段并且挂了的参与者在挂掉之前没有收到协调者的指令。也就是上面的第4步挂了,这是可能协调者还没有发送第4步就挂了。这种情形下,新的协调者重新执行第一阶段和第二阶段操作。
3、发生在第二阶段 并且 有部分参与者已经执行完commit操作。就好比这里订单服务A和支付服务B都收到协调者 发送的commit信息,开始真正执行本地事务commit,但突发情况,Acommit成功,B确挂了。这个时候目前来讲数据是不一致的。虽然这个时候可以再通过手段让他和协调者通信,再想办法把数据搞成一致的,但是,这段时间内他的数据状态已经是不一致的了!2PC 无法解决这个问题。 -
三阶段提交(3PC)
三阶段提交协议(3PC)主要是为了解决两阶段提交协议的阻塞问题,2pc存在的问题是当协作者崩溃时,参与者不能做出最后的选择。因此参与者可能在协作者恢复之前保持阻塞。
三阶段提交(Three-phase commit),是二阶段提交(2PC)的改进版本。
与两阶段提交不同的是,三阶段提交有两个改动点:
1)引入超时机制。同时在协调者和参与者中都引入超时机制。
2)在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
1、CanCommit阶段
之前2PC的一阶段是本地事务执行结束后,最后不Commit,等其它服务都执行结束并返回Yes,由协调者发生commit才真正执行commit。而这里的CanCommit指的是尝试获取数据库锁 如果可以,就返回Yes。这阶段主要分为2步:
事务询问协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。
响应反馈参与者接到CanCommit请求之后,正常情况下,如果其自身认为可以顺利执行事务,则返回Yes响应,并进入预备状态。否则反馈No2、PreCommit阶段
在阶段一中,如果所有的参与者都返回Yes的话,那么就会进入PreCommit阶段进行事务预提交。这里的PreCommit阶段 跟上面的第一阶段是差不多的,只不过这里 协调者和参与者都引入了超时机制 (2PC中只有协调者可以超时,参与者没有超时机制)。3、DoCommit阶段
这里跟2pc的阶段二是差不多的。总结
相比较2PC而言,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制。这解决了一个什么问题呢?这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
另外,通过CanCommit、PreCommit、DoCommit三个阶段的设计,相较于2PC而言,多设置了一个缓冲阶段保证了在最后提交阶段之前各参与节点的状态是一致的。
以上就是3PC相对于2PC的一个提高(相对缓解了2PC中的前两个问题),但是3PC依然没有完全解决数据不一致的问题。
-
TCC ( Try、Confirm、Cancel )
TCC三个操作描述:
1)Try: 检测、预留资源;
2)Confirm: 业务系统执行提交;默认Confirm阶段是不会出错的,只要TRY成功,CONFIRM一定成功;
3)Cancel: 业务取消,预留资源释放;
TCC又称补偿事务。其核心思想是:“针对每个操作都要注册一个与其对应的确认和补偿(撤销操作)”。
它分为三个操作:
1、Try阶段:主要是对业务系统做检测及资源预留。
2、Confirm阶段:确认执行业务操作。
3、Cancel阶段:取消执行业务操作。TCC对应 Try、Confirm、Cancel 三种操作可以理解成关系型数据库事务的三种操作:DML、Commit、Rollback。
在一个跨应用的业务操作中
Try:Try操作是先把多个应用中的业务资源预留和锁定住,为后续的确认打下基础,类似的,DML操作要锁定数据库记录行,持有数据库资源。Confirm:Confirm操作是在Try操作中涉及的所有应用均成功之后进行确认,使用预留的业务资源,和Commit类似;
Cancel:Cancel则是当Try操作中涉及的所有应用没有全部成功,需要将已成功的应用进行取消(即Rollback回滚)。其中Confirm和Cancel操作是一对反向业务操作。
TCC的具体原理图如(盗图):
从图中我们可以明显看到Confirm和Cancel操作是一对反向业务操作即要try返回成功执行Confirm,要么try返回失败执行Cancel操作。
分布式事务协调者:分布式事务协调者管理控制整个业务活动,包括记录维护TCC全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时确认所有的TCC型
操作的confirm操作,在业务活动取消时调用所有TCC型操作的cancel操作。
举例
A服务转30块钱、B服务转50块钱,一起到C服务上。Try:尝试执行业务。完成所有业务检查(一致性):检查A、B、C的帐户状态是否正常,帐户A的余额是否不少于30元,帐户B的余额是否不少于50元。预留必须业务资源
(准隔离性):帐户A的冻结金额增加30元,帐户B的冻结金额增加50元,这样就保证不会出现其他并发进程扣减了这两个帐户的余额而导致在后续的真正转帐操作过程中,帐户A和B的可用余额不够的情况。
Confirm:确认执行业务。真正执行业务:如果Try阶段帐户A、B、C状态正常,且帐户A、B余额够用,则执行帐户A给账户C转账30元、帐户B给账户C转账50元的转帐操作。 这时已经不需要做任何业务检查,Try阶段已经完成了业务检查。只使用Try阶段预留的业务资源:只需要使用Try阶段帐户A和帐户B冻结的金额即可。
Cancel:取消执行业务释放Try阶段预留的业务资源:如果Try阶段部分成功,比如帐户A的余额够用,且冻结相应金额成功,帐户B的余额不够而冻结失败,则需要对帐户A做Cancel操作,将帐户A被冻结的金额解冻掉。
TCC和2PC比较
2PC是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。XA事务中的两阶段提交内部过程是对开发者屏蔽的,事务管理器在两阶段提交过程中,从prepare到commit/rollback过程中,资源实际上一直都是被加锁的。
如果有其他人需要更新这两条记录,那么就必须等待锁释放。
TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。
我的理解就是当执行try接口的时候,已经把所需的资源给预扣了,比如上面举例的A服务已经预扣30元,B服务已经预扣50元,它是由try接口实现,这样就保证不会
出现其他并发进程扣减了这两个帐户的余额而导致在后续的真正转帐操作过程中,帐户A和B的可用余额不够的情况,同时保证不会一直锁住整个资源。(核心点应该就在这)
TCC中的两阶段提交并没有对开发者完全屏蔽,也就是说从代码层面,开发者是可以感受到两阶段提交的存在。
1、try过程的本地事务,是保证资源预留的业务逻辑的正确性。
2、confirm/cancel执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务。
由于是多个独立的本地事务,因此不会对资源一直加锁。
TCC 实质上是应用层的2PC ,好比把 XA 两阶段提交那种在数据资源层做的事务管理工作提到了数据应用层。
2PC是资源层面的分布式事务,是强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。
TCC相比较于2PC来讲性能会好很多,但是因为同时需要改造try、confirm、canel3个接口,开发成本高。
注意:还有一点需要注意的是Confirm和Cancel操作可能被重复调用,故要求Confirm和Cancel两个接口必须是幂等。
常用框架推荐
-
阿里巴巴fescar
-
华为servicecomb-pack
-
Hmily
-
byteTCC
-
tcc-transaction