关注我们,设为星标,每天7:30不见不散,架构路上与您共享 回复"架构师"获取资源
在微服务架构盛行的情况下,在分布式的多个服务中保证业务的一致性,即分布式事务就显得尤为重要。本文将讲述分布式事务及其解决方案,有XA协议、TCC和Saga事务模型、本地消息表、事务消息和阿里开源的Seata。聊什么是分布式事务前,先聊一下我们熟悉的单机事务。所谓单机事务是相对分布式事务来说的,即数据库事务。大家都知道数据库事务有ACID这四个特性:A(Atomicity):指单个事务中的操作要不都执行,要不都不执行
C(Consistency):指事务前后数据的完整性必须保持一致
I(Isolation):指多个事务对数据可见性的规则
D(Durability):指事务提交后,就会被永久存储下来
既然数据库事务有这四个特性的,那么分布式事务也不例外,应该具备这四个特性。在微服务架构下,服务之间通过RPC远程调用,相对单机事务来说,多了“网络通信”这一不确定因素,使得本来服务的调用只有“成功”和“失败”这两种返回结果,变为“成功”、“失败”和“未知”三种返回结果。系统之间的通信可靠性从单一系统中的可靠变成了微服务架构之间的不可靠,分布式事务其实就是在不可靠的通信下实现事务的特性。一般因为网络导致的异常可能有机器宕机、网络异常、消息丢失、消息乱序、数据错误、不可靠的TCP、存储数据丢失、其他异常等等。
二阶段提交(英语:Two-phase Commit)是指在计算机网络以及数据库领域内,为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法。通常,二阶段提交也被称为是一种协议(Protocol)。2PC是一种协议,它的作用保证在分布式系统中每个节点要不都提交事务,要么都取消事务。这个跟ACID中的A原子性的定义很像。2PC引入一个第三方的节点协调者,即Coordinator,其他参与事务的节点为参与者,即Participants。协调者统筹整个事务行为,负责通知参与者进行Commit还是Rollback操作。
2PC是一个强一致性协议,同时它在实际应用中还存在几个问题:
3PC即三阶段提交,它比2PC多了一个阶段,即把原来2PC的准备阶段拆分成CanCommit和PreCommit两个阶段,同时引入超时机制来解决2PC的同步阻塞问题。但是在我看来3PC并没有解决2PC的根本问题,它只是在2PC的基础上做了一些优化,它增加了一个阶段(也增加了1个RTT)来提高对方可用性的概率,这本质跟TCP的三次握手一样,同样也改为四次握手,五次握手等等。XA是一种基于2PC协议实现的规范。在2PC中没有明确资源是什么,以及资源是怎么提交的等等,而XA就是数据库实现2PC的规范,已知常用的支持XA的关系型数据库有Mysql、Oracle等。本地消息表方案应该是业界内使用最为广泛的,因为它使用简单,成本比较低。本地消息表的方案最初是由eBay提出(完整方案),核心思路是将分布式事务拆分成本地事务进行处理。
从处理流程来看,本地消息表方案是一个基于消息中间件的可靠性来达到事务的最终一致性的方案。
本地消息表方案整体来说还是比较简单、可用的,但是也有以下缺点:
事务消息是通过消息中间件来解耦本地消息表和业务数据表,适用于所有对数据最终一致性需求的场景。现在支持事务消息的消息中间件只有RocketMQ,这个概念最早也是RocketMQ提出的。
RocketMQ会定期扫描还没确认的消息,回调给发送方,询问此次事务的状态,根据发送方的返回结果把这条消息进行取消还是提交确认。可以看出事务消息的本质的借鉴了二阶段提交的思想,它跟本地消息表的做法也很像,事务消息做的事情其实就是把消息表的存储和扫描消息表这两个事情放到消息中间件来做,使得消息表和业务表解耦。TCC (Try-Confirm-Cancel)事务模型采用的是补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿操作。相当于XA来说,TCC可以不依赖于资源管理器,即数据库,它是通过业务逻辑来控制确认和补偿操作的,所以它用了’Cancel’而非’Rollback’的字眼。它是一个应用层面的2PC。
看上去TCC跟2PC/3PC可能有点像,但是TCC强调的是补偿,而且对于对资源的“预留”,“确认”,“释放”,TCC并没有明确说要如何做,这个具体是要业务来定义的。例如在转账的场景,“预留”操作可能就是对账号里的部分资金进行冻结,这样这个资金只能是当前事务才能用,别的事务用不了。另外,对于异常的场景,TCC也没有说要怎么做,因为Try、Confirm、Cancel都是业务定义的,这三个阶段中发生了异常,那么就由业务来做相应的处理。一般都有以下几种处理:如果Try成功了,那么Confirm阶段异常了就一直重试,直到成功
Try、Confirm、Cancel三个阶段都有相应的资源及事务日志,应用根据日志(异步)来做重试或补偿
TCC的实现依赖底层数据库,异常后直接利用数据库的事务机制回滚
其中现在使用比较多的TCC框架ByteTCC、tcc-transaction的原理都是基于第三点。
TCC其实是把控制事务的逻辑放在业务应用层面,而非资源管理器,这样实现起来就会相对灵活很多,但相对对数据一致性的保证可能没那么强(具体看怎么实现Try),整体来说TCC还有以下缺点:对于Confirm和Cancel阶段失败后要完全靠业务应用自己去处理
每个业务都需要实现Try、Confirm、Cancel三个接口,代码量比较多
如果是基于现有的业务想使用TCC会比较困难。一是对于原来的接口要拆分为三个接口,入侵性比较大;二是因为要做“预留”资源的操作,有可能需要对原来的业务模型进行改造。
Saga事务模型又叫做长时间运行的事务(Long-running-transaction), 它是由普林斯顿大学的H.Garcia-Molina等人提出,它描述的是另外一种在没有两阶段提交的的情况下解决分布式系统中复杂的业务事务问题。该模型其核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,然后由 Saga工作流引擎负责协调,如果整个流程正常结束,那么就算是业务成功完成,如果在这过程中实现失败,那么Saga工作流引擎就会以相反的顺序调用补偿操作,重新进行业务回滚。Saga也是一种补偿协议,在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。你可以看到Saga跟TCC很像,但是Saga更加宽松,一致性更弱,在Saga看来,在一阶段直接做提交/确认操作就好了,有问题再做补偿。这样的话,Saga可以拥有比XA和TCC更好的性能(XA、TCC需要锁定资源或预留资源),而且Saga强调通过事件驱动异步处理,实现高吞吐。可以看出Saga是对TCC的一种“妥协”,从TCC的三个接口变为两个接口,一阶段直接提交缺少对资源的隔离(如果一阶段提交后,后面发现需要做补偿,但是补偿操作执行前有另外的事务更改了数据,这时数据已经变“脏”了,那么这时该如何处理是一个问题。在TCC没有这个问题,因为资源已经被hold住了),因此对使用者也是比较宽松的,对于现有业务的改造也会比较简单。Saga实现分两种,一种是Saga状态机实现,一种是Saga AOP Proxy实现。Saga状态机实现,在关于参与者服务编排实现又有集中式和协同式两种分支。这点就不展开了。TCC和Saga都属于补偿型事务模型,Saga没有Try,直接Commit,所有会产生实际的事务痕迹,而补偿做的是反向操作。TCC是二阶段的广义实现,利用了数据的中间态,Cancel是中间状态的数据进行撤销,从而不存在数据污染问题。
Seata是一个由阿里做背书的分布式事务框架,致力于提供高性能和简单易用的分布式事务服务。Seata将为用户提供了AT、TCC、SAGA和XA事务模式,为用户打造一站式的分布式解决方案。AT模式是Seata通过拦截、解释用户的SQL,对业务数据进行加锁、回滚等操作的基于二阶段协议的一个实现。它的特点是对业务无入侵,用户只需关注自己的“业务SQL”,用户的“业务 SQL”作为一阶段,Seata框架会自动生成事务的二阶段提交和回滚操作。在一阶段,Seata会拦截“业务SQL”,首先解析SQL语义,找到“业务SQL”要更新的业务数据,在业务数据被更新前,将其保存成“before image”,然后执行“业务SQL”更新业务数据,在业务数据更新之后,再将其保存成“after image”,最后生成行锁。以上操作全部在一个数据库事务内完成,这样保证了一阶段操作的原子性。二阶段如果是提交的话,因为“业务SQL”在一阶段已经提交至数据库, 所以Seata框架只需将一阶段保存的快照数据和行锁删掉,完成数据清理即可。Seata的TCC模式跟上面讲的TCC事务模型差不多。Saga模式也是上面讲的Saga事务模型差不多。在Seata中对服务的编排引入了状态机引擎, 使得对业务流程的定义更加标准化,提高可读性,不过相对来说配置会比较复杂繁琐。同时支持注解的方式,这个在开发上会简单一点,但功能可能少一点。
首先要明确一点的就是对于上述提到的分布式事务解决方案,如TCC、Saga、本地消息表等,其本质都是2PC。Paxos算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。咋看起来2PC和Paxos都是解决关于“一致性”的问题,其实细想它们解决的问题不在一个层面。2PC要求分布式系统中的每个节点要不全部成功,要不全部失败,强调的是原子性。Paxos要求多个副本之间的数据一致性,其实这里用“一致性”并不准确,应该用“共识(Consensus)”才对。例如2PC中的协调者单点的问题可以用Paxos算法通过选举出新的协调者来解决。