基于两阶段提交的分布式事务实现(UP-2PC)

引言:分布式事务是分布式数据库的基础性功能,在2017年上海MySQL嘉年华(IMG)和中国数据库大会(DTCC2018)中做者都对银联UPSQL Proxy的分布式事务作了简要介绍,受限于交流形式难以作全面细致的探讨,借由本文进一步展开。前端

 

UP-2PC是面向分布式数据库的由中国银联自主研发的针对MySQL的2PC分布式事务实现,以UPSQL Proxy(分布式式数据库代理)做为事务管理器,UPSQL(MySQL银联定制版本)为资源管理器。mysql

因为MySQL在5.7中完全解决了xa prepare的高可用问题以及链接关闭自动回滚等问题,使得MySQL的xa真正可用。sql

分布式事务

若是了解分布式事务与2PC,并考虑过以下议题(没必要承认),可略过本节内容:数据库

• TCC是应用层的2PC实现编程

• 2PC的实现难点在于事务管理器(TM)的异常处理安全

• 2PCPaxos:Paxos用于相同数据的分布式高可用存储,2PC更擅长不一样数据分布式存储的事务一致性性能优化

2PC协议(Prepare,Commit, Rollback)服务器

首先咱们要提到分布式事务模型:X/Open DTP(Distributed Transaction Processing) Reference Model。网络

• X/Open组织定义的一套分布式事务的标准,定义了规范和API接口,由厂商进行具体的实现并发

• X/Open DTP定义了三个组件: AP,TM,RM

• AP(Application Program):使用分布式事务的应用

• RM(Resource Manager):资源管理器,这里能够是一个DBMS系统,或者消息服务器管理系统,资源管理器必须实现XA定义的接口

• TM(Transaction Manager):事务管理器,负责协调和管理事务

一般把TM管理的分布式事务称为全局事务,把RM管理的事务称为本地事务。

两阶段提交,是X/Open DTP的一个实现,主要在事务结束动做前加入了一个Prepare阶段:

• Prepare阶段:Prepare成功的数据,后续Commit必定会成功。

• Commit/Rollback阶段:提交或回滚数据

 

图例:2PC协议

 

DTP很早就指出资源管理器的类型不仅仅是数据库,还能够是消息服务。


TCC协议(Try, Confirm, Cancel)

TCC编程: 将业务逻辑拆解为Try、Confirm和Cancel三个阶段,实际上两阶段提交的变种。业务场景如:锁单、下单和取消。

TCC编程须要保证幂等性,即:能够被不断调用接口(直至符合预期),于是须要保证屡次调用不会致使异常影响(如重复扣款等)。2PC的主要问题

2PC协议,主要是在以下场景的处理较为复杂:

• 同步阻塞、应答超时

• TM宕机恢复

 

2PC比较:数据库VS应用

比较容易取得共识的结论:不一样业务系统之间使用2PC。

那剩下的问题就简单了, 相同业务系统之间是使用数据访问层2PC仍是TCC?通常而言,基于研发成本考虑,会建议:新系统由数据库层来实现统一的分布式事务。

但对热点数据,例如商品(票券等)库存,建议使用TCC方案,由于TCC的主要优点正是能够避免长时间锁定数据库资源进而提升并发性。

 

2PC与Paxos

有一种广为流传的观点:"2PC到3PC到Paxos到Raft",即认为:

• 2PC是Paxos的残次版本

• 3PC是2PC的改进

上述2个观点都是我所不认同的,更倾向于以下认知:

• 2PC与Paxos解决的问题不一样:2PC是用于解决数据分片后,不一样数据之间的分布式事务问题;而Paxos是解决相同数据多副本下的数据一致性问题。例如,UP-2PC的数据存储节点可使用MGR来管理统一数据分片的高可用

• 3PC只是2PC的一个实践方法:一方面并无完整解决事务管理器宕机和资源管理器宕机等异常,反而由于增长了一个处理阶段让问题更加复杂

 

MySQL的2PC语法(XA)

MySQL提供的2PC语法(MySQL XA)以下:

XA {START|BEGIN} xid [JOIN|RESUME]
XA END xid [SUSPEND [FOR MIGRATE]]
XA PREPARE xid
XA COMMIT xid [ONE PHASE]
XA ROLLBACK xid
XA RECOVER [CONVERT XID]

 

图例:MySQL XA语法

 

咱们针对语法组合方式略微展开。

2PC的事务提交流程

2PC提交流程为:

XA {START|BEGIN} xid [JOIN|RESUME]
/*事务操做*/
XA END xid [SUSPEND [FOR MIGRATE]]
XA PREPARE xid
XA COMMIT xid

 

2PC的事务回滚流程

2PC回滚流程为:

XA {START|BEGIN} xid [JOIN|RESUME]
/*事务操做*/
XA END xid [SUSPEND [FOR MIGRATE]]
XA PREPARE xid /*可选*/
XA ROLLBACK xid

注意回滚时Prepare阶段不是必须的,因此能够在以下场景中能够省略Prepare阶段,减小网络交互:

• 本地事务未发生写操做

• 本地事务须要回滚

固然没有Prepare阶段也就意味着,其从两阶段退化为一阶段了。

 

2PC退化为一阶段提交

XA语法下退化为一阶段提交的流程为:

XA {START|BEGIN} xid [JOIN|RESUME]
/*事务操做*/
XA END xid [SUSPEND [FOR MIGRATE]]
XA COMMIT xid ONE PHASE

使用XA的一阶段提交(XA COMMIT xid ONE PHASE)的一个典型场景为在全局事务中只有该本地事务有写操做,在UP-2PC由于使用"最后参与者"方案,因此最后参与者也会使用XA一阶段提交。

 

2PC恢复处理

这里指当与MySQL的交互出现异常,例如与MySQL的会话断开、MySQL宕机等,如何获取并恢复处于Prepare状态下的2PC事务,并进行提交或回滚处理。

mysql> XA RECOVER;

 

UP-2PC技术实现

UP-2PC要解决的核心问题正是如何处理:事务管理器、资源管理器异常,以及可能的网络。

• 事务管理器(TM)忽然宕机以及网络异常,致使协调信息丢失。典型的:异常发生后,处于Prepare状态的RM,正确进行后续处理(rollback和commit)

• 部分资源管理器(RM)忽然宕机以及网络异常,致使的信息协调。典型的:全局事务内多个Prepare状态,此时若部分RM发生本异常,则TM如何处理,可选方案:

1.全局事务向请求者应答成功,由事务事务管理器后续提交异常RM

2.回滚全部RM,并向前端应答事务提交失败

经过上述探讨,能够发现2PC的主要难点在于:

• 事务状态信息维护:全局事务状态(TM)、本地事务状态(RM)

• 根据事务状态信息的异常处理策略

毫无疑问事务状态信息不丢失,是实现安全2PC的基础,因为本地事务状态信息能够经过MySQL高可用集群解决,因此咱们仅须要:保证全局事务状态(TM)不丢失。

 

分布式事务的工程实践

在给出UP-2PC的方案以前,咱们能够先了解下已有的分布式事务工程实践经验,“Distributed transactions in Spring, with and without XA”(Spring的分布式事务,使用或不用XA;Spring分布式事务的7种实现)是一篇不错的文章,很好总结了既有实践经验,这里仅对其中涉及XA的两种优化方案进行说明:

• 一阶段提交优化:若是全局事务只涉及一个RM,则不使用两阶段

• 最后参与者优化(Last Participant Support):选择一个RM(最后参与者)不使用两阶段提交,全局事务提交时,先对”最后参与者”进行(一阶段)提交.该策略又被称为:最后资源提交优化(Last Resource Commit Optimization)。

 

图例:最后参与者优化

 

乍看起来“最后参与者优化”方案效果有限,但事实上该方案直接指明了UP-2PC的实现路径,由于:

1.“一阶段提交优化”能够视为“最后参与者优化”的特例,即只全局事务只包含一个RM时二者等效

2.“最后参与者优化”指出了最后参与者在全部RM中最为重要,其事务提交结果决定着全局事务的结果,而前面也提到了全局事务状态是最重要的,因此咱们天然推导出应该由最后参与者记录全局事务状态(咱们称为xa-log)

 

UP-2PC核心方案

UP-2PC核心方案,包含3点:

1.利用RM节点分布式存全局事务状态信息(xa-log)

2.使用最后参与者协议

3.由最后参与者记录全局事务状态信息(xa-log)

xa-log中记录了全局事务id所涉及的全部资源管理器节点列表,和对应的本地xid,以及全局事务状态(commit|rollback)。

这里仅对全局事务须要进行commit作下说明,若是最后参与者commit成功,则认为全局事务commit成功,若最后参与者commit成功而其余资源管理器异常,则交由异常处理程序轮询xa-log表完成其余资源管理器的xa commit操做。

最后参与者的选择策略:若是第一个本地事务参与者若是发生了写操做,则选择其为最后参与者;不然在其余发生了写操做的参与中随机选择。该选择策略实现了最后参与者的分布式随机选择,进而保证xa-log的分布式随机分布。

 

图例:UP-2PC实现

 

如图所示,UP-2PC的实现要点在于:

1.全局事务提交,则由最后参与者记录xa-log

2.最后参与者提交前,须要保证其余参与则处于prepare状态

3.在全部参与者事务提交结束后,删除xa-log

 

UP-2PC异常处理

在确保全局事务状态(xa-log)安全保存后,就能够针对2PC的各类异常进行处理。

不考虑TM异常,将异常按时间线进行区分:

1.全局事务commit以前:由于没有执行本地xa和xa-log的相关操做,则按事务提交失败处理

2.全局事务执行commit阶段:

– xa-log记录与prepare阶段异常:则回滚全部本地事务

– 最后参与者commit异常:则查询xa-log,如xa-log有全局事务的com mit记录,则视为事务提交成功;没有则视为提交失败;查询结果不肯定 (如最后参与者宕机等),则事务状态不肯定,能够考虑断开与AP链接(即不该答AP事务状态), 或保持链接直至获得查询结果未知。

– 其余参与者commit异常:依然应答全局事务成功,交由后续异步commit。

其余异常及异常处理: 以xa-log记录为准,对比xa-log与RM的xa-id信息进行对比分析进行提交或回滚操做。

须要指出的是因为TM是集群多机部署,因此xaid在编码时须要能区分不一样TM节点信息。

 

TDSQL分布式事务实现的区别

在已公开的同类产品中TDSQL与UP-2PC的解决思路最为接近,这里作下简单对比。

 

图例:TDSQL与UP-2PC对比

注:示例来自TDSQL的公开介绍材料,为简化描述略做修改

UP-2PC相对TDSQL的分布式事务解决方案主要区别在于UP-2PC采用了最后参与者策略,由最后参与者进行xa-log记录。

优势:减小启用一个数据库会话、一次网络交互、减小一个事务、退化一个2PC事务为一阶段

缺点:业务数据库用户须要具备xa-log的读写权限,于是具备运维风险,特别是在复用数据库实例的状况下

一次进一步展开思考,TDSQL不使用最后参与者策略的理由可能以下:

• 做为云上产品,应尽量避免运维风险

• XA业务场景占比较少,性能影响有限

• 方便未来xa-log由其余方式存储

 

UP-2PC性能优化

在实现UP-2PC方案后,XA分布式事务的性能影响大约为30%(依赖场景),虽然符合预期,但也说明须要进一步优化。

这里给出UP-2PC两个性能优化方案:

• 本地事务启动优化

• MySQL XA协议优化

 

本地事务启动优化

对于属于不可重复读的全局事务,咱们能够延迟开启本地事务以减小资源锁定时间。

本地事务启动优化的核心策略:

1.全局事务为不可重复读

2.本地事务只有发生了写才须要开启本地XA事务

针对以下业务场景:

/*事务隔离级别为read-commited或read-uncommited*/
Begin;
select db1 and db2; //1st
update db1;
update db2;
select db1 and db2; //2nd
Commit;

咱们推演下全局事务管理器对上述语句的处理逻辑:

1.当第一次执行查询操做时,因为db1和db2都未发生写操做,则没必要开启本地事务,在查询结束后能够将本地事务会话归还链接池。

2.在遇到第一次写入操做时,分别开启对应的本地事务管理器。

3.在后续查询时,因为已持有本地事务链接,则使用该链接进行查询操做。

而实际的场景中,可能操做以下:

Begin;
select db1 and db2; 
update db1;
select db1 and db2;
Commit;

即全局事务内,查询的节点数量多于更新的节点数量,经过本方案只会对写入数据库节点开启本地XA事务,从而避免了没必要要的事务开启操做。

 

MySQL XA协议优化

在实现UP-2PC的过程当中咱们一直有这样一个设计指望:在全局事务只涉及一个本地事务,咱们可否对该事务不启用xa协议?

在咱们实现“本地事务启动优化”后,这个问题视乎解决了,由于让最后参与者不使用XA协议就能够轻松实现了上述目标,但最后参与者不使用XA协议可能会致使咱们没法解决后面说起的分布式死锁问题。

因而咱们转换思路,对咱们的设计目标的本质进行剖析,即XA一阶段提交与传统事务提交有什么区别?

答案:多了一个xa end。

因而XA协议优化方案也就呼之欲出,即省略掉没必要需要的XA END。

 

图例:UP-2PC优化方案

如图所示,经过减小了xa end阶段,减小了交互次数天然提升了系统健壮性,也提升了性能。

 

分布式事务带来的新问题

在实现了基于2PC的分布式事务,对外提供统一事务管理特征,也会引入一些新问题,例如:

• 分布式事务死锁

• 对外XA协议支持(外部2PC)

• Savepoint支持

这里仅对部分解决方案进行说明。

 

分布式事务死锁

因为分布式事务管理多个RM,因此必然会带来分布式资源竞争和分布式死锁。例如:

• 会话a 和 b 开启分布式事务

• time 1: a   write db1.resource1

• time 2: b   write db2.resource2

• time 3: a   write db2.resource2 -> 锁:等待b的 db2.resource2

• time 4: b   write db1.resource1 -> 锁:等待a的 db1.resource1

在时间点4, a和b发生了资源循环等待,从而出现了分布式死锁。

如何解决分布式死锁呢,这里提供一种死锁检测方法:

• 扩展innodb_trx表,增长事务xid的记录

• TM层确保同一全局事务所对应的本地事务xid相同

这样就能够对全部全局事务的锁信息进行分析,并发现分布式死锁。

下表为扩展innodb_trx的效果,因为代码修改较为简单这里就不累述。

 

另外一种方法是在本地事务中加入事务时间信息,在RM层根据时间前后顺序,保证只会存在单一时序的资源等待,从而避免死锁发生。

 

对外XA协议支持(外部2PC)

若是咱们将应用UP-2PC的数据库集群视为一个总体,则实现分布式事务的过程为内部2PC,对外部应用提供2PC协议支持(即支持XA协议),则称为外部2PC。数据集群的内部外部2PC,能够与MySQL的内部外部2PC对应理解。

该逻辑较为简单,即在外部执行XA PREPARE时,咱们在xa-log中记录全局事务状态为Prepare便可,须要指出的是:外部2PC下,内部的分布式事务没法使用前述的最后参与者优化方案。

 

图例:外部两XA协议实现

 

总结

本文介绍了UP-2PC的具体实现,涵盖了核心解决方案与异常处理策略、性能优化、带来的新问题与解决方案。

相关文章
相关标签/搜索