跨服务跨库的分布式事务
跨服务跨库的分布式事务
跨服务,跨数据库的分布式事务,这类分布式事务只是部分遵循 ACID 规范的:
- 原子性:严格遵循
- 一致性:事务完成后的一致性严格遵循;事务中的一致性可适当放宽
- 隔离性:并行事务间不可影响;事务中间结果可见性允许安全放宽
- 持久性:严格遵循
这里面一致性和隔离性都没有严格遵守,但是 ACID 这四个特性中,AID 这三个特性其实是数据库实现的人非常关心,而对于使用数据库的人,最终的用户,最关心的则是 C,即用户视角看,分布式事务的一致性是什么样的?对于这里面的 C(一致性),我们以一个非常具体的业务例子,来进行解释。假如我们正在处理一个转账业务,假设是 A 转给 B 30 元,在本地事务的支持下,我们的用户看到 A+B 的总金额,在整个转账前后,以及转账过程中,都是保持不变的。那么这个时候用户认为他看到的数据是一致的,符合业务约束的。
当我们业务变复杂,引入多个数据库和大量微服务时,上述本地事务的一致性,依旧是业务非常关心。假如一个业务更新操作,跨库或者跨服务时,那么此时业务关心的一致性问题,就变成了 分布式事务中的一致性问题。在单机本地事务中,A+B 的总金额在任何时刻去查(以常见的 ReadCommitted 或 ReadRepeatable 隔离级别),都是不变的,也就是业务约束一直都保持的这种一致性,我们称之为强一致性。
无法强一致
目前在跨库、跨服务的分布式实际应用中,尚未看到有强一致性的方案。我们来看看一致性级别最高的 XA 事务,是否是强一致的,我们以跨行转账(在这里,我们以跨库更新 AB 来模拟)作为例子来说明,下面是一个 XA 事务的时序图:
在这个时序图中,我们在如图所示的时间点发起查询,就是在两个 commit 中间进行的查询,那么我们查到的结果数据,将是 A+B+30,不等于 A+B,不符合强一致的要求。从理论上分析,由于是分布式系统,那么一定是无法保证两个 commit 同时结束,只要两个 commit 中间有时间差,那么无论如何我们都无法保证强一致性。
理论上的强一致性
我们接下来思考,普通 XA 事务不是强一致的,但假如完全不考虑性能因素,有没有可能在理论上做到强一致:我们先看看如果我们把 XA 事务涉及的数据库,隔离级别设定到 Serializable,是否能到到强一致的效果呢?我们来看看前面的时序场景:
这种情况下,查到结果等于 A+B,但是又有另一些场景出现了问题,如下图所示:
按照图中时序查询的结果是:A+B-30,依旧是不一致。深入思考这个强一致的问题之后,有一种做法可以做到强一致,做法如下:
- 对于查询,也采用 XA 事务,并且查询数据时,采用 select for update 的方式,所有数据查完之后,再 xa commit
- 为了避免死锁,需要将涉及到的数据库排序,访问数据都必须要按照相同的数据库顺序来写入和查询
在上述策略下,我们可以看到,在时序图任何一个时间点进行查询,获得的结果都是 A+B
在 T0 时间查询,那么修改一定发生在查询全部完成之后,所以查询得到结果 A+B 在 T1,T2,T3 查询,那么查询结果返回一定全部发生在修改完成之后,所以查询得到结果也是 A+B 很明显这种理论上的强一致,效率极低,所有有数据交集的数据库事务都是串行执行,而且还需要按照特定的顺序查询/修改数据,因此成本极高,几乎无法应用在生产中。
未来有没有可能借鉴 NewSQL 的这种方式,来实现跨库、跨微服务这类分布式事务的强一致性?理论上是可以的。
- 实现跨服务但不跨库的分布式事务一致性,会相对简单一些,其中一种方式就是实现 XA 事务中的 TMRESUME 选项。我们从前面的分析中看到,XA 事务的不一致,来源于分布式系统上的两个 commit 无法同时完成,现在已经在一个数据库,只是跨服务,那么 TMRESUME 可以允许我们将某个服务的 xa 事务继续往前操作,最终提交时,只有一个 xa commit,因此避免了两个 xa commit 中间的不一致时间窗口,那么就是强一致的。
- 实现跨数据库的分布式事务一致性,会困难很多,因为各个数据库的内部版本机制都不一样,想要协同非常困难。困难来自于两点:一是不同厂商之间的 MVCC 机制不一样,例如 Spanner 是 TrueTime,TiDB 是单点授时,还有一些是逻辑时钟,想要兼容多种 MVCC 非常困难。二是不同厂商难以有足够的商业利益驱动去做这样的协同。
最终一致性
从前面的分析中可以看到,在分布式事务进行的过程中,一致性是无法得到保证的,但是分布式事务完成之后,一致性是没问题的,严格遵守的。因此我们将分布式事务方案称为最终一致性方案,这个最终一致性,与 CAP 中的最终一致性用了同样的词语,但他们的具体含义是不一样的,在 CAP 中是指读取操作最终能够读取到最后一次写入的结果,在分布式事务中是指最终事务完成后,数据严格满足业务约束。
既然现有的各种分布式事务方案都无法做到强一致,那么最终一致性之间是否有差别呢?我们进行了以下关于一致性强弱的分类,一致性由强到弱分别是:
XA 事务>TCC>二阶段消息>SAGA
- 不一致窗口短:XA 和 TCC 在理想的情况下,可以做到不一致的窗口时间很短
- 不一致窗口长:SAGA 和 MSG 则缺少控制不一致窗口时间的方法,相对来说会更长
- XA:XA 虽然不是强一致,但是 XA 的一致性是多种分布式事务中,一致性最好的,因为他处于不一致的状态时间很短,只有一部分分支开始 commit,但还没有全部 commit 的这个时间窗口,数据是不一致的。因为数据库的 commit 操作耗时,通常是 10ms 内,因此不一致的窗口期很短。
- TCC:理论上,TCC 可以用 XA 来实现,例如 Try-Prepare,Confirm-Commit,Cancel-Rollback。但绝大多数时候,TCC 会在业务层自己实现 Try|Confirm|Cancel,因此 Confirm 操作耗时,通常高于 XA 中的 Commit,不一致的窗口时间比 XA 长
- MSG:二阶消息型事务在第一个操作完成后,在所有操作完成之前,这个时间窗口是不一致的,持续时长一般比前两者更久。
- SAGA:SAGA 的不一致窗口时长与消息接近,但是如果发生回滚,而子事务中正向操作修改的数据又会被用户看到,这部分数据就是错误数据,容易给用户带来较差的体验,因此一致性是最差的。