⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 segmentfault.com/a/1190000041106277 「叶东富」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

概述

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

一致性指什么

在数据库的理论中,事务具备大家都熟悉的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

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

NewSQL的强一致性

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

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

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

弱一致性的分类

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

一致性由强到弱分别是:

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

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

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

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

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

总结

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

文章目录
  1. 1. 概述
  2. 2. 一致性指什么
    1. 2.1. 无法做到强一致
    2. 2.2. 理论上的强一致性
    3. 2.3. NewSQL的强一致性
  3. 3. 弱一致性的分类
  4. 4. CAP理论中的一致性
  5. 5. 总结