本文核心是”事务“,由基础理论引出解决方案,也阐述了个人对分布式系统的理解。全文包含以下内容:
关注公众号:码神手记,第一时间获取最新干货
在讲事务之前先介绍下 SQL(Structured Query Language)的历史,建立宏观认识。
在 19 世纪 70 年代,为了满足数据库查询的需要,SQL 作为一门特定领域的语言横空出世。1986 年,美国国家标准化学会(ANSI)基于 IBM 的实现将 SQL 核准为国家标准。仅在几个月之后的 1987 年,国际标准化组织(ISO)将其采纳为国际标准(ISO9075-1987)。
就像 hotspot 虚拟机是参照 JVM 规范进行实现一样,MySQL、Oracle 等数据库的 SQL 也是基于 SQL 标准实现的。标准一共有九大部分,其第一部分:Framework,对事务进行了定义:事务是一个不可分割的 SQL 语句执行序列,要么都执行成功,要么对数据库没有任何影响。不同的数据库供应商为了满足自身的需要,在 SQL 标准的实现过程中会进行一些修改,但大部分都是可以通用的。
主流数据库基本都支持并发操作,事务是进行并发控制和数据恢复的基本单位,具有原子性(Atomicity,又称不可分割性)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),即 ACID 特性。如果让我们自己设计一个事务处理方案,ACID 就是用来检验方案是否正确可靠的基本原则。
数据库作为存储组件,持久性是最基本的要求。在无并发的情况下,原子性就是一致性的保证。在并发情况下,原子性和隔离性共同保证了一致性。
与多线程并发操作共享数据同理,多个事务并发操作相同的数据时可能会发生一些无法预料的事。事务隔离是数据库处理的基础能力之一,在并发事务场景下,隔离显得尤为重要。在 SQL 标准的第二部分:Foundation,定义了 4 个事务隔离级别,不同级别决定了并发事务对数据的不同影响程度。其中串行化是最高隔离级别,是最可靠的隔离级别,对性能的影响最大。隔离级别的选择就是在性能、可靠性、一致性之间进行权衡。
从低到高,事务隔离级别有四种:
事务并发执行期间,不同隔离级别下可能会发生不同的现象:
三种现象出现的可能性不同,要根据业务的实际需求进行权衡,可以在事务执行之前显式地设置隔离级别。
4 种隔离级别的划分还是太抽象,怎么理解?P1/P2/P3 三种现象出现的可能性为什么不同?
并发操作共享数据的安全问题是客观存在的,由多核 CPU 并发执行进程的基本原理所决定,这是 SQL 规范必须要考虑的问题。SQL 规范是统一的国际标准,而对标准的实现则可以百花齐放,接下来以 MySQL InnoDB 的事务模型为例进行解析,相信你会有更深的理解。
本文提到的 MySQL 特指 5.7 及以上版本。
InnoDB 遵循 ACID 模型,通过使用不同的锁策略支持 SQL 规范中的每一个事务隔离级别,所有的读操作分为无锁和有锁两种情况。
InnoDB 使用多版本并发控制(MVCC)的方式向查询提供数据库在某个时间点的快照。在同一个事务中,查询可以看到快照时间点之前提交的数据,但不会看到未提交或者快照时间点之后才提交的数据。旧版本数据是不能被加锁的,其读取结果是通过 undo 日志在内存中构建出来的,读取过程中也不会设置任何锁,其它事务的锁设置也都会被忽略,称为无锁一致性读。例如最常见的 SQL:
SELECT … FROM t1 WHERE …
复制代码
与无锁一致性读对应的就是有锁读,InnoDB 支持以下两种有锁读:
无论是共享锁还是排它锁,默认都是锁定索引区间范围,其它事务是无法在索引区间的缝隙中插入新数据的,此时不会出现脏读、幻读以及不可重复读。除非在查询时使用到了唯一索引,查询的结果只有唯一的一行,才会只锁定对应的索引记录。
REPETABLE READ 是默认的事务隔离级别,也是最常用的。你可以使用默认隔离级别保证较强的数据一致性,也可以使用 READ COMMITTED、READ UNCOMMITTED 弱化一致性。在某些场景下,强一致性、可重复读要比更小的锁开销更加重要,此时可牺牲一定并发能力,使用数据一致性较强的事务隔离级别。SERIALIZABLE 是一致性最强,并发能力最弱的隔离级别,通常很少使用。
通俗地讲,一个事务中的操作涉及多个数据库实例(跨库事务)或多个应用服务时,就叫做分布式事务。分布式事务同样要遵循 ACID 模型,但由于事务跨库,处理过程变得更复杂。系统必须能够将多个数据库实例上的操作指向同一个事务,必须考虑每个数据库实例上的操作完成情况来做出提交或回滚的决定,无论提交还是回滚,都必须在所有数据库实例上统一生效(要么都提交,要么都回滚)。
以上是对分布式事务比较通俗、便于理解的解释。当然,也有更加抽象、规范的标准。
成立于 1996 年的 The Open Group 组织,以厂商中立的角色致力于标准的制定与推广,分布式事务处理模型就是由该组织制定,即:X/Open Distributed Transaction Processing Model,简称 X/Open DTP Model 或 XA。该模型是一种可扩展的软件架构(XA,eXtended Architecture),定义了四种软件组件和六种组件间接口。它允许多个应用程序共享由多个资源管理器提供的资源,并在一个全局事务中协调这些应用程序的工作。
DTP 模型中使用两阶段提交(Two-Phase Commit)进行事务的提交/回滚:
从发起事务到完成,一个 RM 最多要经历 8 次 XA 接口调用。要想完全实现 DTP 模型是非常有难度的,在实践中将会面临众多的问题。
微服务架构的提出者 Martin Flowler 曾提出这样的忠告:微服务架构中应尽量避免分布式事务。换而言之,分布式事务的最佳解决方案是不用分布式事务。一旦使用了分布式事务,即便一些异常情况出现概率很低,我们也需要付出一些精力和代价,去避免可能会发产生的不可接受的后果。在工程实践中,局部的完美经常性无法达到,最难和最重要的通过取舍找到全局平衡,原因可以参考分布式系统的 8 大谬论以及 FLP 不可能定理。
最初由 Sum Microsystems 的创始人 Peter Deutsch 提出,1997 年被 Java 之父 James Gosling 完善。之所以称之为谬论(错误的假设),是因为历史上的无数实践已经做出证明。
以上 8 点总结起来就是两个字:网络,而分布式系统必然有网络调用,网络问题值得我们在进行系统设计时好好斟酌。
FLP 取自 Fischer、Lynch、Patterson 三位科学家名字的首字母。1985 年,三位科学家在一篇论文中(Impossibility of Distributed Consensus with One Faulty Process)提出并证明了该定理:在网络可靠,但允许节点失效(即便只有一个)的异步模型系统中,不存在可以完全解决一致性问题的共识算法。(No completely asynchronous consensus protocol can tolerate even a single unannounced process death。)
在实践中,我们如何从 8 大谬论和 FLP 不可能定理中自我拯救?既然干不掉它们,那就想办法共存。
小概率的异常事件并不会使分布式失去应用价值。分布式系统在工程实践中可以做到利大于弊,可以通过付出一些尚可接受的代价去获得更大的收益。
对于单体架构的应用,事务的场景通常是一个应用程序访问一个数据库,且不存在远程调用,无需实现完整的 DTP 模型即可满足需求(仅仅实现一个 AP、一个 RM,用不上 CRM 和 OSI TP)。随着业务体量和系统复杂度的提高,单体架构开始向分布式架构演进。以业务领域为划分标准,单体应用拆分成了多个应用,大而全的单数据库实例拆分成了多数据库实,形成了一个或多个 AP 访问多个 RM 的分布式事务场景。此时我们需要在 CRM 组件以及 OSI TP 组件的支持下,使 TM 能够跨多个 AP、RM 协调全局事务的完成。至此,一个完整的基于 DTP 模型的分布式系统出现了。在这样的系统中,我们必须做出以下假设:存储设备、中间件、网络通信、应用程序,任何一个节点都有可能出现故障。如果节点能够内部纠正错误,则不会对全局事务产生负面影响,反之则可能出现一些业务无法接受的数据不一致。
Spring 是 Java 世界中流行的应用服务开发框架,对事务管理进行了统一抽象,提供了一致性的编程模型,可以支持不同的事务 API,包括:JTA、JDBC、JMS、Hibernate、JPA、MyBatis 等。使用 Spring 提供的编程模型可以有效屏蔽使用不同事务 API 时的差异,通过事务注解可实现无代码侵入的事务管理,帮助开发人员把精力集中在业务逻辑上。Atomikos 是一个第三方的 JTA 实现,支持 XA,可以集成到 Spring 工程中使用。Atomikos 有免费和收费两个版本,只有收费版可以支持跨 AP 通信的分布式事务(Atomikos 官方称之为 Microservice Transactions)。如果你的分布式事务场景不涉及跨 AP 通信,Atomikos 会是一个合适的选择,比如:一个应用程序中连接多个数据源。
由于老板很努力,公司的业务蒸蒸日上,团队规模变得更大,对系统的稳定性、吞吐量、性能有了更高的要求,出现了多个 AP 访问多个 RM 的分布式事务场景。此时需要更好的解决方案(不想用收费版 Atomikos,也不想依赖 Atomikos)。于是行业内开始涌现出一些分布式事务解决方案(京东的 JDTX、阿里的 Seata),它们参考了 DTP 模型,但又不完全遵循标准的 DTP,主要原因在于 DTP 有以下几种限制:
DTP 模型完全满足 ACID(前提是使用 SERIALIZABLE 隔离级别),是分布式事务的可选方案之一。但在当代,大多数互联网公司更愿意在一致性上做出一定程度的妥协,从而换取高并发,这一点可以参考 CAP 理论与 BASE 理论。
2000 年,加州大学柏克莱分校的计算机科学家埃里克·布鲁尔在分布式计算原理研讨会(PODC)上提出猜想。2000 年,麻省理工学院(MIT)的赛斯·吉尔伯特和南希·林奇发表了布鲁尔猜想的证明,使之成为一个定理。
在理论计算机科学中,CAP 定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:
想象有两个节点,允许至少一个节点更新状态则会导致数据不一致,即丧失了 C 性质。如果为了保证数据一致性,将其中一个节点设置为不可用,那么又丧失了 A 性质。除非两个节点可以互相通信,才能既保证 C 又保证 A,这又会导致丧失 P 性质。
2008 年 5 月 1 号,eBay 的架构师 Dan Pritchett 在 ACM 上发表了一篇文章,名为《BASE: An Acid Alternative: In partitioned databases, trading some consistency for availability can lead to dramatic improvements in scalability.》
BASE(basically available,soft state,eventually consistent)是 ACID 的替代方案,ACID 追求强一致性,而 BASE 允许数据库的一致性处在不断变化中。BASE 通过牺牲一些一致性换取可用性,这样可以显著提升系统的伸缩性。BASE 的核心是以下三点:
Dan Pritchett 在文章中给出了一个通用的不依赖 2PC 的分布式事务解决方案:消息队列解耦法。
消息队列解耦法注意事项:
BASE 是 CAP 在长期实践中得出的普适性方案,大多数场景都能适用,是柔性事务解决方案的理论基础。分布式事务解决方案分为四种模式:AT、TCC、Saga、XA,XA 模式在上文中已经讲过,很少被使用。在后续文章中,会对 AT、TCC、Saga 模式进行详细梳理。
领取专属 10元无门槛券
私享最新 技术干货