简介
X/Open XA 协议
最早的分布式事务模型是 X/Open 国际联盟提出的 X/Open Distributed Transaction Processing(DTP)模型,也就是大家常说的 X/Open XA 协议,简称XA 协议。
https://publications.opengroup.org/standards/dist-computing
https://publications.opengroup.org/standards/dist-computing/c193
DTP 模型中包含一个全局事务管理器(TM,Transaction Manager)和多个资源管理器(RM,Resource Manager)。全局事务管理器负责管理全局事务状态与参与的资源,协同资源一起提交或回滚;资源管理器则负责具体的资源操作。
XA 协议描述了 TM 与 RM 之间的接口,允许多个资源在同一分布式事务中访问。
流程
基于 DTP 模型的分布式事务流程大致如下:
应用程序(AP,Application)向 TM 申请开始一个全局事务。 针对要操作的 RM,AP 会先向 TM 注册(TM 负责记录 AP 操作过哪些 RM,即分支事务),TM 通过 XA 接口函数通知相应 RM 开启分布式事务的子事务,接着 AP 就可以对该 RM 管理的资源进行操作。 当 AP 对所有 RM 操作完毕后,AP 根据执行情况通知 TM 提交或回滚该全局事务,TM 通过 XA 接口函数通知各 RM 完成操作。TM 会先要求各个 RM 做预提交,所有 RM 返回成功后,再要求各 RM 做正式提交,XA 协议要求,一旦 RM 预提交成功,则后续的正式提交也必须能成功;如果任意一个 RM 预提交失败,则 TM 通知各 RM 回滚。 所有 RM 提交或回滚完成后,全局事务结束。
原子性
XA 协议使用 2PC(Two Phase Commit,两阶段提交)原子提交协议来保证分布式事务原子性。
两阶段提交是指将提交过程分为两个阶段,即准备阶段(投票阶段)和提交阶段(执行阶段):
- 准备阶段:
TM 向每个 RM 发送准备消息。如果 RM 的本地事务操作执行成功,则返回成功;如果 RM 的本地事务操作执行失败,则返回失败。
- 提交阶段
如果 TM 收到了所有 RM 回复的成功消息,则向每个 RM 发送提交消息;否则发送回滚消息;RM 根据 TM 的指令执行提交或者回滚本地事务操作,释放所有事务处理过程中使用的锁资源。
隔离性
XA 协议中没有描述如何实现分布式事务的隔离性,但是 XA 协议要求DTP 模型中的每个 RM 都要实现本地事务,也就是说,基于 XA 协议实现的分布式事务的隔离性是由每个 RM 本地事务的隔离性来保证的,当一个分布式事务的所有子事务都是隔离的,那么这个分布式事务天然的就实现了隔离性。
以 MySQL 来举例,MySQL 使用 2PL(Two-Phase Locking,两阶段锁)机制来控制本地事务的并发,保证隔离性。2PL 与 2PC 类似,也是将锁操作分为加锁和解锁两个阶段,并且保证两个阶段完全不相交。加锁阶段,只加锁,不放锁。解锁阶段,只放锁,不加锁。
如上图所示,在一个本地事务中,每执行一条更新操作之前,都会先获取对应的锁资源,只有获取锁资源成功才会执行该操作,并且一旦获取了锁资源就会持有该锁资源直到本事务执行结束。
MySQL 通过这种 2PL 机制,可以保证在本地事务执行过程中,其他并发事务不能操作相同资源,从而实现了事务隔离。
一致性
前面提到一致性有两层语义,一层是确保事务执行结束后,数据库从一个一致状态转变为另一个一致状态。另一层语义是事务执行过程中的中间状态不能被观察到。
前一层语义的实现很简单,通过原子性、隔离性以及 RM 自身一致性的实现就可以保证。至于后一层语义,我们先来看看单个 RM 上的本地事务是怎么实现的。还是以 MySQL 举例,MySQL 通过 MVCC(Multi Version Concurrency Control,多版本并发控制)机制,为每个一致性状态生成快照(Snapshot),每个事务看到的都是各Snapshot对应的一致性状态,从而也就保证了本地事务的中间状态不会被观察到。
虽然单个 RM 上实现了Snapshot,但是在分布式应用架构下,会遇到什么问题呢?
如上图所示,在 RM1 的本地子事务提交完毕到 RM2 的本地子事务提交完毕之间,只能读到 RM1 上子事务执行的内容,读不到 RM2 上的子事务。也就是说,虽然在单个 RM 上的本地事务是一致的,但是从全局来看,一个全局事务执行过程的中间状态被观察到了,全局一致性就被破坏了。
XA 协议并没有定义怎么实现全局的 Snapshot,像 MySQL 官方文档里就建议使用串行化的隔离级别来保证分布式事务一致性: “As with nondistributed transactions, SERIALIZABLE may be preferred if your applications are sensitive to read phenomena. REPEATABLE READ may not be sufficient for distributed transactions.”(对于分布式事务来说,可重复读隔离级别不足以保证事务一致性,如果你的程序有全局一致性读要求,可以考虑串行化隔离级别.)
当然,由于串行化隔离级别的性能较差,所以很多分布式数据库都自己实现了分布式 MVCC 机制来提供全局的一致性读。一个基本思路是用一个集中式或者逻辑上单调递增的东西来控制生成全局 Snapshot,每个事务或者每条 SQL 执行时都去获取一次,从而实现不同隔离级别下的一致性。比如 Google 的 Spanner 就是用 TrueTime 来控制访问全局 Snapshot。
小结
XA 协议通常实现在数据库资源层,直接作用于资源管理器上。因此,基于 XA 协议实现的分布式事务产品,无论是分布式数据库,还是分布式事务框架,对业务几乎都没有侵入,就像使用普通数据库一样。
XA 协议严格保障事务 ACID 特性,能够满足所有业务领域的功能需求,但是,这同样是一把双刃剑。
由于隔离性的互斥要求,在事务执行过程中,所有的资源都被锁定,只适用于执行时间确定的短事务。同时,整个事务期间都是独占数据,对于热点数据的并发性能可能会很低,实现了分布式 MVCC 或乐观锁(optimistic locking)以后,性能可能会有所提升。
同时,为了保障一致性,要求所有 RM 同等可信、可靠,要求故障恢复机制可靠、快速,在网络故障隔离的情况下,服务基本不可用。