分布式事务系统

什么是分布式事务?

分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。
简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。
本质上来说,分布式事务就是为了保证不同数据库的数据一致性(不同业务对应的数据库、同一业务分库导致的多个数据库)。

事务特性

事务具有 4 个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为 ACID 特性。

  • Atomicity(原子性):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复到事务开始前的状态,就像这个事务从来没有执行过一样。
  • Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。完整性包括外键约束、应用定义的等约束不会被破坏。
  • Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  • Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
    主流的数据库例如MySQL、PostgreSQL等,都支持ACID事务,其内部会采用MVCC(多版本并发控制)技术,实现高性能、高并发的本地事务。

分布式理论

分布式事务涉及多个节点,是一个典型的分布式系统,与单机系统有非常大的差别。一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项,这被称为CAP理论。

Consistency

分布式系统中,数据一般会存在不同节点的副本中,如果对第一个节点的数据成功进行了更新操作,而第二个节点上的数据却没有得到相应更新,这时候读取第二个节点的数据依然是更新前的数据,即脏数据,这就是分布式系统数据不一致的情况。

Availability

在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
在现代的互联网应用中,如果因为服务器宕机等问题,导致服务长期不可用,是不可接受的

分区容错性

以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在 C 和 A 之间做出选择。

问题取舍

对于多数大型互联网应用的场景,主机众多、部署分散,而且现在的集群规模越来越大,所以节点故障、网络故障是常态,而且要保证服务可用性达到N个9,即保证P和A,舍弃C。

BASE理论

BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的简写,BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网系统分布式实践的结论,是基于CAP定理逐步演化而来的,其核心思想是即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)。

  • 基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性。但请注意,这绝不等价于系统不可用。
  • 弱状态也称为软状态,和硬状态相对,是指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
  • 最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

业界各种事务模式

银行跨行转账业务是一个典型分布式事务场景,假设A需要跨行转账给B,那么就涉及两个银行的数据,无法通过一个数据库的本地事务保证转账的ACID,只能够通过分布式事务来解决。

角色:

  • RM:资源管理器
  • TM:事务管理器
  • ApplicationProgram:应用程序。

示例:

我们要进行一个类似于银行跨行转账的业务,将A中的30元转给B。

两阶段提交/XA

XA是由X/Open组织提出的分布式事务的规范,XA规范主要定义了(全局)事务管理器™和(局部)资源管理器(RM)之间的接口。本地的数据库如mysql在XA中扮演的是RM角色。

特点

  • 简单易理解,开发较容易
  • 对资源进行了长时间的锁定,并发度低

执行逻辑

第一阶段(prepare):即所有的参与者RM准备执行事务并锁住需要的资源。参与者ready时,向TM报告已准备就绪。
第二阶段 (commit/rollback):当事务管理者™确认所有参与者(RM)都ready后,向所有参与者发送commit命令。
目前主流的数据库基本都支持XA事务,包括mysql、oracle、sqlserver、postgre

时序图

在这里插入图片描述

SAGA

Saga一旦到了Cancel阶段,那么Cancel在业务逻辑上是不允许失败了。如果因为网络或者其他临时故障,导致没有返回成功,那么TM会不断重试,直到Cancel返回成功。

特点

  • 并发度高,不用像XA事务那样长期锁定资源
  • 需要定义正常操作以及补偿操作,开发量比XA大
  • 一致性较弱,对于转账,可能发生A用户已扣款,最后转账又失败的情况

执行逻辑

转出(TransOut)服务,这里转出将会进行操作A-30
转出补偿(TransOutCompensate)服务,回滚上面的转出操作,即A+30
转入(TransIn)服务,转入将会进行B+30
转入补偿(TransInCompensate)服务,回滚上面的转入操作,即B-30

时序图

正常case

在这里插入图片描述

异常case

在这里插入图片描述

TCC

什么是TCC,TCC是Try、Confirm、Cancel三个词语的缩写,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。

执行逻辑

  • Try 阶段:尝试执行,完成所有业务检查(一致性), 预留必须业务资源(准隔离性)
  • Confirm 阶段:如果所有分支的Try都成功了,则走到Confirm阶段。Confirm真正执行业务,不作任何业务检查,只使用 Try 阶段预留的业务资源
  • Cancel 阶段:如果所有分支的Try有一个失败了,则走到Cancel阶段。Cancel释放 Try 阶段预留的业务资源。

特点

  • 并发度较高,无长期资源锁定
  • 开发量较大,需要提供Try/Confirm/Cancel接口
  • 一致性较好,不会发生SAGA已扣款最后又转账失败的情况

试用场景

  • 适合短事务
  • 适用于订单类业务,对中间状态有约束的业务

时序图

正常case

在这里插入图片描述

异常case

在这里插入图片描述

本地消息表

本地消息表这个方案最初是 ebay 架构师 Dan Pritchett 在 2008 年发表给 ACM 的文章。设计核心是将需要分布式处理的任务通过消息的方式来异步确保执行。

执行逻辑

在这里插入图片描述
容错机制:

  • 扣减余额事务失败时,事务直接回滚,无后续步骤
  • 轮序生产消息失败, 增加余额事务失败都会进行重试

特点

  • 长事务仅需要分拆成多个任务,使用简单
  • 生产者需要额外的创建消息表
  • 每个本地消息表都需要进行轮询
  • 不支持回滚:消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作
  • 轮询生产消息难实现,如果定时轮询会延长事务总时长,如果订阅binlog则开发维护困难

试用场景

  • 适用于可异步执行的业务,且后续操作无需回滚的业务

事务消息

在上述的本地消息表方案中,生产者需要额外创建消息表,还需要对本地消息表进行轮询,业务负担较重。阿里开源的RocketMQ 4.3之后的版本正式支持事务消息,该事务消息本质上是把本地消息表放到RocketMQ上,解决生产端的消息发送与本地事务执行的原子性问题。

执行逻辑

  • 发送消息(half消息)
  • 服务端存储消息,并响应消息的写入结果
  • 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)
  • 根据本地事务状态执行Commit或者Rollback(Commit操作发布消息,消息对消费者可见)
    在这里插入图片描述

补偿流程

对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”
Producer收到回查消息,返回消息对应的本地事务的状态,为Commit或者Rollback
事务消息方案与本地消息表机制非常类似,区别主要在于原先相关的本地表操作替换成了一个反查接口

特点

  • 长事务仅需要分拆成多个任务,并提供一个反查接口,使用简单
  • 事务消息的回查没有好的方案,极端情况可能出现数据错误

试用场景

  • 适用于可异步执行的业务,且后续操作无需回滚的业务

最大努力通知

发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。

  • 提供接口,让被通知方能够通过接口查询业务处理结果
  • 消息队列ACK机制,消息队列按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 ,直到达到通知要求的时间窗口上限。之后不再通知

试用场景

  • 微信交易的结果,既有回调通知,也有交易查询接口

AT模式

这是阿里开源项目seata中的一种事务模式,在蚂蚁金服也被称为FMT。优点是该事务模式使用方式,类似XA模式,业务无需编写各类补偿操作,回滚由框架自动完成,该模式缺点也较多,一方面类似XA,存在较长时间的锁,不满足高并发的场景;另一方面存在脏回滚之类的问题,容易引发数据不一致。感兴趣可以点击ATGithubAT文档

二阶段消息(分布式事务的新方案)

Msg的提交是按照两个阶段发起的,第一阶段调用Prepare,第二阶段调用Commit,DTM收到Prepare调用后,不会调用分支事务,而是等待后续的Submit。只有收到了Submit,开始分支调用,最终完成全局事务。
我们需要跨行从A转给B 30元,我们先进行可能失败的转出操作TransOut,即进行A扣减30元。如果A因余额不足扣减失败,那么转账直接失败,返回错误;如果扣减成功,那么进行下一步转入操作,因为转入操作没有余额不足的问题,可以假定转入操作一定会成功。

特点

  • 不需要队列,因此不需要消费者,用户就是简单的调用API
  • 二阶段消息也有回查,但是回查是框架自动处理,且保证数据正确

时序图

正常case

在这里插入图片描述

异常回查

这个回查函数,会到表里面查询,本地事务是否提交了:

  • 已提交: 返回成功,DTM进行下一步子事务调用
  • 已回滚: 返回失败,DTM终止全局事务,不再进行子事务调用
  • 进行中: 这个回查会等待最终结果,然后按照前面的已提交/已回滚的情况处理
  • 未开始: 这个回查会往 RM 本地数据库插入数据,保证本地事务最终失败

提交后异常case在这里插入图片描述

提交前异常case

在这里插入图片描述

异常与子事务屏障

NPC

  • Network Delay:网络延迟。虽然网络在多数情况下工作的还可以,虽然TCP保证传输顺序和不会丢失,但它无法消除网络延迟问题。
  • Process Pause:进程暂停。有很多种原因可以导致进程暂停:比如编程语言中的GC(垃圾回收机制)会暂停所有正在运行的线程;再比如,我们有时会暂停云服务器,从而可以在不重启的情况下将云服务器从一台主机迁移到另一台主机。我们无法确定性预测进程暂停的时长,你以为持续几百毫秒已经很长了,但实际上持续数分钟之久进程暂停并不罕见。
  • 时钟漂移。现实生活中我们通常认为时间是平稳流逝,单调递增的,但在计算机中不是。计算机使用时钟硬件计时,通常是石英钟,计时精度有限,同时受机器温度影响。为了在一定程度上同步网络上多个机器之间的时间,通常使用NTP协议将本地设备的时间与专门的时间服务器对齐,这样做的一个直接结果是设备的本地时间可能会突然向前或向后跳跃。

异常分类

  • 空补偿:回滚操作在预提交请求之前执行
  • 悬挂:预提交执行时,回滚已执行完成
  • 幂等:由于任何一个请求都可能出现网络异常,出现重复请求,所有的分布式事务分支操作,都需要保证幂等性

异常原因(TCC为例)

在这里插入图片描述

  • 业务处理请求4的时候,Cancel在Try之前执行,需要处理空回滚
  • 业务处理请求6的时候,Cancel重复执行,需要幂等
  • 业务处理请求8的时候,Try在Cancel后执行,需要处理悬挂

子事务屏障

在这里插入图片描述

原理实现

子事务屏障技术的原理是,在本地数据库,建立分支操作状态表dtm_barrier,唯一键为全局事务id-分支id-分支操作

  • 开启本地事务
  • 对于当前操作op,insert ignore一条数据gid-branchid-op,如果插入不成功,提交事务返回成功(常见的幂等控制方法)
  • 如果当前操作是rollback,那么在insert ignore一条数据gid-branchid-op,如果插入成功(注意是成功),则提交事务返回成功
  • 调用屏障内的业务逻辑,如果业务返回成功,则提交事务返回成功;如果业务返回失败,则回滚事务返回失败

原理说明

  • 空补偿控制–如果Pre没有执行,直接执行了Rollback,那么3中回滚插入gid-branchid-op会成功,不走屏障内的逻辑,保证了空补偿控制
  • 幂等控制–2中任何一个操作都无法重复插入唯一键,保证了不会重复执行
  • 防悬挂控制–Pre在Rollback之后执行,那么Rollback会在3中插入gid-branchid-op,导致Pre在2中不成功,就不执行屏障内的逻辑,保证了防悬挂控制。

竞态分析

上面分析了Prerollback的执行时间没有重叠的情况下,能够解决空补偿和悬挂问题。如果出现了pre和rollback执行时间重叠的情况,我们看看会发生什么。
假设Pre和Rollback并发执行,Pre和Rollback都会插入同一条记录gid-branchid-op,由于唯一索引冲突,那么两个操作中只有一个能够成功,而另一个则会等持有锁的事务完成后返回。

  • Pre插入gid-branchid-op失败,回滚操作插入gid-branchid-op成功,此时就是典型的空补偿和悬挂场景,按照子事务屏障算法,Pre和Rollback都会直接返回。
  • Pre插入gid-branchid-op成功,Rollback操作插入gid-branchid-op失败,按照上述子事务屏障算法,会正常执行业务,而且业务执行的顺序是预提交在rollback前
  • Pre和Rollback的操作在重叠期间又遇见宕机等情况,那么至少Rollback会被dtm重试,那么最终会走到情况1或2。

小结

二阶段消息模式

适合不需要回滚的场景

  • 秒杀场景:当秒杀访问量很大时,多数系统都会选择在redis中扣减库存,扣减成功后再创建订单。这种场景下没有回滚,适合二阶段消息。二阶段消息还能够保证出现进程崩溃的情况下,扣减的库存量与创建的订单量是完全相等的
  • 缓存管理场景:使用redis缓存来提供数据,降低数据库的压力是非常常见的场景。通常情况下,会先更新DB,在更新缓存,不涉及回滚

saga模式:

适合需要回滚的场景

  • 假设您有一个订单业务,需要保证您的订单创建,库存扣减,优惠券扣减是同时成功或同时失败的。那么它适合Saga。

tcc事务模式

适合一致性要求较高的场景

  • 假设您有一个类似资金转账的业务,对一致性要求较高,不允许在转账失败的情况下,让用户看到中间的余额变动,这种情况适合TCC,它能够灵活的控制整个全局事务的数据可见性。

xa事务模式

适合并发要求不高,没有数据库行锁争抢的场景

  • 假设您的业务,对并发性要求不高,也不会出现多个请求争用同一行数据(例如不会出现扣减同一个商品库存的情况),那么可以选用XA。

猜你喜欢

转载自blog.csdn.net/weixin_43885417/article/details/130754265