引言
最近在做会员体系的时候,涉及到了积分商城订单体系,既然设计到订单体系,因为现在的项目架构体系基本都是微服务分布式的,所以必然会涉及到分布式事务。事务问题在早些时候都是单机部署的时代依靠数据库本身提供的事务机制非常容易解决,但是一旦设计到分布式,各个独立的服务是无法感知其他事务执行状态的,所以我们需要借助一些其他手段来保证分布式事务
分布式理论
-
CAP
- C(一致性)一致性是指数据的原子性,在经典的数据库中通过事务来保障,事务完成时,无论成功或回滚,数据都会处于一致的状态,在分布式环境下,一致性是指多个节点数据是否一致;
- A(可用性)服务一直保持可用的状态,当用户发出一个请求,服务能在一定的时间内返回结果;
- P(分区容错性)机器故障、网络故障、机房停电等异常情况下仍然能够满足一致性和可用性,分布式应用一般都满足分区容错性
分布式事务我们要解决的就是数据的一致性问题
-
一致性模型
- 基本可用(Basically Available)
- 软状态(Soft State)
- 最终一致性(Eventually Consistent)
其中最终一致性又可分为:会话一致性 单调一致性等等
2PC (Two/Three Phase Commit)
2PC 我们又把它叫做两阶段提交,在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的算法思路可以概括为: 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。
前提
二阶段提交算法的成立基于以下假设:
- 该分布式系统中,存在一个节点作为协调者(Coordinator),其他节点作为参与者(Participants)。且节点之间可以进行网络通信。
- 所有节点都采用预写式日志,且日志被写入后即被保持在可靠的存储设备上,即使节点损坏不会导致日志数据的消失。
- 所有节点不会永久性损坏,即使损坏后仍然可以恢复。
第一阶段(提交请求阶段)
- 协调者节点向所有参与者节点询问是否可以执行提交操作,并开始等待各参与者节点的响应。
- 参与者节点执行询问发起为止的所有事务操作,并将
Undo
信息和Redo
信息写入日志。 - 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功,则它返回一个"同意"消息;如果参与者节点的事务操作实际执行失败,则它返回一个"中止"消息。
有时候,第一阶段也被称作投票阶段,即各参与者投票是否要继续接下来的提交操作。
第二阶段(提交执行阶段)
- 成功
当协调者节点从所有参与者节点获得的相应消息都为"同意"时:- 协调者节点向所有参与者节点发出"正式提交"的请求。
- 参与者节点正式完成操作,并释放在整个事务期间内占用的资源。
- 参与者节点向协调者节点发送"完成"消息。
- 协调者节点收到所有参与者节点反馈的"完成"消息后,完成事务。
- 失败
如果任一参与者节点在第一阶段返回的响应消息为"终止",或者 协调者节点在第一阶段的询问超 时之前无法获取所有参与者节点的响应消息时:- 协调者节点向所有参与者节点发出"回滚操作"的请求。
- 参与者节点利用之前写入的Undo信息执行回滚,并释放在整个事务期间内占用的资源。
- 参与者节点向协调者节点发送"回滚完成"消息。
- 协调者节点收到所有参与者节点反馈的"回滚完成"消息后,取消事务。
有时候,第二阶段也被称作完成阶段,因为无论结果怎样,协调者都必须在此阶段结束当前事务
缺点
二阶段提交算法的最大缺点就在于: 它的执行过程中间,节点都处于阻塞状态。即节点之间在等待对方的响应消息时,它将什么也做不了。特别是,当一个节点在已经占有了某项资源的情况下,为了等待其他节点的响应消息而陷入阻塞状态时,当第三个节点尝试访问该节点占有的资源时,这个节点也将连带陷入阻塞状态。
TCC 补偿型
上面大篇幅的介绍了2PC提交,但实际的应用中我们基本都不会使用这种方案,原因在上面的缺点中已经说明了,2PC这种传统的分布式事务解决方案性能实在是太差了,在互联网大并发的背景下显然是不行的,这就引申出了TCC补偿性分布式事务解决方案
TCC是(Try-Confirm-Cancel
)的简称,从名字我们知道TCC有三个阶段
- Try
Try阶段主要做资源的预留,锁定操作 - Confirm
如果Try预留资源成功,则执行Confirm操作,对资源做最终的提交 - Cancel
如果Try预留资源失败,则执行Cancel取消对资源的锁定
通过上文的描述,可以发现TCC与2PC非常相似,但实际上两者在解决分布式事务的层面上不一样的,2PC主要还是借助数据库层面的事务来协调解决,相对于数据库事务的rollback
,TCC在逻辑层面的Cancel操作,代价要小的多。并且TCC事务引入了中间状态(也就是资源的锁定),只要全部资源都锁定成功,我们就认为最终是执行成功的(最终一致性)
下图是积分下单用TCC事务解决的典型场景
image.png
TCC是如何解决最终一致性的?
当我们在Try
阶段预留资源成功的话,那么我们就认为最终这个事务是肯定可以完成的,即使因为某些原因(数据库down了等的)Confirm
执行失败,TCC事务框架会不停的重试调用它的Confirm
逻辑,务必会保证其最终一致性
典型应用场景
订单系统
事务消息
事务消息的应用场景是,事务参与方的资源已经锁定,只需要保持最终一致性的场景。比较典型的实例是银行转账,当A账户完成账户扣减后,B账户不需要锁定账户,只需要保证最终B账户可以增加指定金额
image.png为何需要先发送 Prepare消息?
试想一种场景,本地事务执行成功,准备发送消息的时候断网了,这时候就会造成数据不一致的情况
commit or rollback 消息发送失败?
这时候需要消息中间件提供消息回查功能,也就是当间隔一段时间之后,Prepare消息没有收到 commit or rollback消息,需要发起消息回查,并且由业务方判断最终消息是否需要投递
对于事务消息的解决方案,阿里的RocketMq
也提供了解决方案
总结
总体来说,每种分布式事务解决方案都有其应用场景,但目前业界比较主流的还是TCC和事务消息,TCC事务需要业务实现Try-Confirm-Cancel
逻辑,需要在Try阶段提前锁定资源,相对于事务消息来说成本较高,但是事务消息的适用场景也是有限的,如下单扣减库存这个场景因为需要提前锁定库存,事务消息就不适用了。
网友评论