事务是数据库系统中一个很有趣也很重要的概念。它是数据库管理系统执行过程中的一个逻辑单元。它可以确保事务中的所有操作都被执行或全部执行,或者根本不执行;在当今流行的SOA和微服务架构中,分布式多服务中保证业务一致性需要我们实现分布式事务。在本文中,我们将介绍事务的实现原理,分布式事务的理论基础和实现原理。事务在文章的开头我们已经说过,事务是数据库管理系统执行过程中的一个逻辑单元。它可以确保一组数据库操作要么执行要么根本不执行。我们可以将数据库从一种状态迁移到另一种状态,在每一种状态下,数据库中的数据都保持一致。database-and-transaction数据库事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)四个特性:transaction-basics我们常将以上四个特性简称为ACID,而数据库的实现原理交易其实就是实现这四个特性的原理。实现原理在上一篇《由浅入深》中MySQL中事务的实现中,其实已经对事务ACID的基本属性如何实现进行了更详细的介绍和分析。这里简单介绍几个比较重要的。的实现细节,可以阅读上面关于扩展内容的文章。事务日志为了保证事务在任何执行过程中都可以回滚(原子的),并且提交的事务会永久保存在数据库中,我们会使用事务日志来存储事务执行过程中数据库的变化。每个事务日志都包含事务的ID、当前修改的元素、修改前后的值。Transaction-Log当我们有了上面的事务日志,一旦事务需要回滚,就很容易了。数据库会根据上述日志产生反向操作,恢复事务发生前的状态;事务日志不仅可以回滚事务,保证原子性,还可以实现持久化。当一个事务修改数据库时,它实际上会产生一个日志并刷新到磁盘。写日志的操作非常快,因为是追加的。之后才会在数据库中写入或更新相应的记录。在MySQL最常见的存储引擎InnoDB中,其实有两种事务日志,一种是undolog,一种是redolog。前者保证事务的原子性,后者保证事务的持久性,两者可以统称为事务日志。作为并发控制最关键的后端服务,很难想象串行执行每个数据库操作对性能的影响。但是在并发执行SQL的过程中,可能无法保证数据库的隔离需求。归根结底,这是一致性、隔离性和性能之间的权衡。tradeoff-and-concurrency为了避免并发带来的一致性问题,同时满足数据库的隔离需求,数据库系统往往会使用并发控制机制,尽可能的发挥机器效率。几种常见的并发控制机制是锁、时间戳和MVCC:concurrency-contro作为悲观并发控制机制。锁用于在更新资源之前锁定资源,以确保多个数据库会话在同时修改一行记录时不会偏离预期的行为。时间戳这样的方法检查资源是否在每次提交时被更改。分布式事务从广义上讲,分布式事务其实就是事务,但是由于业务的定义和微服务架构的设计,需要保证业务在多个服务之间的事务性,即四个特性酸;当从单机数据库事务转为分布式事务时,原来单机中相对靠谱的方法调用和进程间通信方式就没有办法使用了,而且由于网络通信经常不稳定,信息的传递服务之间将出现。障碍。tx-and-distributed-tx模块(或服务)之间通信方式的改变是造成分布式事务复杂化的最重要原因。在同一个事务之间执行多段代码会因为网络不稳定导致各种奇怪的问题。问题是,当我们通过网络请求其他服务的接口时,往往会得到三种结果:正确、失败和超时。无论是成功还是失败,我们都能得到唯一确定的结果。超时是指请求的发起者不能确定是否接受。请求是否被作者成功处理也是造成很多问题的原因。通信-可靠性-交易系统之间的通信可靠性已经从单一系统的可靠性,转变为微服务架构之间的不可靠性。分布式事务实际上是在不可靠通信下实现事务的特性。无论是事务还是分布式事务要实现原子性,都无法避免对持久化存储的依赖。事务使用磁盘上的日志记录来记录执行过程和上下文,这样可以通过日志追溯是否需要回滚或补偿,而分布式事务也会依赖数据库、Zookeeper等服务或ETCD来跟踪交易的执行过程。总而言之,各种形式的日志是保证交易几大特性的重要手段。2PC和3PC两阶段提交是一种旨在使分布式系统中的所有节点在提交事务时保持一致的协议;状态,但无法知道其他节点的运行状态。当事务跨越多个系统时,需要引入一个组件作为协调器来控制所有节点,并指示这些节点是否提交操作结果。其他在分布式系统中实现一致性的协议都是在两阶段提交的基础上改进的。two-phase-commit两阶段提交的执行过程就像它的名字一样分为两个阶段,投票阶段和提交阶段。在投票阶段,协调者(Coordinator)会询问事务参与者(Cohort)是否可以执行操作的请求,等待其他参与者的响应,参与者会执行相应的事务操作并记录重做和回滚log,所有成功的参与者都会向协调者发送AGREEMENT或者ABORT来表示操作结果的执行。two-phase-commit-voting-phase当所有参与者返回确定结果(同意或终止)时,两阶段提交进入commit阶段,协调者根据投票阶段的返回状态向所有参与者报告发送提交或回滚命令。two-phase-commit-commit-phase当事务中的所有参与者都决定提交事务时,协调器会向参与者发送COMMIT请求,参与者在完成操作并释放事务后向协调器返回一个完成消息资源。当收到所有参与者的完成消息时,整个交易将结束;相反,当一个参与者决定ABORT当前事务时,协调器会向该事务的参与者发送一个回滚请求,参与者会根据之前执行的回滚日志回滚该操作并发送一个完成消息给协调员。在提交阶段,无论当前事务是提交还是回滚,所有资源都会被释放,事务肯定会结束。两阶段提交协议是一个阻塞协议,这意味着在两阶段提交的执行过程中,另外,如果在事务执行过程中协调器永久宕机,事务中的一些参与者将永远无法完成交易。它会等待协调器发送COMMIT或ROLLBACK消息,甚至可能出现多个参与者状态不一致的问题。two-phase-commit-problems3PC为了解决两阶段提交协议中的一些问题,三阶段提交引入了超时机制和准备阶段。如果协调者或参与者在指定时间内没有收到其他节点的响应,它将根据当前状态选择提交或终止整个交易。准备阶段的引入实际上给了交易参与者除了回滚之外的其他选择。Three-phase_commit_diagra当参与者向协调器发送ACK时,如果协调器长时间没有响应,默认情况下,参与者会自动提交超时事务,不会像两阶段那样被阻塞犯罪;上图非常清楚地展示了不同阶段的协调器或参与者超时会导致什么样的行为。XA事务MySQL的InnoDB引擎其实是可以支持分布式事务的,也就是我们常说的XA事务;XA事务使用上一节我们提到的两阶段提交协议来实现分布式事务,其中事务管理器是协调器,资源管理器是分布式事务的参与者。two-phase-commit-and-xa-transaction至此,其实我们已经能够清楚的理解MySQL中的XA事务是如何实现的了:资源管理器提供访问事务性资源的能力,数据库是一个公共资源管理它可以提交或回滚它管理的事务;事务管理器协调整个分布式事务的各个部分,它与多个资源管理器通信,分别处理它们管理的事务,而这些事务是整个事务的一个分支。distributed-transaction-and-transactions在两阶段提交协议中定义,MySQL提供的XA接口可以方便的实现协议中的投票和提交阶段。我们可以通过下面的流程图来简单了解一下MySQLXA接口。使用:mysql-xa-transaction-statesXA确实可以保证强一致性,但是MySQLXA在执行过程中,会锁定相应的资源,阻止其他事务访问该资源。如果事务长时间不COMMIT或者ROLLBACK,实际上会对数据库造成严重的影响。Saga两阶段提交其实可以保证事务的强一致性,但是在很多业务场景下,我们只需要保证业务的最终一致性即可。在一定的时间窗口内,多个系统的数据不一致是可以接受的。时间窗口过去后,所有系统都会返回一致的结果。Saga实际上是一个简化的分布式事务解决方案,将一系列的分布式操作转化为一系列的本地事务。在每个本地事务中,我们更新数据库并向集群中的其他服务发送消息。一条新消息会触发下一次本地事务;一旦本地事务因为违反了业务逻辑而失败,它会立即触发一系列回滚操作,以撤消之前本地事务带来的副作用。与本地数据库事务相比,LLT会在较长时间内持有部分数据库资源,这会严重影响其他正常数据库事务的执行。为了解决这个问题,HectorGarcia-Molina和KennethSalem在1987年发表了论文Sagas来解决这个问题。如果一个LLT可以改写成一系列交错重叠的多个数据库事务,那么这个LLT就是一个Saga;数据库系统可以保证Saga中的一系列事务要么全部成功执行,要么它们的补偿事务可以回滚所有Sideeffects,保证整个分布式事务的最终一致性。Saga的概念及其实现非常简单,但在提高整个系统的处理能力方面具有巨大的潜力。long-lived-transaction-and-transactions事务越长越复杂,事务因为异常和死锁被回滚的可能性会逐渐增加。Saga会将一个LLT分解为多个短交易,可以非常显着降低交易被回滚的风险。CollaborationandOrchestration当我们使用Saga模式开发分布式事务时,有两种方式来协调不同的服务,一种是Choreography,另一种是Orchestration:saga-pattern对于分布式事务,我们采用协作的方式开发,各自本地事务会在其他服务中触发一个本地事务的执行,也就是说事务的执行过程是以流的形式进行的:saga-pattern-choreography当我们选择使用collaborative处理事务时同理,服务之间的通信其实也是通过事件来进行的。每个本地事务最终都会向服务的下游发送一个新的事件,它可以是消息队列中的一条消息,也可以是一个RPC请求,但下游提供的接口需要保证幂等性和可重入性。另外,协作创建的分布式交易没有明显的集中节点。多个服务参与者之间的交互协议必须全局定义。每个服务可以处理和发送的事件和接口需要更严谨的设计,尽可能提供高度抽象的事件或接口,让每个服务实现自治,重用已有的代码和逻辑。如果我们不想以协调的方式处理分布式事务,我们也可以选择以编排的方式实现分布式事务。orchestred方式引入了一个中心化的coordinator节点,我们使用一个Saga对象来跟踪所有子任务的进度,根据任务的调用情况,决定是否调用相应的补偿计划,当网络请求超时时重试:saga-pattern-orchestration这里我们引入一个中心化的“协调器”,它会将当前分布式事务的状态保存到最后,并根据情况回滚或提交事务。在服务编排的过程中,我们从协调者自身触发整个事务的执行过程。与协同方式相比,编排实现的过程相对简单。协作和编排其实是两种理念截然相反的模式。前者强调各个服务的自治和去中心化,而后者则需要一个中心化的组件来统一管理交易执行的过程。两者的优缺点其实就是中心化和去中心化的优缺点,中心化的解决方案往往会打造一个“上帝服务”,其中包括大量的工作来组织和整合其他节点,同时也会有单点故障问题,而分散的解决方案会给管理和调试带来不便。当我们需要跟踪一个业务的执行过程时,需要跨多个服务,增加了维护成本。DownstreamConstraints当我们选择使用Saga开发分布式事务时,会对分布式事务的参与者有一定的约束。每个交易参与者需要保证:提供接口和补偿副作用的接口;接口支持可重入,并通过全局唯一ID保证幂等性;这样我们就可以保证一个长事务在网络通信超时的时候可以重试,同时在事务需要回滚的时候调用回滚接口来达到我们的目的。总结Saga模型其实完全放弃了同时满足ACID的四个基本特征的思路。相反,它选择降低分布式事务的实现难度,减少资源同步和锁定带来的问题。它选择实现BASE(BasicAvailability,Soft,Eventualconsistency)事务来实现业务上的基本可用性和最终一致性。在大部分业务场景下,实现最终一致性基本可以满足所有业务需求。在极端场景下,应该选择两阶段提交或者干脆放弃分布式事务容易出错的实现方式,使用单机数据库事务来解决。消息服务分布式事务之所以带来复杂性,是因为各个模块之间的通信不稳定。当我们发送网络请求时,可能的返回结果是成功、失败或超时。网络-通信网络返回的是成功还是失败,其实是一个确定的结果。当网络请求超时的时候,其实是很难处理的。这时候调用者并不确定这次请求是否送达,也就不知道这个请求了。因此,消息服务可以保证将一条信息传递给调用者;大多数消息服务都会提供两种不同的QoS,即服务级别。message-delivery-qos的两个最常见的服务级别是At-Most-Once和At-Least-Once。前者可以保证发送方不保证接收方能否收到消息。消息将被发送一次或不发送。会被投递,其实和普通的网络请求没有太大区别;At-Least-Once可以解决消息投递失败的问题,它要求发送方检查投递结果,在失败或者超时时重新处理消息投递,发送方会继续推送消息直到接收方确认已收到消息。与At-Most-Once相比,At-Least-Once可以被更多人使用,因为它可以保证消息的传递。除了这两种常见的服务级别外,还有一种服务级别,即Exactly-Once。这种服务水平不仅对发送方提出要求,同时也对消费者提出要求。所有收到的消息都去重,发送方和接收方重试消息,对方去重消息,两者部署在不同的节点上,这样对于每个节点上的服务,它们之间的通信是Exactly-Once,但是需要注意的是,Exacly-Once必须需要接收方的参与。我们可以通过实现AMQP协议的消息队列来实现分布式事务。协议标准中定义了三个与事务相关的接口,tx_select、tx_commit和tx_rollback。其中tx_select可以启动一个事务,tx_commit和tx_rollback分别可以提交或者回滚一个事务。使用消息服务实现分布式事务在底层原理上和其他方法没有太大区别,但是消息服务可以帮助我们实现消息持久化和重试功能,并且可以为我们提供更合理的API接口,方便开发者使用使用。综上所述,分布式事务的实现是分布式系统中一个非常重要的问题。在微服务架构和SOA流行的今天,掌握分布式事务的原理和使用方法是后端开发者应该掌握的一项技能,从实现ACID事务的2PC、3PC,到实现BASE补偿事务的Saga,最终保证消息将通过异步事务消息被成功消费。为了提高系统的吞吐量和可用性,我们逐渐降低了系统对一致性的要求。当业务对一致性没有那么强烈的需求时,笔者一般采用Saga协议来设计开发分布式事务。在实际工作中,几乎没有业务场景需要强一致性事务,我们都可以实现最终的Consistency,当出现脑裂或者不一致时,通过补偿来解决,几乎可以解决所有问题。参考数据库事务·维基百科《精简深入》MySQL事务实现MySQL·特性分析·浅谈MySQL5.7XA事务改进XATransactionsTwo-phasecommitprotocolPattern:SagaSagasRocketMQ4.3正式发布,支持分布式事务AkkaMessageDelivery-At-Most-Once、至少一次和恰好一次第1部分至多一次第2部分至少一次第3部分恰好一次消息传递可靠性
