浅谈分布式事务那些事

浅谈分布式事务那些事

本篇文章咱们重点讨论关于分布式事务的一些相关知识点。这是学习分布式系统的一个必不可少的技术。咱们最多见的案例就是银行转帐问题,A帐户向B帐户转100元,那么A帐户余额要减小100,B帐户上要增长100。两个步骤必须都要成功才算成功,只成功一个的话应当回滚掉。若是A和B不在同一个环境或者系统上,这个事务就是分布式事务了,那么在这种状况下,如何保证事务的正确执行,有哪些执行方案呢?html

CAP理论和BASE理论

在将分布式事务的开始,咱们先从几个分布式系统方面的基础知识点提及,首先是大名鼎鼎的CAP理论,CAP三个字母表明了三个单词,Consistency(一致性),Availability(可用性),Partition Tolerance(分区容忍性)。CAP理论指的是任何一个分布式系统,最多只能保证CAP三项中的两项。算法

首先,一致性,它表示全部的数据节点上的数据要保证是一致且正确的。可用性指的是每个操做老是可以在必定时间内返回结果便是说每一个读写操做都是成功的。咱们平时常常看到的一些对于系统稳定性的描述都是达到了几个9,好比3个9就是99.9%,4个9就是99.99%。这就意味着系统只有在极少数状况下才会发生故障或错误。分区容忍性指的是当出现部分节点故障时,分布式系统仍然可以正常运行。数据库

常见的分布式系统应用架构都是AP或者CP,由于若是没有P那本质上就是个单机应用谈不上分布式了。工做中最经常使用到的几个注册中心,ZooKeeper就是基于CP架构的,Eureka是基于AP的。编程

在工程实践中,基于CAP理论又演化出一个新的理论-BASE理论。Base表明了三个单词:Basically Available(基本可用),Soft State(软状态)和Eventally Consistent(最终一致性)。它的核心思想是最终一致性,即没法作到强一致性,但咱们能够根据实际业务状况,使系统达到最终一致性。服务器

首先,Base理论中的基本可用,就是不追求CAP中的任什么时候候的读写操做都是成功的,但系统可以保证基本运行。好比咱们平时双十一的时候可能会出现排队或者失败的状况,其实就是牺牲了部分可用性来保证系统的稳定。网络

软状态能够对标ACID中的强一致性,ACID中要么全作要么全不作,全部用户看到的数据都是绝对一致的。但软状态是容许出现数据不一致的时刻,至关因而一个中间状态,几个数据节点的数据出现了延迟的状况。架构

最终一致性指的是数据不能一直处于软状态的状况,最终仍是要达到全部节点数据一致的。这也是咱们大部分开发过程当中所追求的。框架

数据一致性模型

数据一致性模型通常分为弱一致性和强一致性,上述的BASE理论是实现的最终一致性其实就是弱一致性,而强一致性有时候也称为线性一致性,就是每次更新操做后,其余每一个进程获取到的数据都是最新的,这种方式对用户友好,即用户以前作了什么操做,下一步就能保证获得什么。但这种方式会牺牲系统的可用性,能够理解为咱们实现了CP牺牲了A。异步

除了强一致性以外的一致性模型都是弱一致性,也就是说系统不承诺更新操做后必定能保证下次能读取到最新数据,要能正确读取到这个数据须要等待一段时间,这个时间差称做“不一致窗口”。分布式

最终一致性是弱一致性的特例,它强调的是全部节点的数据副本,通过一段时间后,必定能达到所有一致。它的不一致窗口的时间主要受通讯延迟,系统负载和复制副本的个数影响。其根据不一样的保证也能够分为不一样模型,包括因果一致性会话一致性等。

因果一致性要求有因果关系的操做顺序获得保证,非因果关系的操做顺序则无所谓。

会话一致性将对系统数据的访问过程框定在了一个会话当中,约定了系统能保证在同一个有效的会话中实现“读己之所写”的一致性,就是在你的一次访问中,执行更新操做以后,客户端可以在同一个会话中始终读取到该数据项的最新值。

那么为了实现数据一致性,咱们就天然要回到咱们一开始就要说的分布式事务的概念了,它不一样于咱们平时单机应用上的服务,因为在数据分布在多台服务器上,咱们就不能用传统的方式来保证事务的正确提交,这就引出了集中针对分布式事务的解决方案。

2PC 两阶段提交

两阶段提交(2PC,Two-phase Commit Protocol)是很是经典的强一致性、中心化的原子提交协议。这个算法包含了两类角色,协调者(Coordinator)和参与者(Participants)。所谓的两阶段指的是准备阶段(Commit-request)和执行阶段(Commit)。

在准备阶段的时候,协调者通知各个参与者准备提交事务,询问它们是否接受。参与者们会各自反馈本身的响应,赞成或者取消(故障),在这个阶段里面,全部参与者都没有进行commit事务操做。

协调者收到参与者们的反馈结果后,进行决策,若是全部的参与者都表示赞成,则要提交事务,不然就是取消。此时通知全部参与者们进行事务提交/取消,参与者们接受到协调者的通知后进行事务操做commit/rollback。

可是两阶段提交存在着一些问题,列举以下:

  • 资源被同步阻塞:在执行过程当中,全部参与者都是事务独占的,也就意味着,当参与者占用公共资源的时候,其余节点访问公共资源会处于阻塞状态。
  • 协调者可能出现单点故障:协调者做为这个算法中的核心角色,一旦发生故障,参与者会一直阻塞下去。若是在第二阶段发生的,全部的参与者还处于锁定事务资源的状态,但收不到协调者的决策通知,致使一直锁定没法继续完成事务。
  • Commit阶段出现数据不一致:在第二阶段中,协调者发起了commit的通知,但因为网络等缘由致使部分节点收到部分节点每收到通知,会致使没收到的还处于阻塞状态,收到的会进行commit操做,从而出现了数据不一致问题。

XA规范

由于讲了二阶段提交,那么这里也顺便提一下咱们被问到MySQL时常常会说的XA规范,由于它其实是实现了二阶段提交的。它是由 X/Open 组织提出的分布式事务规范,XA 规范主要定义了事务协调者(Transaction Manager)和资源管理器(Resource Manager)之间的接口。

XA

事务管理器担任着协调者的角色,它来负责协调和管理事务,提供给AP应用程序编程接口并管理资源管理器。事务管理器向事务指定标识,监视它们的进程,并负责处理事务的完成和失败。资源管理器,能够理解为一个DBMS系统,或者消息服务器管理系统。应用程序经过资源管理器对资源进行控制,资源必须实现XA定义的接口。资源管理器提供了存储共享资源的支持。

目前,主流数据库都提供了对 XA 的支持,在 JMS 规范中,即 Java 消息服务(Java Message Service)中,也基于 XA 定义了对事务的支持。

根据2PC的规范,XA也是将事务分为两个步骤准备(Prepare)和提交(Commit)阶段。准备阶段就是TM向RM发送Prepare命令,准备提交,RM执行数据操做后,返回TM结果。TM根据收到的结果,进入提交阶段,通知RM进行Commit或者Rollback操做。

在MySQL中,有两种XA事务,一种是内部XA,一种是外部XA。

在 MySQL 的 InnoDB 存储引擎中,开启 binlog 的状况下,MySQL 会同时维护 binlog 日志与 InnoDB 的 redo log,为了保证这两个日志的一致性,MySQL 使用了 XA 事务,因为是在 MySQL 单机上工做,因此被称为内部 XA。内部 XA 事务由 binlog 做为协调者,在事务提交时,则须要将提交信息写入二进制日志,也就是说,binlog 的参与者是 MySQL 自己

外部 XA 就是典型的分布式事务,MySQL 支持 XA START/END/PREPARE/Commit 这些 SQL 语句,经过使用这些命令,能够完成分布式事务。

3PC 三阶段提交

三阶段提交(3PC,Three-phase commit)是基于2PC的改进版本,它引入了两个改动点。

  1. 引入超时机制,同时在协调者和参与者都加入超时机制。
  2. 把2PC的第一个阶段拆分红了两步:询问,而后再锁资源,最后真正提交。

它的三个阶段叫作Can Commit, PreCommit和Do Commit。

CanCommit 阶段

和2PC的准备阶段很像,就是协调者向参与者发起CanCommit 请求,参与者返回yes或no。

PreCommit阶段

协调者根据参与者的反应状况来决定是否能够继续事务的 PreCommit 操做。根据响应状况,有如下两种可能。

  • 所有返回ok

    • 若是所有返回OK的话,就要进行事务预提交,协调者向参与者发送PreCommit请求,参与者接受到后进行事务操做但不提交,若是成功执行了则返回ACK响应。
  • 部分返回ok

    • 若是部分返回OK可能指的是有部分参与者返回了NO或者是超时后协调者依然未收到参与者的反馈。那么此时进行中断事务,协调者发送中断请求给参与者,参与者收到后进行abort中断。

DoCommit阶段

此阶段进行真正的提交,跟2PC的最终阶段有点类似。若是协调者上一步收到了全部的ACK则会通知参与者进行提交操做,参与者收到后进行提交并反馈协调者ACK。但若是协调者上一步未收到ACK响应,一样也要执行中断事务。若是超过超时时间参与者都没有收到协调者的通知,则自动进行Commit。

很显然3PC因为协调者和参与者都有了超时机制(2PC只有协调者有),能够保证资源不会由于协调者的故障而一直锁定,而且因为多了一个PreCommit阶段,使得参与者在提交以前的状态是一致的。但这并不能解决最终可能出现的一致性问题,由于在上面DoCommit阶段也有介绍,若是参与者未收到协调者的通知,达到了超时时间后,依然会进行提交,这就出现了数据的不一致性。

TCC(Try-Confirm-Cancel)

TCC(Try-Confirm-Cancel)的概念来源于 Pat Helland 发表的一篇名为“Life beyond Distributed Transactions:an Apostate’s Opinion”的论文。它的三个字母也对应了三个阶段:

  • Try阶段:调用Try接口,尝试执行业务,完成业务检查,预留业务资源。
  • Confirm阶段:确认执行业务操做,不作业务检查,只使用Try阶段预留的业务资源。
  • Cancel阶段:跟Confirm互斥,二者只能进入其中一个。在业务执行错误的时候进行回滚,执行业务取消,释放资源。

Try阶段失败能够Cancel,但Confirm/Cancel阶段没有,为了解决此问题,TCC中添加了事务日志,若是Confirm或者Cancle阶段出错,是容许进行重试的,因此这两个接口须要支持幂等,若是重试依然失败那就要靠人工介入了。

TCC的特色

经过上面的描述,很显然,TCC相对于以前的2PC等方式,它关注的是业务层而不是数据库或者存储资源层面的事务。它的核心思想是针对每一个业务操做,都要添加相应的确认和补偿操做,同时把相关的处理从数据库拿到了业务层,以此实现了跨数据库的事务。

但正由于它是在业务层进行事务的处理,所以对微服务的侵入性强,业务逻辑的每一个分支都要实现Try,Confirm,Cancel三个操做,并且Confirm,Cancel还要实现幂等。另外TCC 的事务管理器要记录事务日志,也会损耗必定的性能。

在业务中引入 TCC 通常是依赖单独的 TCC 事务框架,最多见的就是Seata框架了,这个能够本身尝试去使用体验下。

基于消息补偿的最终一致性

在实际生产工做中,咱们还常常会用到一种方案,基于消息补偿的方式,它是一种异步事务机制。常见的实现方案有本地消息表、消息队列等。

本地消息表

首先说下本地消息表,它最初是由 ebay 的工程师提出,核心思想是将分布式事务拆分红本地事务进行处理,经过消息日志的方式来异步执行。因此其实就是利用了各系统本地的事务实现了分布式事务。在本地要建立一张消息表,业务执行的时候也要往这个消息表里面存放一条数据,这样能够保证存放消息和业务数据都是同时成功或失败的。成功存放后再进行后续的业务操做,若是成功了则将消息状态更新为成功。若是失败了,首先会有个定时任务常常去扫描未成功的任务去执行,若是失败也是能够重试的,因此要保证业务处理接口的幂等。

因此能看出本地消息表的方式是能保证最终一致性的,但可能出现部分时间的数据不一致。

可靠消息队列

咱们常见的消息队列中RocketMQ就支持消息事务。因此我这里讲下RocketMQ实现分布式事务。这部分我从阿里云的文档上找的,感兴趣的能够本身去看,显示几个概念:

  • 半事务消息:暂不能投递的消息,发送方已经成功地将消息发送到了消息队列RocketMQ版服务端,可是服务端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的消息即半事务消息。
  • 消息回查:因为网络闪断、生产者应用重启等缘由,致使某条事务消息的二次确认丢失,消息队列RocketMQ版服务端经过扫描发现某条消息长期处于“半事务消息”时,须要主动向消息生产者询问该消息的最终状态(Commit或是Rollback),该询问过程即消息回查。

交互流程以下

交互流程

事务消息发送步骤以下:

  1. 发送方将半事务消息发送至消息队列RocketMQ版服务端。
  2. 消息队列RocketMQ版服务端将消息持久化成功以后,向发送方返回Ack确认消息已经发送成功,此时消息为半事务消息。
  3. 发送方开始执行本地事务逻辑。
  4. 发送方根据本地事务执行结果向服务端提交二次确认(Commit或是Rollback),服务端收到Commit状态则将半事务消息标记为可投递,订阅方最终将收到该消息;服务端收到Rollback状态则删除半事务消息,订阅方将不会接受该消息。

事务消息回查步骤以下:

  1. 在断网或者是应用重启的特殊状况下,上述步骤4提交的二次确认最终未到达服务端,通过固定时间后服务端将对该消息发起消息回查。
  2. 发送方收到消息回查后,须要检查对应消息的本地事务执行的最终结果。
  3. 发送方根据检查获得的本地事务的最终状态再次提交二次确认,服务端仍按照步骤4对半事务消息进行操做。

尽最大努力通知

和上面的可靠消息队列方式相似的,还有一种方案叫作尽最大努力通知。它的核心思想是发起通知方经过必定的机制最大努力将业务处理结果通知到接收方。通常也是经过MQ去实现的。

它和基于可靠消息一致的区别以下(摘自某博客):

一、解决方案思想不一样

可靠消息一致性,发起通知方须要保证将消息发出去,而且将消息发到接收通知方,消息的可靠性关键由发起通知方来保证。 最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,可是可能消息接收不到,此时须要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。

二、二者的业务应用场景不一样

可靠消息一致性关注的是交易过程的事务一致,以异步的方式完成交易。 最大努力通知关注的是交易后的通知事务,即将交易结果可靠的通知出去。

三、技术解决方向不一样

可靠消息一致性要解决消息从发出到接收的一致性,即消息发出而且被接收到。 最大努力通知没法保证消息从发出到接收的一致性,只提供消息接收的可靠性机制。可靠机制是,最大努力的将消息通知给接收方,当消息没法被接收方接收时,由接收方主动查询消费(业务处理结果)。

总结

2PC和3PC都是一种强一致性事务,基于数据库层面,但也存在一些数据不一致的风险。TCC是一种补偿性的事务思想,因为须要在业务层实现,因此对业务侵入大。基于消息补偿的方式,TCC还有尽最大努力通知其实都是一种柔性事务,都是保证了最终一致性,容许出现部分时刻数据不一致的状况。

这篇文章有点水,其实就是讲了点概念,不少仍是摘录自其余博客和拉勾教育-分布式技术原理与实战45讲这门课程的,就当个读书笔记看吧,至少扩展了一些对分布式事务的基础概念的了解。

相关文章
相关标签/搜索