系列目录
一、引子
事务(数据库事务)是java开发工程师必须掌握的一项技能。又可分为本地事务和分布式事务,其中分布式事务是进阶为高级开发工程师必会的技能。本文从概念、原理、实践多角度剖析分布式事务,希望有所收获。
二、概念
2.1.本地事务
大部分情况下,一个服务操作一个数据库,这就是本地事务,ACID特性由数据库提供支持,比如mysql innodb引擎。如下图所示(网上的图,挺好直接用):
spring 提供了2种方式实现:
- 编程式:基于transactionTemplate去实现,适合手动精准控制事务的场景,少用。
- 声明式事务注解:@Transactional加在serviceImpl的方法上即可,这也是常用的方法。
关于本地事务这里不多说,飞机票:本地事务飞机票。
2.2.分布式事务
当遇到复杂业务调用时,可能会出现跨库多资源调用(一个事务管理器,多个资源)/多服务调用(多个事务管理器,多个资源),期望全部成功或失败回滚,这就是分布式事务,用以保证“操作多个隔离资源的数据一致性”。
2.3 相关协议发展历史
三、DTP模型 & XA规范
背景
Mysql官方对于XA事务,描述如下:
- Mysql InnoDB引擎支持分布式事务,mysql的XA实现是基于X/Open CAE 文档中的 Distributed Transaction Processing: The XA Specification. (DTP XA规范)的。飞机票:13.3.7 XA Transactions官方飞机票。X/Open CAE解释如下:
- X/Open: X/Open是一个独立的、全球性的开放系统组织,由世界上最大的信息系统供应商、用户组织和软件公司支持。其使命是通过开放系统的实际实施,为用户带来更大的计算价值。
- X/Open CAE规范: 即X/Open Common Applications Environment,这个环境覆盖了高于硬件级别的,支持开放系统所需的一组标准。它提供了应用程序的可移植性和互操作性。
- MySQL Connector 5.0.0及更高版本直接支持XA,通过一个类接口为您处理XA SQL语句接口。XA支持分布式事务,即允许多个单独的事务资源参与全局事务。事务资源通常是rdbms,但也可能是其他类型的资源。
- X/Open CAE文档是发布在open group官网上的,http://www.opengroup.org/public/pubs/catalog/c193.htm。
在open group官网可查到,有2个XA规范,一个是XA,一个是XA+, 其中XA+是XA的超集,新定义了通信资源管理器CRM的协议,建议直接看XA+即可。后续分析直接以XA+ 1994版为准。官方下载链接如下:
- 分布式事务处理:参考模型 Distributed Transaction Processing: Reference Model
- 分布式事务处理:XA+规范 Distributed Transaction Processing:The XA+ Specification
看名字我们就知道 XA规范是依托于DTP场景的,下面我们分别从DTP模型、XA规范2个视角来剖析原理。
3.1 DTP模型
依据X/Open《Distributed Transaction Processing: Reference Model, Version 3》上的介绍,DTP模型是一种软件体系结构,它允许多个应用程序共享多个资源管理器提供的资源,并允许将它们的工作协调到全局事务中。
3.1.1 模型元素
要深度了解DTP,先看看模型内的元素概念,如下:
- 应用程序(Application Program ,简称AP):每个AP指定一个包含资源(如数据库)的操作序列。AP定义全局事务的开始和结束,访问事务边界内的资源,通常决定是提交还是回滚每个事务。
- 资源管理器(Resource Manager,简称RM):如数据库、文件系统等,并提供访问资源的方式。
- 事务管理器(Transaction Manager ,简称TM):管理全局事务,负责分配事务唯一标识XID,监控事务的执行进度,并负责事务的提交、回滚等。如果RM是一个通信资源管理器(CRM),那么在执行两个APs之间的通信时,它将xid传递给合作伙伴、从属的CRMs。
- 通信资源管理器(Communication Resource Manager,简称CRM):控制一个TM域(TM domain)内或者跨TM域的分布式应用之间的通信。
- 通信协议(Communication Protocol,简称CP):一种通信协议,它提供分布式应用程序使用的、由CRMs支持的底层通信服务。
介绍完模型元素,下面来看2种典型的DTP场景,一种是单应用跨库DTP,另一种是跨应用DTP。
3.1.2 单应用跨库DTP
一个应用使用一个事务管理器TM,操作多个资源管理器RMs,如下图:
3.1.3 跨应用DTP
如果分布式事务需要跨多个应用,例如微服务调用,那就必须增加通讯资源管理器CRMs(跨应用管理事务),如下图:
上图中使用的接口如下:
- AP-RM接口 : 允许AP访问资源,如SQL和ISAM,提供AP可移植性。
- AP-TM接口 : 即TX接口,为AP提供了一个API, AP通过API与TM协调全局事务管理。
- TM-RM接口 : 即XA接口,允许TM将RMs的工作构造为全局事务,并协调完成或恢复。XA接口是TM与RM之间的双向接口。
- TM-CRM接口 : 即XA+接口,支持跨TM域的全局事务信息流。XA+ 接口是TM与CRM之间的双向接口。
- AP-CRM接口 : 为全局事务中的多应用之间的DTP通信提供了可移植的api,例如:TxRPC、XATMI、Peer-to-Peer。
- CRM-OSI TP接口 : 即XAP-TP接口,提供了CRM和OSITP(Open Systems Interconnection — Distributed Transaction Processing)服务之间的编程接口。X/Open定义了这个接口来支持特定于应用程序的OSI服务的可移植实现。
本节我们剖析了DTP模型,以及XA接口在DTP中的作用,下面我们来更详细的看一下XA规范。
3.2 XA规范
通过上面的分析,我们知道XA和XA+规范的使用场景,如下图所示:
下面来具体看一下XA/XA+接口定义的函数群。其中带+号的是XA+规范,不带+号的是XA规范。
3.2.1 xa_*()函数群
TM通过xa_*()函数调用RM。当AP调用TM启动全局事务时,TM可以使用xa_interface通知事务分支的RMs。AP使用RM的本机接口完成支持全局事务的工作后,TM调用xa_()函数提交或回滚分支。xa_()函数如下表所示:
3.2.2 ax_*()函数群
RM通过ax_*()函数调用TM。所有的TMs都必须提供这些功能。这些函数允许RM动态地控制它在事务分支中的参与。此外,CRMs使用ax_interface创建事务分支,挂起或完成事务分支,并将承诺协议传播到事务分支。ax_()函数如下表所示:
关于XA/XA+的具体方法如何调用流程这里就不再提供。有兴趣的自己看规范原文。
3.3 两阶段提交-2PC
XA协议中有一个细节:按照OSITP标准(模型)的定义,TMs和RMs使用两阶段提交全局事务。
3.3.1 XA的两阶段提交模型
如上图,XA规范实现的两阶段提交流程:(下面全部翻译自XA规范原文)
阶段1:
TM要求所有RMs准备提交(或准备)事务分支。这询问RM是否能够保证提交事务分支的能力。RM可能会查询该RM内部的其他实例。CRM被要求准备它们创建的事务分支,将prepare请求发送到远程站点并接收结果。在返回失败并回滚其工作之后,RM可以丢弃事务分支的信息。
阶段2:
TM根据实际情况向所有RMs发出提交或回滚事务分支的请求。CRM被要求提交或回滚它们创建的事务分支,向远程站点发送提交或回滚请求并接收结果。所有RMs提交或回滚对共享资源的更改,然后将状态返回给TM。然后TM可以丢弃全局事务的信息。
3.3.2 XA对2PC的优化
1.只读断言
当事务分支没有更新共享资源时,这个RM会断言并响应给TM的prepare请求。也就免去了阶段2。但是,如果一个RM在全局事务的所有RMs返回prepared之前返回了只读优化,该RM释放事务上下文,例如read locks。这时候其他事务就有机会去改变这些数据(可能是写锁),显然全局序列化被破坏。同样CRM也可以断言,当TM挂起或终止线程与事务分支的关联时,它不是某个特定线程中活动的事务分支的参与者。
2.一阶段提交
如果一个TM知道DTP系统中只有一个RM在修改共享资源,那么它可以使用单阶段提交。即TM免去了阶段1的prepare,直接执行了阶段2的commit。
3.3.3 2PC的缺点
1.资源阻塞
由于协调者的重要性,一旦协调者TM发生故障。参与者RM会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
2.数据不一致
在阶段二,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。
由于二阶段提交存在着这些缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。
3.4 三阶段提交-3PC
3PC,即Three-phase commit protocol,由一个协调者领导事务(领头人),和一组被指导的参与者(同伙)组成。协调者和参与者都有超时执行机制,如下图:
阶段1:
【协调者】接收事务请求。如果此时出现故障,协调者将中止事务,否则,协调者发送一个canCommit给所有参与者,并切换到waiting状态。
【参与者】获得了canCommit的请求,如果同意,它将向协调者发送Yes消息并切换到prepared状态。否则它将发送No消息并中止。如果出现故障,它将移动到abort状态。
阶段2:
【协调者】在一个时间段内,接收来自所有参与者的Yes消息,向所有参与者发送preCommit消息并切换到prepared状态。如果出现故障、超时或协调者(prepared状态)接收到No消息,将中止事务并向所有参与者发送abort消息。
【参与者】收到preCommit消息,它将发送ACK消息并等待最后的提交或中止。如果接收到中止消息、失败或等待提交的超时,它将中止。
阶段3:
【协调者】在收到来自大多数参与者的确认的情况下,协调者切换到commit状态。并向所有参与者发送doCommit请求。如果协调者在等待一个参与者的ack时超时了,它将中止事务。
【参与者】参与者接收到doCommit请求之后,执行正式的事务提交,并发送ack给协调者。注:超时未收到消息,一样会提交事务!!!!
3.5 总结
上面讲解了2pc、xa、3pc,比较如下:
协议/优缺点 | 优点 | 缺点 |
2PC | 逻辑简单,容易理解。 |
1.全程阻塞。例如:TM故障,RM阻塞资源。 2.网络故障时,部分commit,数据一致性无法保证。 |
XA(提交时使用2PC规范) |
1.只读断言 2.可进化为一阶段提交 |
全局序列化被破坏。脏读问题。 |
3PC |
引入双边超时机制,避免阻塞。 |
1.需要3次请求返回,可能会有长延迟,性能低。 2.基于失败-停止(fail-stop)模型,出现网络问题,无法恢复。 |
问题:
上面3种协议都无法解决分布式系统下的数据一致性问题,只有Paxos算法,才能彻底解决该问题。paxos飞机票:底层算法系列:Paxos算法。具体实践中,为了提高可用性(性能)一般很少做到强一致性。且大批的技术先驱们已经总结出了一套理论,让我们有理可依。
四、CAP理论
2000年7月,Eric Brewer教授在ACM PODC会议上提出CAP猜想。Brewer认为在设计一个大规模的分布式系统时会遇到三个特性:一致性(consistency)、可用性(Availability)、分区容错(partition-tolerance),而一个分布式系统最多只能满足其中的2项。2年后,麻省理工学院的Seth Gilbert和Nancy Lynch从理论上证明了CAP。之后,CAP理论正式成为分布式计算领域的公认定理。
1. 一致性(Consistency)
一致性指“all nodes see the same data at the same time”,即更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。
强一致性:所有节点在同一时间的数据完全一致,那么称之为强一致性。
弱一致性:此外,如果允许存在部分数据不一致,那么就称之为弱一致性。
最终一致性:如果允许存在中间状态,只要求经过一段时间后,数据最终是一致的,则称之为最终一致性。
2. 可用性(Availability)
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
3. 分区容错性(Partition tolerance)
分区容错的意思是,节点间通信可能失败,仍然需要能够保证对外提供满足一致性和可用性的服务。
4.1 分析
首先我们必须保证P(分区容错性),才能称之为一个分布式系统,因此只能在C(一致性)和A(可用性)之间寻求平衡。而前面我们提到的X/Open XA 两阶段提交协议的分布式事务方案,强调的就是一致性。并且由于其阻塞执行效率低,且当网络出现问题时也无法真正保证数据一致性,实际应用的并不多。而基于BASE理论的柔性事务,强调的是可用性,目前大行其道,大部分互联网公司采可能会优先采用这种方案。(有的同学问为啥不用paxos?实现过于复杂,且保证了强一致性,想一想也知道性能会有损耗,所以一般也不用!)
五、BASE理论
2008年7月28日,eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论。文章链接:https://queue.acm.org/detail.cfm?id=1394128
BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consistency)。
BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。
1. 基本可用(Basically Available)
指分布式系统在出现不可预知故障的时候,允许损失部分可用性。
2. 软状态( Soft State)
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。
3. 最终一致( Eventual Consistency)
强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。
BASE理论面向的是大型高可用可扩展的分布式系统,和传统的事物ACID特性是相反的。通过牺牲强一致性来获得可用性,允许数据在一段时间内是不一致的,但最终达到一致状态。实际应用中,会在对数据库操作进行本地事务(ACID特性)+Eventually consistent最终一致性(BASE理论)结合使用。
那么如何实现分布式环境下数据的最终一致性呢?
六、最终一致性方案(柔性事务)
实践中,有些高可用场景下,不必要强一致性,只需要最终一致性即可,这在业内称呼为"柔性事务",也就是最终一致性方案,是遵循BASE理论设计出来的。
6.1 正向幂等重试+反向异步回调(最大努力通知型)
对于某些非核心service,可以采取正向重试机制。比如一个请求超时失败了,可以再重试请求几次,一直到接收到成功返回或者达到重试次数为止。注意这里要保证接口的幂等性。即多次调用结果一样。
很多调用第三方的接口(比如征信接口,耗时比较长),接口是异步回调型。请求方发送请求后,等待第三方异步回调自己的返回结果接口。
这两种机制都是不可靠的,必要时刻可以两者相结合使用。如下图所示:
6.2 可靠消息最终一致(异步确保型)
这里不讲解已支持分布式事务的MQ.(例如RocketMQ)
可靠消息就是使用独立的消息服务,使用“预发送机制”把消息提前入库,业务确定执行完毕,再修改消息状态为可发送,然后再发送消息给MQ,消费者再消费。
预发送机制
如果先执行业务,再发消息(先发消息再执行业务也不行),入kafka,可能立刻就消费了。本地事务回滚是无法回滚已发送到kafka的消息的。使用预发送机制,保证了消息服务DB中有一条“初始化”状态的消息记录。业务异常,就不会“确认发送Msg”,消息就不会发送。
可靠场景下,甚至可以在消息服务中轮询”初始化“状态的且过了“一个时间段”(一般超过这个时间,肯定是出问题了)的消息,再去查询业务系统是否完成,如果完成则自修复成"待发送"状态。
注:上图主业务作为生产者,严格来说,消息服务平台才是生产者(如果把kafka作为中心的话)。
整个流程如上图:
- 消息入库:主业务系统,初始化一条消息进msg表,state=初始化。
- 确认可发送:更新消息状态state=待发送。
- send生产消息:定时轮询state=待发送 的消息,send给kafka。
- pull消费消息:从业务作为消费者主动去kafka 拉取消息消费。
- ack告知kafka:消费成功告知kafka。
- 告知消息服务:消费成功告知消息服务,更新状态state=完成,或者删除消息记录。(如果场景要求较高建议留下存根)
本地消息表(业务系统+消息表,强一致性)
如果不使用独立的消息服务平台,在业务系统内部新建一张消息表,就可以完全由一个本地事务来控制,这样第1、2步的“消息入库”、“确认发送”可以确保成功,也就不需要“轮询自修复”了,如果公司不要求使用统一消息服务平台的话,使用本地消息表也是ok的。
6.3 TCC(两阶段补偿型)
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC 实质上是应用层的2PC(2 PhaseCommit, 两阶段提交),好比把 XA 两阶段提交那种在数据资源层做的事务管理工作提到了数据应用层。TCC流程如下图:
如上图所示,步骤:
- 阶段1
主业务活动请求(try)各个从业务服务预留资源。try过程的本地事务,是保证资源预留的业务逻辑的正确性。
- 阶段2
如果在第一阶段所有业务资源都预留成功,那么confirm各个从业务服务,否则取消(cancel)所有从业务服务的资源预留请求。
优点:
相比XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。
TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁。confirm/cancel执行的本地事务逻辑确认/取消预留资源,confirm和cancel就是补偿型事务
(Compensation-Based Transactions)。注意:confirm和cancel都是独立的本地事务,是对try的补偿。
缺点:
针对一个请求,需要从业务服务提供3个接口,供主业务服务调用,业务方改造成本高。
====参考=======
分布式事务 :第一节很多都是参考本文,写的不错。
如果你觉得本文对你有点帮助的话,记得在右下角点个“推荐”哦,博主在此感谢!