微服务系列之分布式事务理论
概述
事务是由一组操作构成的可靠的独立的工作单元,事务具备ACID的特性,即原子性、一致性、隔离性和持久性。
分类
大多数情况下,分类是没有意义的一件事。但是分类可以一定程度上,加深理解。
实现
从实现角度来看,Java事务的类型有三种:JDBC事务、JTA(Java Transaction API)事务、容器事务。
一个JDBC事务不能跨越多个数据库!
容器事务:常见的如Spring事务,主要是J2EE应用服务器提供的,大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现。
本地、全局、分布式事务
站在事务管理的角度,可以把Java事务分为本地事务、全局事务、分布式事务。
本地事务
当事务由资源管理器本地管理时被称作本地事务,如JDBC事务。优点是支持严格的ACID特性,高效可靠,状态可以只在资源管理器中维护,且应用编程模型简单。但不具备分布式事务的处理能力,隔离的最小单位受限于资源管理器,如数据库中的一条记录。
全局事务
全局事务是指跨越多个独立的组件或服务的事务。全局事务会涉及多个子事务,这些子事务可能分布在不同的计算机或系统上。全局事务的目标是确保多个子事务的一致性,即要么全部执行成功,要么全部回滚。
当事务由全局事务管理器进行全局管理时成为全局事务,事务管理器负责管理全局的事务状态和参与的资源,协同资源的一致提交回滚
全局事务通常由一个协调器来管理,协调器负责协调各个子事务的执行。全局事务具有以下几个阶段:开始(Start)、提交(Commit)和回滚(Rollback)。
全局事务是由资源管理器管理和协调的事务,是一个DTP模型的事务。DTP参考模型指的是X/Open DTP(X/Open Distributed Transaction Processing Reference Model),是X/Open组织定义的一套标准,定义规范和API接口,由各个厂商进行具体的实现。
X/Open DTP定义三个组件(AP,TM,RM)和两个协议(XA、TX):
- AP:Application Program,可理解为使用DTP(Data Tools Platform)的程序
- RM:Resource Manager,资源管理器,可以是一个DBMS或消息服务器管理系统,应用程序通过资源管理器对资源进行控制,资源必须实现XA定义的接口,资源管理器负责控制和管理实际的资源
- TM:Transaction Manager,事务管理器,负责协调和管理事务,提供给AP编程接口以及管理资源管理器。事务管理器控制着全局事务,管理事务的生命周期,并且协调资源
- TX协议:应用或应用服务器与事务管理器的接口
- XA协议:全局事务管理器与资源管理器的接口。XA是由X/Open组织提出的分布式事务规范。该规范主要定义了全局事务管理器和局部资源管理器之间的接口。主流的数据库产品都实现了XA接口。XA接口是一个双向的系统接口,在事务管理器以及多个资源管理器之间作为通信桥梁。之所以需要XA是因为在分布式系统中从理论上讲两台机器是无法达到一致性状态的,因此引入一个单点进行协调。由全局事务管理器管理和协调的事务可以跨越多个资源和进程。全局事务管理器一般使用XA二阶段协议与数据库进行交互
分布式事务
分布式事务是指多个数据库节点之间的事务,这些节点可以是不同的计算机或系统。分布式事务的目标是确保多个节点之间的数据操作的一致性和原子性。
分布式事务通常有一个全局事务管理器来管理,全局事务管理器负责协调各个节点之间的事务。分布式事务采用两阶段提交(Two-Phase Commit,2PC)和两阶段提交(3PC)协议来确保数据的一致性和原子性。
对比
全局事务和分布式事务都具有处理多个数据操作的一致性和原子性的目标。区别如下:
- 一致性与可用性
全局事务通常更强调一致性,即要求所有子事务的操作都成功或全部回滚。分布式事务则更加注重可用性,即允许部分节点执行成功,部分节点执行失败的情况。 - 系统复杂度与性能
全局事务对系统复杂度的要求相对较高,需要一个协调器来管理,并且可能涉及到大量的网络通信。分布式事务虽然也需要全局事务管理器来协调,但其性能相对更高,可以并行处理多个事务。 - 事务的执行效率
由于全局事务更注重一致性,其执行效率可能会受到一些限制,特别是在子事务之间存在依赖关系的情况下。而分布式事务允许并行执行,因此其执行效率可能相对较高。 - 适用场景
全局事务适用于需要在不同的组件或服务之间保持一致性的场景,如分布式数据库的联合操作、分布式锁的管理以及跨部门的业务操作等。
分布式事务适用于需要在多个数据库节点之间保持一致性和原子性的场景,比如跨数据库的事务处理、分布式消息队列的事务以及微服务架构中的事务管理等。
模型
ACID
在传统的RDBMS中,事务具有ACID4个属性:
- 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。
- 一致性(Consistency):在事务开始和完成时,数据都必须保持一致状态。这意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
- 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是不可见的,反之亦然。
- 持久性(Durability):事务完成之后,它对于数据的修改是永久性的,即使出现系统故障也能够保持。
保证原子性是DBMS的责任:即事务管理器和恢复管理器的责任。遵循ACID原则强调一致性,对成本要求很高,对性能影响很大。
CAP
CAP 定理是分布式系统设计中最基础理论。它指出,分布式数据存储不可能同时满足以下三个条件:
- 一致性(Consistency):每次读取要么获得最近写入的数据,要么获得一个错误。
- 可用性(Availability):每次请求都能获得一个(非错误)响应,但不保证返回的是最新写入的数据。
- 分区容忍(Partition tolerance):尽管任意数量的消息被节点间的网络丢失(或延迟),系统仍继续运行。
CAP 定理表明,在存在网络分区的情况下,一致性和可用性必须二选一。CAP中的一致性与 ACID中一致性截然不同。
CAP的证明:
假设两个节点集{G1, G2}
,由于网络分片导致G1和G2之间所有的通讯都断开。
如果在G1中写,在G2中读刚写的数据, G2中返回的值不可能是刚刚在G1中的写值。
对于分布式数据系统而言,分区容错性是基本要求,否则就不称其为分布式系统。
由于可用性的要求,G2一定要返回这次读请求,因为分区容错性的存在,导致一致性一定是不可满足的。
CAP理论,一个分布式系统不可能同时满足一致性,可用性和分区容错性这三个需求,三个要素中最多只能同时满足两点。
显然,任何横向扩展策略都要依赖于数据分区,软件架构通常必须在一致性与可用性之间做出选择。
BASE
BASE,Basically Available、Soft state、Eventually consistent,是对CAP中C和A的延伸。
- Basically Available:基本可用,即数据一致性能够基本满足二八定律,即至少保证80%一致性,剩下20%就不要过于纠结。
- Soft-state:软状态/柔性事务,即状态可以有一段时间的不同步。
在不过分追求数据一致性(强一致性)前提下可考虑软状态策略,例如把数据(State)缓存在客户端一段时间,在一段时间过后,如果客户端没有再次刷新状态的请求的话,就清除此缓存(Soft),这个状态就会消失。 - Eventual consistency:最终一致性,即在某一段短时间内允许数据不一致,但经过一段较长时间(这里的一段时间多数是业务能够容忍的延迟),等所有节点上数据的拷贝都整合在一起时,数据会最终达到完全一致。最终一致性贯穿着互联网尤其是电子商务类型的主要应用的生命周期。
BASE,基于CAP理论逐步演化而来,核心思想:即便不能达到强一致性(Strong Consistency),但可以根据应用特点采用适当的方式来达到最终一致性的效果。BASE是反ACID的,它完全不同于ACID模型,牺牲强一致性,获得基本可用性和柔性可靠性并要求达到最终一致性。
CAP、BASE理论是NoSQL的理论基础。
分布式事务
协议
2PC
即二阶段提交协议(Two Phase Commitment Protocol),2PC通常用来保证数据的强一致性。
XA用于在全局事务中协调多个资源的机制。TM和RM之间采取两阶段提交的方案来解决一致性问题。两节点提交需要一个协调者(TM)来掌控所有参与者(RM)节点的操作结果并且指引这些节点是否需要最终提交。两阶段提交的局限在于协议成本,准备阶段的持久成本,全局事务状态的持久成本,潜在故障点多带来的脆弱性,准备后,提交前的故障引发一系列隔离与恢复难题。
在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败,故而需要协调节点。
2PC中存在两种类型的节点:协调节点(TM)和数据节点(或称协调者与参与者,RM),数据节点可以说是数据在多个节点的备份,协调节点用户协调管理多个数据节点在事务操作中数据的一致性问题。
2PC协议通常分为两个阶段进行,提交请求阶段(Commit Request Phase)或称投票阶段(Voting phase,表决阶段)、与提交阶段(Commit Phase)
- 提交请求阶段,协调者发送请求给参与者,通知参与者提交或取消事务,参与者进入投票过程,每个参与者回复给协调者自己的投票:同意(事务在本地执行成功)或取消(事务本地执行失败)。
- 提交阶段,协调者对上一阶段参与者的投票结果进行表决,当所有投票为同意时提交事务,否者中止事务,并通知参与者,参与者接到通知后执行相应的操作。
第一阶段:
协调者会问所有的参与者结点,是否可以执行提交操作
各个参与者开始事务执行的准备工作:如:为资源上锁,预留资源,写undo/redo log
参与者响应协调者,如果事务的准备工作成功,则回应可以提交,否则回应拒绝提交
第二阶段:
如果所有的参与者都回应可以提交,那么,协调者向所有的参与者发送“正式提交”的命令。参与者完成正式提交,并释放所有资源,然后回应“完成”,协调者收集各结点的“完成”回应后结束这个Global Transaction。
如果有一个参与者回应拒绝提交,那么,协调者向所有的参与者发送“回滚操作”,并释放所有资源,然后回应“回滚完成”,协调者收集各结点的“回滚”回应后,取消这个Global Transaction。
2PC假定节点没有崩溃、任意两个节点的网络都是正常连通的、在写日志的过程中数据不会丢失的前提下。
两阶段提交协议交互构成描述
两阶段提交协议是协调所有分布式原子事务参与者,并决定提交或取消(回滚)的分布式算法。
- 协议参与者
在两阶段提交协议中,系统一般包含两类机器(或节点):一类为协调者(coordinator),通常一个系统中只有一个;另一类为事务参与者(participants,cohorts或workers),一般包含多个,在数据存储系统中可以理解为数据副本的个数。协议中假设每个节点都会记录写前日志(write-ahead log)并持久性存储,即使节点发生故障日志也不会丢失。协议中同时假设节点不会发生永久性故障而且任意两个节点都可以互相通信。 - 两个阶段的执行
- 请求阶段
准备(prepare)阶段,协调者将通知事务参与者(即各个服务应用)准备提交或取消事务,然后进入表决过程。
在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障)。 - 提交阶段
在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。
当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。
参与者在接收到协调者发来的消息后将执行响应的操作。
- 请求阶段
- 两阶段提交的缺点
- 同步阻塞问题
执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。 - 单点故障
由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题) - 数据不一致
在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现数据部一致性的现象。
- 同步阻塞问题
- 两阶段提交无法解决的问题
当协调者出错,同时参与者也出错时,两阶段无法保证事务执行的完整性。
考虑协调者再发出commit消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。
那么即使协调者通过选举协议产生新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
另一个主要的问题是在TimeOut上,如果第一阶段中,参与者没有收到询问请求,或是参与者的回应没有到达协调者。那么,需要协调者做超时处理,一旦超时,可以当作失败,也可以重试。
如果第二阶段中,正式提交发出后,如果有的参与者没有收到,或是参与者提交/回滚后的确认信息没有返回,一旦参与者的回应超时,要么重试,要么把那个参与者标记为问题结点剔除整个集群,这样可以保证服务结点都是数据一致性的。
第二阶段中,如果参与者收不到协调者的commit/fallback指令,参与者将处于状态未知
阶段,参与者完全不知道要怎么办,比如:如果所有的参与者完成第一阶段的回复后(可能全部yes,可能全部no,可能部分yes部分no),如果协调者在这个时候挂掉。所有的结点完全不知道怎么办(问另的参与者都不行)。为了一致性,要么死等协调者,要么重发第一阶段的yes/no命令。
两段提交最大的问题就是第3项,如果第一阶段完成后,参与者在第二阶没有收到决策,那么数据结点会进入不知所措的状态,这个状态会block住整个事务。也就是说,协调者Coordinator对于事务的完成非常重要,Coordinator的可用性是个关键。
优点:
- 遵循事务严格的 ACID 特性
缺点:
- 反可伸缩模式,业务规模越大,涉及模块越多,局限性越大,系统可伸缩性越差
- 在技术栈比较杂的分布式应用中,存储组件有很多不支持XA协议
3PC
把二段提交的第一阶段拆成两段:询问,然后再锁资源。最后真正提交。
3PC的核心理念是:在询问时并不锁定资源,除非所有人都同意,才开始锁资源。
理论上来说,如果第一阶段所有的结点返回成功,那么有理由相信成功提交的概率很大。这样可以降低参与者Cohorts的状态未知的概率。也就是说,一旦参与者收到PreCommit,意味他知道大家其实都同意修改。
3PC的状态迁移图
注:
- F是Failuer,T是Timeout;
- 状态:q – Query,a – Abort,w – Wait,p – PreCommit,c – Commit
缺点:
- 性能不好
TCC
TCC本质上是一个业务层面上的2PC,要求业务在使用TCC模式时必须实现三个接口Try()
、Confirm()
、Cancel()
。2PC无法解决宕机问题,TCC解决2PC宕机问题的方案:不断重试。
TCC是服务化的二阶段编程模型:
- Try阶段:尝试执行业务,完成所有业务的检查,实现一致性;预留必须的业务资源,实现准隔离性。
- Confirm阶段:真正的去执行业务,不做任何检查,仅适用Try阶段预留的业务资源,Confirm操作还要满足幂等性。
- Cancel阶段:取消执行业务,释放Try阶段预留的业务资源,Cancel操作要满足幂等性。
TCC与2PC协议的区别
TCC位于业务服务层而不是资源层,TCC没有单独准备阶段,Try操作兼备资源操作与准备的能力,TCC中Try操作可以灵活的选择业务资源,锁定粒度。TCC的开发成本比2PC高。TCC也属于两阶段操作
一个完整的业务活动由一个主业务服务于若干的从业务服务组成。主业务服务负责发起并完成整个业务活动。从业务服务提供TCC型业务操作。业务活动管理器控制业务活动的一致性,它登记业务活动的操作,并在业务活动提交时确认所有的TCC型操作的Confirm操作,在业务活动取消时调用所有TCC型操作的Cancel操作。
成本:实现TCC操作的成本较高,业务活动结束的时候Confirm和Cancel操作的执行成本。业务活动的日志成本。
使用范围:强隔离性,严格一致性要求的业务活动。适用于执行时间较短的业务,比如处理账户或者收费等等。
特点:不与具体的服务框架耦合,位于业务服务层,而不是资源层,可以灵活的选择业务资源的锁定粒度。TCC里对每个服务资源操作的是本地事务,数据被锁住的时间短,可扩展性好,可以说是为独立部署的SOA服务而设计的。
方案
截止目前,还没有任何一种分布式事务的技术方案,可以满足所有场景的问题。最具代表性的事务模式有四种:AT、TCC、Saga和XA:
AT模式
优势:业务无侵入;轻量,不依赖数据库的高级特性;回滚较少的场景性能高。
劣势:隔离性不高,目前只能支持到接近读已提交(RC,Read Commited)的程度,更高的隔离级别,实现成本将非常高。
TCC模式
优势:适用场景广泛;隔离性和性能都可以做极致优化。
劣势:业务侵入性非常高。
Saga模式
优势:适用于长事务场景。
劣势:有一定业务侵入性;隔离性差。
XA模式
优势:业务无侵入;隔离性好。
劣势:较重的依赖;阻塞协议。
主流的分布式事务解决方案:2PC、3PC、TCC、最大努力消息通知、基于补偿。
服务模式
- 可查询操作:服务操作具有全局唯一的标识,操作唯一的确定的时间。
- 幂等操作:重复调用多次产生的业务结果与调用一次产生的结果相同。一是通过业务操作实现幂等性,二是系统缓存所有请求与处理的结果,最后是检测到重复请求之后,自动返回之前的处理结果。
- 可补偿操作
- Do阶段:真正的执行业务处理,业务处理结果外部可见。
- Compensate阶段:抵消或者部分撤销正向业务操作的业务结果,补偿操作满足幂等性。
- 约束:补偿操作在业务上可行,由于业务执行结果未隔离或者补偿不完整带来的风险与成本可控。
实际上,TCC的Confirm和Cancel操作可以看做是补偿操作。
柔性事务有两个特性:基本可用和柔性状态。所谓基本可用是指分布式系统出现故障的时候允许损失一部分的可用性。柔性状态是指允许系统存在中间状态,这个中间状态不会影响系统整体的可用性,比如数据库读写分离的主从同步延迟等。柔性事务的一致性指的是最终一致性。
基于可靠消息的最终一致性方案概述
实现:业务处理服务在业务事务提交之前,向实时消息服务请求发送消息,实时消息服务只记录消息数据,而不是真正的发送。业务处理服务在业务事务提交之后,向实时消息服务确认发送。只有在得到确认发送指令后,实时消息服务才会真正发送。
消息:业务处理服务在业务事务回滚后,向实时消息服务取消发送。消息发送状态确认系统定期找到未确认发送或者回滚发送的消息,向业务处理服务询问消息状态,业务处理服务根据消息ID或者消息内容确认该消息是否有效。被动方的处理结果不会影响主动方的处理结果,被动方的消息处理操作是幂等操作。
成本:可靠的消息系统建设成本,一次消息发送需要两次请求,业务处理服务需要实现消息状态回查接口。
优点:消息数据独立存储,独立伸缩,降低业务系统和消息系统之间的耦合。对最终一致性时间敏感度较高,降低业务被动方的实现成本。兼容所有实现JMS标准的MQ中间件,确保业务数据可靠的前提下,实现业务的最终一致性,理想状态下是准实时的一致性。
最大努力通知型
实现:业务活动的主动方在完成处理之后向业务活动的被动方发送消息,允许消息丢失。业务活动的被动方根据定时策略,向业务活动的主动方查询,恢复丢失的业务消息。
约束:被动方的处理结果不影响主动方的处理结果。
成本:业务查询与校对系统的建设成本。
使用范围:对业务最终一致性的时间敏感度低。跨企业的业务活动。
特点:业务活动的主动方在完成业务处理之后,向业务活动的被动方发送通知消息。主动方可以设置时间阶梯通知规则,在通知失败后按规则重复通知,知道通知N次后不再通知。主动方提供校对查询接口给被动方按需校对查询,用户恢复丢失的业务消息。
适用范围:银行通知,商户通知。
开源
tcc-transaction
Easytransaction
ByteTCC
dts
DTS
能够与Spring事务良好结合
支持Saga/TCC及基于消息的最终一致多种分布式事务解决方案,并易于扩展
具备人工干预的手段