分布式事务的几种实现模式

基本概念

XA标准

X/Open组织很早就提出的分布式事务处理(DTP)规范[1],对事务的状态、执行、接口都做了定义。当前主流的数据库包括一些编程语言标准库上都有一些支持。

基本架构

XA事务基本的构成部分如下图所示:

  • AP: 应用程序,通过事务接口访问RM
  • RM(参与者):资源池,一般是数据库
  • TM(协调者):负责事务的执行和恢复

image.png

二阶段执行流程(2PC)

二阶段执行是XA标准中定义的流程,过程如下[4]。在分布式环境下,将多个RM上执行事务和提交事务拆分成两阶段,确保了所有事务可以一起提交或者有个别事务执行失败时可以回滚。整个过程可以类比quorum选举投票的模式,RM是voter,TM是协调者coordinator,只有所有voter都投票通过,TM才允许所有voter真正的执行commit。
image.png

3PC

原始论文中的2PC确实有不少问题,显著问题应该是coordinatoe和voter一起crash时的阻塞问题。3PC通过引入pre-commit状态和超时机制做了一些改进,使得协调器配合voter以及中间状态可以配合超时机制做commit或者rollback。
不过话说回来,论文的3PC一样有很多问题,并且没有被生产实践过,看看就好。基于2PC的基本模式下,数据库仍然是可以实现生产可用的分布式事务的,并没有原始论文中那么不堪,很多问题都是可以通过工程解决的,例子的话可以看看[6]。XA以及2PC更多是提供了一个处理分布式事务的基本模式,其中很多细节问题还是得根据具体的数据库系统去设计和实现的。

分布式数据库实现分布式事务

这里我们说的分布式事务实现主要指的是分布式数据库其内部实现分布式事务的几种方式。由于分布式数据库由网络中由多个节点一起构成,数据有分片,因此多个事务分布式写入到各个数据节点时需要通过分布式事务保证事务原子性。这个需要区别于数据库本身支持的XA协议。数据库支持XA协议表示数据库本身可以作为一个RM然后配合业务应用作为AP再结合TM一起完成整体的一个分布式事务。需要注意两者的区别。

基于XA标准实现

基于XA的2PC来实现数据库内部分布式事务的商业数据库包括OceanBase、PolarDB-X、PostgreSQL-XC等。他们在数据库内部参考XA标准来进行状态管理、2PC执行等,可以认为是XA标准实现的变种。

MVCC+共识+TSO

precolator模型

Percolator模型最早是在2010年,由Google论文《Large-scale Incremental Processing Using Distributed Transactions and Notifications》提出。

Google Percolator核心设计特点是:

  • 构建与bigtable之上,在bigtable上存储data、lock、write多个列簇信息
  • 引入全局时钟TSO:关键是分配全局单调递增的分布式唯一事务ID
  • 改进的2PC,没有中心化的coordinator。采用自定义的分布式事务协议,将锁控制在更加细的粒度,可以避免2PC锁范围过大的问题

缺点:

  • 提交阶段延迟较高
  • TSO单点问题

基于precolator模型优化改进实现分布式事务的有PingCAP的TiDB

Omid模型

yahoo出品,Omid顾名思义,《Optimistically transaction Management In Datastores》。可以理解成乐观版本的precolator。比较接近precolator,但是存储是基于hbase的。
特点是:

  • 相比precolator更加注重乐观事务的处理,lock-free的并发控制没有死锁检测
  • 也有TSO,事务的驱动交给TSO

Transaction flow

Calvin

12年提出,属于deterministic database的概念,对于需要处理的事务,Calvin 会在全局确定好事务的顺序,并按照这个提前确定的顺序执行。典型的实现是支持ACID的内存数据库VoltDB

数据库分布式事务实现总结

数据库分布式事务实现的模式比较多,国内现在主流的分布式事务主要还是通过TSO+MVCC+2PC+paxos来实现,像TiDB、OceanBase、PolarDB-X等。

分布式事务中间件

在微服务和云原生的趋势下,催生了分布式事务中间件。注意分布式事务中间件和数据库分布式事务解决的问题是不太一样的。数据库分布式事务是为了解决分布式数据库内部多个数据节点写入时的原子性问题。分布式事务中间件是为了协调分布式的微服务之间原子写入到底层数据库的。

当然,分布式事务的核心理念是差不多的,不过基于中间件的分布式事务和数据库层面实现分布式事务还是有一些不同。中间件来实现分布式事务毕竟不是实现一个数据库,而是在数据库上构建一个分布式事务层,制约会少一些,可以更加聚焦于事务协调相关的工作,和业务代码之间交互更加紧密,针对是否有业务入侵就可以分为多种模式。

现在主流的分布式事务中间件有dtm、seata、rocketmq(靠事务消息)等。

XA模式

区别于分布式数据库实现XA标准是为了实现其内部分布式事务的能力,DTP中间件的XA模式指的是DTP中间件对接实现了XA协议的数据库,让数据库本身可作为一个RM,然后应用程序作为AP和数据库整体一起工作执行分布式事务。

  • 优点:
    • 模型简单开发友好:因为本质上把分支事务(本地事务)的执行委托了底下支持XA协议的数据库了,DTP中间件开发者不需要关心分支事务上的资源锁定、分支事务的回滚和提交。
    • 较薄的RM层方便多语言SDK研发:采用XA协议实现的DTP中间件,其本身的RM层就可以做的比较薄也不涉及自己SQL解析和补偿,扩展多语言SDK也会简单很多。
    • 业务无入侵:对于业务来说,RM的职责基本委托给数据库了,TM做好基本的协调业务可以完全无感像执行本地事务一样
    • 全局一致性:相比于TCC、SAGA的补偿性方案,XA是全局强一致的
  • 缺点:
    • DB兼容性不是最好:依赖数据库本身支持XA协议,数据库的兼容性和适用性相比DTP直接完全接管RM职责的实现方式会差些。
    • 资源锁定:全局一致性带来的副作用、如果数据库层面没有做很好的优化的话,这种资源锁定会影响事务执行的性能。数据库层面可以利用MVCC做好读写并行尽量提升一些并发。
    • XA 2PC的崩溃恢复问题:一般需要中间件处理好prepare阶段voter或者协调器crash的问题
  • 适用场景:由于大部分DB对XA协议支持都比较简陋,XA模式适合并发不高、大事务的场景。高并发时性能不行,大事务失败了全部回滚代价又高。

Saga模式

Saga是一种基于补偿的分布式事务处理方式,来源于论文[16],专门处理长事务。核心是将长事务拆成若干个子事务,每个子事务有自身对应的补偿操作。通过对子事务编排可以并发执行,支持前向(重试)或者后向(回滚)恢复。实现上可以通过状态机引擎去实现,参考
优点:

  • 适合长事务、复杂事务的场景

缺点:

  • 实现复杂、用户用起来也复杂

TCC

TCC本质上和2PC理念是一致的,只不过主要特点是让用户自己去控制prepare、commit、rollback,是一种有业务入侵的策略。相关论文可以参考《Life beyond Distributed Transactions:an Apostate’s Opinion》。TCC将分布式事务的隔离性交给用户自己去控制了,这有利有弊。好处是用户如果自己控制的好,有更好的性能并且满足一致性,使用上也很灵活。缺点是如果自己控制的不好,不仅代码复杂度提升,还容易引入数据问题。

一般而言TCC的过程如下[14]:

  • Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
  • Confirm 阶段:确认执行真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源,Confirm 操作要求具备幂等设计,Confirm 失败后需要进行重试。
  • Cancel 阶段:取消执行,释放 Try 阶段预留的业务资源。Cancel 阶段的异常和 Confirm 阶段异常处理方案基本上一致,要求满足幂等设计。

AT模式

AT模式实际上就是手动挡模式了,由DTP中间件完全接管分布式事务的实现。不过基本理念也是符合XA的理念的,DTP中间件基本上也是会设计类似XA标准中RM、TM、AP这样的角色然后基于2PC的理念去实现。AT模式的优缺点完全取决于其本身如何实现的。以seata为例,其更加侧重执行事务的性能,因此牺牲了一些一致性。在分布式事务执行时会存在脏读和脏写的问题,得配合seata的一些机制才能解决。

事务消息

利用消息来完成分布式事务的灵感最早可以从追溯到Dan Pritchett 2008年ACM的论文[20]。那时候的消息系统没事务消息,因此这种玩法没真正流行起来。后来像RocketMQ这类消息系统支持了事务消息后,利用消息来完成分布式事务也成为一个可选项。

事务消息的基本工作流程如下[21]:
image.png

优点:

  • 性能还不错:相比XA事务锁定时间过长的情况通过消息可以有更好的性能。
  • 复用MQ:企业内如果本来就使用了消息队列,可以利用MQ事务消息的能力处理一些匹配的分布式事务场景

缺点:

  • 非强一致:存在不一致窗口,取决于消息发送的延迟。延迟如果增大,增可能出现脏读的情况
  • 不支持复杂依赖关系的事务:不像TCC、SAGA等方式可以编排事务。使用MQ不太适合子事务有复杂依赖关系、链式依赖关系的场景。

下图是电商场景下通过事务消息完成分布式事务的场景:
image.png

参考资料