vlambda博客
学习文章列表

深入剖析分布式事务一致性

管她前浪,还是后浪?

能浪的浪,才是好浪!

每天 10:33 更新文章,每天掉亿点点头发...

源码精品专栏

 





分布式事务是用来解决跨数据库、跨服务更新数据一致性问题的。那么这里的一致性指的是什么,什么是强一致性,什么是弱一致性,与CAP理论中的一致性概念是一样的吗?本文将为您深入解答相关的问题。

基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能。

在数据库的理论中,事务具备大家都熟悉的ACID特性,分别如下:

  • Atomicity(原子性):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复到事务开始前的状态,就像这个事务从来没有执行过一样。
  • Consistency(一致性):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。完整性包括外键约束、应用定义的等约束不会被破坏。
  • Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
  • Durability(持久性):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

对于这里面的C(一致性),我们以一个非常具体的业务例子,来进行解释。假如我们正在处理一个转账业务,假设是A转给B 30元,在本地事务的支持下,我们的用户看到A+B的总金额,在整个转账前后,以及转账过程中,都是保持不变的。那么这个时候用户认为他看到的数据是一致的,符合业务约束的。

当我们业务变复杂,引入多个数据库和大量微服务时,上述本地事务的一致性,依旧是业务非常关心的。假如一个业务更新操作,跨库或者跨服务时,那么此时业务关心的一致性问题,就变成了分布式事务中的一致性问题。

在单机本地事务中,A+B的总金额在任何时刻去查(以常见的ReadCommitted或ReadRepeatable隔离级别),都是不变的,也就是业务约束一直都保持的这种一致性,我们称之为强一致性。

目前在跨库、跨服务的分布式实际应用中,尚未看到有强一致性的方案。

我们来看看一致性级别最高的XA事务,是否是强一致的,我们以跨行转账(在这里,我们以跨库更新AB来模拟)作为例子来说明,下面是一个XA事务的时序图:

在这个时序图中,我们在如图所示的时间点发起查询,那么我们查到的数据,将是A+B+30,不等于A+B,不符合强一致的要求。

我们接下来思考,普通XA事务不是强一致的,但假如完全不考虑性能因素,有没有可能在理论上做到强一致:

我们先看看如果我们把XA事务涉及的数据库,隔离级别设定到Serializable,是否能到到强一致的效果呢?我们来看看前面的时序场景:

这种情况下,查到结果等于A+B,但是又有另一些场景出现了问题,如下图所示:

按照图中时序查询的结果是:A+B-30,依旧是不一致。

深入思考这个强一致的问题之后,有一种做法可以做到强一致,做法如下:

  • 对于查询,也采用XA事务,并且查询数据时,采用select for update的方式,所有数据查完之后,再xa commit
  • 为了避免死锁,需要将涉及到的数据库排序,访问数据都必须要按照相同的数据库顺序来写入和查询

在上述策略下,我们可以看到,在时序图任何一个时间点进行查询,获得的结果都是A+B

  • 在T0时间查询,那么修改一定发生在查询全部完成之后,所以查询得到结果A+B
  • 在T1,T2,T3查询,那么查询结果返回一定全部发生在修改完成之后,所以查询得到结果也是A+B

很明显这种理论上的强一致,效率极低,所有有数据交集的数据库事务都是串行执行,而且还需要按照特定的顺序查询/修改数据,因此成本极高,几乎无法应用在生产中。

我们讨论了跨库、跨微服务的分布式事务是无法做到强一致的,其实还有一种分布式数据内部的事务,因为事务跨节点了,也被成为分布式事务。这种分布式事务是可以做到强一致的,这种强一致是通过MVCC的技术达到的,原理和单机的数据库类似,但复杂很多。详细的实现方法可以参考谷歌的percolator

未来有没有可能借鉴NewSQL的这种方式,来实现跨库、跨微服务这类分布式事务的强一致性?理论上是可以的。

  • 实现跨服务但不跨库的分布式事务一致性,会相对简单一些,其中一种方式就是实现XA事务中的TMRESUME选项(因为最终只有一个xa commit,不会出现两个xa commit中间的不一致时间窗口)。
  • 实现跨数据库的分布式事务一致性,会困难很多,因为各个数据库的内部版本机制都不一样,想要协同非常困难。

基于微服务的思想,构建在 B2C 电商场景下的项目实战。核心技术栈,是 Spring Boot + Dubbo 。未来,会重构成 Spring Cloud Alibaba 。

既然现有的各种分布式事务方案都无法做到强一致,那么弱一致性之间是否有差别呢?我们进行了以下关于一致性强弱的分类:

一致性由强到弱分别是:

XA事务>消息>TCC>SAGA

这里的消息指的是本地消息表这种类型的分布式事务

他们的分类为:

  • 无中间态:数据只有两个状态,事务前和事务后,没有其他第三种状态。XA、消息这两种都是这种
  • 有中间态:数据有中间态,例如TCC的Try,数据状态和事务前事务后都不一样;SAGA也有中间态,假如一个SAGA事务执行正向操作后数据为W,又回滚了,那么W也与事务前事务后的状态不同。
  • XA:XA虽然不是强一致,但是XA的一致性是多种分布式事务中,一致性最好的,因为他处于不一致的状态时间很短,只有一部分分支开始commit,但还没有全部commit的这个时间窗口,数据是不一致的。因为数据库的commit操作耗时,通常是10ms内,因此不一致的窗口期很短。
  • 消息:消息型在第一个操作完成后,在所有操作完成之前,这个时间窗口是不一致的,持续时长一般比XA更久。
  • TCC:TCC的中间态,通常可控,可以自定义。通常情况下,这部分数据不展示给用户,因此一致性比后面的SAGA要好。
  • SAGA:SAGA如果发生回滚,而子事务中正向操作修改的数据会被用户看到,可能给用户带来较差的体验,因此一致性是最差的。

我们这里的分类仅仅从我们关心的几个维度进行了归纳,适用于多数场景,但并不一定适用所有情况。在实际的应用中,也可能出现TCC的一致性比消息更好,例如我在Try中执行xa prepare,Confirm中执行xa commit,Cancel中执行xa rollback,在这种实现下,TCC的一致性就跟XA一样,一致性其实高于消息。

我们这里讨论的一致性是指数据库中的一致性概念,与CAP中的一致性不同。

  • CAP中的强一致性是指用户在分布式系统中写完之后,立刻去读,如果能够像本地读写那样,读到最新版本,那么是强一致性。
  • 分布式事务中的强一致性,是指事务进行的过程中,用户读取的数据始终满足业务约束,目前在实际应用中的方案,都无法做到强一致。

上述两者的强一致性在具体的含义上是不同的,但从用户的视角看,也有共通性,即能否像单机系统一样,不需要关心分布式带来的新问题。

读者通常会有另一个疑问,那就是分布式事务是一个分布式系统,那么在CAP中的一致性如何?

当前Paxos/Raft等分布式共识协议已经在工业领域有了成熟的实现,当遇见机器故障或网络隔离的情况时,可以做到大约几百个毫秒到几秒内选举出新的leader,从故障中恢复。也就是说CAP中,选择CP,在A上面只有大约几百个毫秒的不可用时间。

因此对于NewSQL或者分布式事务这类数据敏感性应用,一般都选择CAP中的CP,而牺牲几百毫秒的A。因此在这方面,分布式事务是CAP中强一致的。例如我们的dtm分布式事务框架,将全局事务进度保存在CP的数据库中(云厂商大多提供了CP的数据库)

本文详尽的分析了分布式事务中一致性相关的问题,在确认没有强一致性方案的情况下,分析了弱一致性分类及理论上可能的强一致方案。



已在知识星球更新源码解析如下:

最近更新《芋道 SpringBoot 2.X 入门》系列,已经 101 余篇,覆盖了 MyBatis、Redis、MongoDB、ES、分库分表、读写分离、SpringMVC、Webflux、权限、WebSocket、Dubbo、RabbitMQ、RocketMQ、Kafka、性能测试等等内容。

提供近 3W 行代码的 SpringBoot 示例,以及超 4W 行代码的电商微服务项目。

文章有帮助的话,在看,转发吧。

谢谢支持哟 (*^__^*)