聊聊事务与分布式系统

Posted 码神手记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊事务与分布式系统相关的知识,希望对你有一定的参考价值。

码神手记-资深攻城狮的私房笔记,微信公众平台/知乎/头条同步。

动动小手,点个关注!

事务与分布式系统


本文核心是”事务“,由基础理论引出解决方案,也阐述了个人对分布式系统的理解。全文包含以下内容:

  1. 基础规范:统一基础认知,了解SQL规范、ACID模型。

  2. 优秀实现:理论联系实际,剖析mysql InnoDB的事务模型。

  3. 扩展进阶:X/Open DTP模型、2PC协议。

  4. 工程实践:柔性事务解决方案基础。 



01


基础规范


SQL规范的历史背景


在讲事务之前先介绍下SQL(Structured Query Language)的历史,建立宏观认识。


在19世纪70年代,为了满足数据库查询的需要,SQL作为一门特定领域的语言横空出世。1986年,美国国家标准化学会(ANSI)基于IBM的实现将SQL核准为国家标准。仅在几个月之后的1987年,国际标准化组织(ISO)将其采纳为国际标准(ISO9075-1987)。


就像hotspot虚拟机是参照JVM规范进行实现一样,MySQL、Oracle等数据库的SQL也是基于SQL标准实现的。标准一共有九大部分,其第一部分:Framework,对事务进行了定义:事务是一个不可分割的SQL语句执行序列,要么都执行成功,要么对数据库没有任何影响。不同的数据库供应商为了满足自身的需要,在SQL标准的实现过程中会进行一些修改,但大部分都是可以通用的。


ACID模型


主流数据库基本都支持并发操作,事务是进行并发控制和数据恢复的基本单位,具有原子性(Atomicity,又称不可分割性)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),即ACID特性。如果让我们自己设计一个事务处理方案,ACID就是用来检验方案是否正确可靠的基本原则。


  1. 原子性:一个SQL执行序列,要么全部成功,要么对数据库无任何影响。执行过程中发生错误时,要恢复(rollback,又称回滚)到执行之前的快照(也称保存点,savepoint)。

  2. 一致性:事务执行前后的数据完整性、准确性没有遭到破坏,这与原子性密切相关。比如A账户给B账户转账后,两个账户的总余额与转账前是一致的。

  3. 隔离性:数据库可以进行并发的事务操作,但各事务之间不能互相干扰,要防止交叉执行导致的数据不一致。

  4. 持久性:事务一旦提交,对数据库库的改变是永久的,即使系统故障也不会丢失。


数据库作为存储组件,持久性是最基本的要求。在无并发的情况下,原子性就是一致性的保证。在并发情况下,原子性和隔离性共同保证了一致性。


事务隔离级别


与多线程并发操作共享数据同理,多个事务并发操作相同的数据时可能会发生一些无法预料的事。事务隔离是数据库处理的基础能力之一,在并发事务场景下,隔离显得尤为重要。在SQL标准的第二部分:Foundation,定义了4个事务隔离级别,不同级别决定了并发事务对数据的不同影响程度。其中串行化是最高隔离级别,是最可靠的隔离级别,对性能的影响最大。隔离级别的选择就是在性能、可靠性、一致性之间进行权衡。


从低到高,事务隔离级别有四种:

  1. 读未提交(READ UNCOMMITTED)。

  2. 读已提交(READ COMMITTED)。

  3. 可重复读(REPETABLE READ)。

  4. 串行化(SERIALIZABLE)。


事务并发执行期间,不同隔离级别下可能会发生不同的现象:

  1. P1(Dirty Read,脏读):T1修改了一行数据,T2在T1提交之前读到了这行数据。如果T1进行了回滚,那么T2拿到的数据就成了数据库中不存在的数据。

  2. P2(Non-Repeatable Read,非可重复读):T1读取了一行数据,然后T2修改或删除了这行数据并执行了提交。如果T1要重读这行数据,则会发现数据已被修改或删除。

  3. P3(Phantom,幻读):T1读取了N行符合某些搜索条件的数据集合,然后T2又生成了若干条符合T1搜索条件的数据,如果T1用同样的条件重新读取数据,将会获取不一样的数据。


三种现象出现的可能性不同,要根据业务的实际需求进行权衡,可以在事务执行之前显式地设置隔离级别。

聊聊事务与分布式系统

4种隔离级别的划分还是太抽象,怎么理解?P1/P2/P3三种现象出现的可能性为什么不同?


并发操作共享数据的安全问题是客观存在的,由多核CPU并发执行进程的基本原理所决定,这是SQL规范必须要考虑的问题。SQL规范是统一的国际标准,而对标准的实现则可以百花齐放,接下来以MySQL InnoDB的事务模型为例进行解析,相信你会有更深的理解。


02


优秀实现:InnoDB存储引擎


本文提到的MySQL特指5.7及以上版本。


InnoDB遵循ACID模型,通过使用不同的锁策略支持SQL规范中的每一个事务隔离级别,所有的读操作分为无锁和有锁两种情况


无锁一致性读(Consistent Nonlocking Reads)

InnoDB使用多版本并发控制(MVCC)的方式向查询提供数据库在某个时间点的快照。在同一个事务中,查询可以看到快照时间点之前提交的数据,但不会看到未提交或者快照时间点之后才提交的数据。旧版本数据是不能被加锁的,其读取结果是通过undo日志在内存中构建出来的,读取过程中也不会设置任何锁,其它事务的锁设置也都会被忽略,称为无锁一致性读。例如最常见的SQL:

SELECTFROM t1 WHERE


有锁读(Locking Reads)

与无锁一致性读对应的就是有锁读,InnoDB支持以下两种有锁读:

  1. SELECT … LOCK IN SHARE MODE:搜索中遇到的所有索引记录都会被设置共享锁,其它会话可以读取但不能修改,直到当前事务提交。如果发起查询时,数据已被其它事务修改但尚未提交,查询将会被阻塞并等待修改数据的事务结束后(提交/回滚)获取最新的值。

  2. SELECT … FOR UPDATE:搜索中遇到的所有索引记录都会被设置排它锁,其它事务的UPDATE、SELECT … LOCK IN SHARE MODE都会被阻塞,在特定事务隔离级别下(指SERIALIZABLE),SELECT也会被阻塞

无论是共享锁还是排它锁,默认都是锁定索引区间范围,其它事务是无法在索引区间的缝隙中插入新数据的,此时不会出现脏读、幻读以及不可重复读。除非在查询时使用到了唯一索引,查询的结果只有唯一的一行,才会只锁定对应的索引记录。


不同事务隔离级别的实现策略

  1. REPETABLE READ:InnoDB默认的隔离级别,同一事务中每次无锁读都读取首次读到的快照,保证了可重复读、不会脏读,但可能幻读。有锁读遵循上文中的默认规则。

  2. READ COMMITTED:同一事务中每次无锁读都会设置并去读它自己的最新快照(仅包括已提交的数据版本),保证了不会脏读,但可能幻读以及不可重复读。有锁读时,InnoDB只会锁定已存在的索引记录而不包括有序索引区间里的缝隙,这一点和默认有锁读规则不同,可能会发生幻读。

  3. READ UNCOMMITTED:同一事务中每次无锁读都会设置并去读它自己的最新快照,注意与READ COMMITTED不同的是快照会包括未提交的数据版本,不可重复读、脏读、幻读都有可能发生。有锁读时,和READ COMMITTED一样,可能会幻读。

  4. SERIALIZABLE:有锁读时遵循上文中的默认规则。无锁读时,分为两种情况:关闭或启用autocommit。当关闭会话的autocommit时,InnoDB会将所有SELECT查询隐式转换为SELECT … LOCK IN SHARE MODE,即加上了共享锁,变成有锁读,不会出现脏读、幻读以及不可重复读。当开启会话的autocommit时,每个无锁SELECT本身就是一个独立的事务,而且是只读的,事务的SQL序列中只有一条,也就不存在脏读、幻读、不可重复读的问题了。


如何选择合适的隔离级别?

REPETABLE READ是默认的事务隔离级别,也是最常用的。你可以使用默认隔离级别保证较强的数据一致性,也可以使用READ COMMITTED、READ UNCOMMITTED弱化一致性。在某些场景下,强一致性、可重复读要比更小的锁开销更加重要,此时可牺牲一定并发能力,使用数据一致性较强的事务隔离级别。SERIALIZABLE是一致性最强,并发能力最弱的隔离级别,通常很少使用。 


03


扩展进阶


分布式事务


通俗地讲,一个事务中的操作涉及多个数据库实例(跨库事务)或多个应用服务时,就叫做分布式事务。分布式事务同样要遵循ACID模型,但由于事务跨库,处理过程变得更复杂。系统必须能够将多个数据库实例上的操作指向同一个事务,必须考虑每个数据库实例上的操作完成情况来做出提交或回滚的决定,无论提交还是回滚,都必须在所有数据库实例上统一生效(要么都提交,要么都回滚)。


以上是对分布式事务比较通俗、便于理解的解释。当然,也有更加抽象、规范的标准。


X/Open DTP Model(X/Open分布式事务处理模型)


成立于1996年的The Open Group组织,以厂商中立的角色致力于标准的制定与推广,分布式事务处理模型就是由该组织制定,即:X/Open Distributed Transaction Processing Model,简称X/Open DTP Model或XA。该模型是一种可扩展的软件架构(XA,eXtended Architecture),定义了四种软件组件和六种组件间接口。它允许多个应用程序共享由多个资源管理器提供的资源,并在一个全局事务中协调这些应用程序的工作。 


聊聊事务与分布式系统


四种软件组件

  1. Application Program(AP,应用程序):实现终端用户所需要的功能。每个应用程序都包含一个操作序列,这些操作会引用一些资源(比如:数据库中的数据)。应用程序定义了全局事务的开始与结束,在事务边界内访问资源,且通常决定着每个事务分支的提交与回滚。

  2. Resource Managers (RMs,资源管理器):管理已定义的部分共享资源,对这些资源的访问需要使用RM提供的服务,比如数据库管理系统就是最常见的资源管理器。在X/Open DTP模型中,资源管理器对所管理资源的改变都是可恢复的。一个全局事务会关联多个RM,每个RM上都会创建一个全局事务的分支(Branch)。

  3. Transaction Manager(TM,事务管理器):管理全局事务,分配标识符给事务,监控事务进程,协调所有资源管理器完成事务以及失败恢复。应用程序通过调用事务管理器定义全局事务的开始和结束。

  4. Communication Resource Managers(CRMs,通信资源管理器):控制一个事务或跨多个事务的应用程序之间的通信。CRM允许DTP模型中的一个组件实例访问当前事务域内部或者外部的其它组件实例,上级事务可以通过CRM将信息传递给下级事务。在X/Open DTP 模型中,CRMs使用OSI TP服务提供跨事务管理器域的通信层。在一个事务管理器域中可以使用不同的CRM支持不同的通信模式。每种通信模式都有自己的优势,X/Open提供了以下几种比较流行的通信模式:TxRPC、XATMI、Peer-to-Peer(P2P对等网络)。


六种组件间接口

  1. AP-RM接口:该接口用于应用程序(AP)对资源管理器(RM)的访问,比如使用SQL访问数据库系统,应用程序是具备可移植性的。

  2. AP-TM(TX)接口:提供给应用程序使用,应用程序(AP)调用该接口来协调由事务管理器(TM)管理的全局事务。

  3. TM-RM(XA)接口:事务管理器(TM)与资源管理器(RM)之间的双向接口,事务管理器通过该接口将各个资源管理器的工作构造为一个全局事务,并协调事务的提交与恢复。

  4. TM-CRM(XA+)接口:事务管理器(TM)与通信资源管理器(CRM)之间的双向接口。通过CRM对OSI TP的调用,支持跨多个事务管理器域的事务传播。

  5. AP-CRM接口:X/Open为全局事务内应用程序间的通信提供了可移植的API,CRMs中对应着API的不同实现。

  6. CRM-OSI(XAP-TP)接口:该接口为CRM和OSI TP(Open System Interconnection Transaction Process,开放式系统互联 事务处理)服务之间的通信提供了编程接口,位于七层OSI模型中的表示层。多个事务管理器域之间的通信必须要用到OSI TP服务。 


两阶段提交协议


DTP模型中使用两阶段提交(Two-Phase Commit)进行事务的提交/回滚:

  1. 准备阶段:TM向所有与当前事务相关的RM发起准备请求,每个RM评估自身是否能正常进行提交,并在响应中告知TM。这类似于一个TM发起投票,各个RM进行投票的过程。

  2. 提交阶段:在第一阶段中,如果所有RM能在一定时间内表示OK,则第二阶段由TM向所有RM发起提交请求。有任何一个RM没有准备好,TM则向所有RM发起回滚请求。

从发起事务到完成,一个RM最多要经历8次XA接口调用。要想完全实现DTP模型是非常有难度的,在实践中将会面临众多的问题。 


04


工程实践


微服务架构的提出者Martin Flowler曾提出这样的忠告:微服务架构中应尽量避免分布式事务。换而言之,分布式事务的最佳解决方案是不用分布式事务。一旦使用了分布式事务,即便一些异常情况出现概率很低,我们也需要付出一些精力和代价,去避免可能会发产生的不可接受的后果。在工程实践中,局部的完美经常性无法达到,最难和最重要的通过取舍找到全局平衡,原因可以参考分布式系统的8大谬论以及FLP不可能定理


分布式系统8大谬论


最初由Sum Microsystems的创始人Peter Deutsch提出,1997年被Java之父James Gosling完善。之所以称之为谬论(错误的假设),是因为历史上的无数实践已经做出证明。

  1. 网络是可靠的。

  2. 延迟为0。

  3. 带宽是无限的。

  4.  网络是安全的。

  5. 网络拓扑结构不会改变。

  6. 有一个网络管理员。

  7. 数据传输成本为0。

  8. 网络是同质的。

以上8点总结起来就是两个字:网络,而分布式系统必然有网络调用,网络问题值得我们在进行系统设计时好好斟酌。


FLP不可能定理


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在单体架构下的应用


小概率的异常事件并不会使分布式失去应用价值。分布式系统在工程实践中可以做到利大于弊,可以通过付出一些尚可接受的代价去获得更大的收益。


对于单体架构的应用,事务的场景通常是一个应用程序访问一个数据库,且不存在远程调用,无需实现完整的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会是一个合适的选择,比如:一个应用程序中连接多个数据源。


DTP在分布式架构下的限制


由于老板很努力,公司的业务蒸蒸日上,团队规模变得更大,对系统的稳定性、吞吐量、性能有了更高的要求,出现了多个AP访问多个RM的分布式事务场景。此时需要更好的解决方案(不想用收费版Atomikos,也不想依赖Atomikos)。于是行业内开始涌现出一些分布式事务解决方案(京东的JDTX、阿里的Seata),它们参考了DTP模型,但又不完全遵循标准的DTP,主要原因在于DTP有以下几种限制

  1. 性能:标准的DTP是完全同步阻塞性的协议,锁时间长,响应时间也相对较长。

  2. 资源限制:并不是所有的资源都能做RM、支持XA接口,MySQL也是5.7开始才较好地支持了XA接口。

  3. Server限制:Weblogic这样的重量级Server可以支持跨多AP的分布式事务,但微服务以及容器化趋势下基本不会有人选择Weblogic,恨不得裸机部署才觉得香。


DTP模型完全满足ACID(前提是使用SERIALIZABLE隔离级别),是分布式事务的可选方案之一。但在当代,大多数互联网公司更愿意在一致性上做出一定程度的妥协,从而换取高并发,这一点可以参考CAP理论与BASE理论。


CAP定理


2000年,加州大学柏克莱分校的计算机科学家埃里克·布鲁尔在分布式计算原理研讨会(PODC)上提出猜想。2000年,麻省理工学院(MIT)的赛斯·吉尔伯特和南希·林奇发表了布鲁尔猜想的证明,使之成为一个定理。


在理论计算机科学中,CAP定理(CAP theorem),又被称作布鲁尔定理(Brewer's theorem),它指出对于一个分布式计算系统来说,不可能同时满足以下三点:

  1. 一致性(Consistency):所有节点访问同一份最新的数据副本。

  2. 可用性(Availability):每次请求都能获取到非错的响应,但是不保证获取的数据为最新数据。

  3. 分区容错性(Partition tolerance):以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,必须就当前操作在C和A之间做出选择。


想象有两个节点,允许至少一个节点更新状态则会导致数据不一致,即丧失了C性质。如果为了保证数据一致性,将其中一个节点设置为不可用,那么又丧失了A性质。除非两个节点可以互相通信,才能既保证C又保证A,这又会导致丧失P性质。


BASE理论与柔性事务解决方案基础


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的核心是以下三点:

  1. 基本可用(basically available):在整个系统可用的前提下,允许部分分区的失败。

  2. 软状态(soft state):网络必然会有延迟,在通信完成之前,允许中间状态的存在。如果延迟足够短,对用户来说是透明或者可容忍的。

  3. 最终一致性(eventually consistent):处于中间状态时会有短暂的数据不一致,只要最终数据一致即可。


Dan Pritchett在文章中给出了一个通用的不依赖2PC的分布式事务解决方案:消息队列解耦法

  1. 将全局事务中的分支解耦。从设计上解开依赖,和尽可能不用分布式事务的思想不谋而合。

  2. 在数据库中记录全局事务信息。

  3. 将事务分支信息写入消息队列。

  4. 从消息队列中取出事务分支信息,执行并持久化。


消息队列解耦法注意事项

  1. 消息持久化:消息队列中间件需要有持久化能力,可以在故障后自动恢复,避免丢失消息。

  2. 幂等性要求:意外的消息重复会导致事务分支重复执行,必须保证重复执行的结果是一致的。

  3. 消息顺序问题:不怕一万就怕万一,假设上游生产者意外不能按顺序写入消息,消息中间件意外不能按顺序接收消息。那么极短时间窗口内的先后两条消息,有可能并不是按照先后顺序进入消息队列。当业务与顺序性紧密相关时,要注意处理这种情况,常见的方法是借助单调递增的事务ID、毫/微秒级时间戳判断。


BASE是CAP在长期实践中得出的普适性方案,大多数场景都能适用,是柔性事务解决方案的理论基础。分布式事务解决方案分为四种模式:AT、TCC、Saga、XA,XA模式在上文中已经讲过,很少被使用。在后续文章中,会对AT、TCC、Saga模式进行详细梳理。


参考资料:

  1. ANSI:https://blog.ansi.org/2018/10/sql-standard-iso-iec-9075-2016-ansi-x3-135/

  2. MySQL:https://dev.mysql.com/doc/refman/5.7/en/innodb-locking-transaction-model.html

  3. 国际标准:ISO/IEC 9075-1:2003 Infomation Technology - Database Languages -SQL -Part1: Framework (SQL/Framework)

  4. 国际标准:ISO/IEC 9075-2:2003 Infomation Technology - Database Languages -SQL -Part2: Foundation (SQL/Foundation)

  5. 分布式事务处理-XA+规范:Distributed Transaction Processing:The XA+ Specification Version 2

  6. 维基百科:https://zh.wikipedia.org

  7. ACM:https://dl.acm.org/doi/10.1145/1394127.1394128




扫描二维码关注


码神手记

关注码神手记,干货持续输出中~

点击阅读原文 关注码神手记 SUBSCRIBE to ME

以上是关于聊聊事务与分布式系统的主要内容,如果未能解决你的问题,请参考以下文章

聊聊分布式事务一致性与本地消息表

阿里P9:做了 6 年架构设计,这次聊聊微服务与分布式事务细节

系统架构大白话聊聊分布式事务

聊聊分布式事务&分布式系统事务一致性解决方案

聊聊分布式事务

聊聊分布式事务