传统的事务
事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。在关系数据库中,一个事务由一组SQL语句组成。事务应该具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。
- 原子性:一个事务是一个不可分割的工作单位,事务中包括的操作要么全部完成,要么全部取消
- 一致性:事务使数据库从一个一致性状态转换为另一个一致性状态,事务的中间状态不可被观察到。
- 隔离性:一个事务内部的操作及使用的数据对并发中的其它事务是隔离的。
- 持久性:一个事务一旦被提交,其对数据库中数据的改变是永久性的,不因其它故障而受影响。
集中式数据库与分布式数据库
ACID
以一个学生管理系统为例,可以看到对于这个学生成绩管理系统,不同的系统使用角色,对它有不同的要求:
那么传统的事务
ACID属性,是如何帮助我们做到满足这些需求的呢?
数据量变大
而实际上,当我们考虑更大的数据量的时候,会发现,依赖于传统集中式数据库并无法很好地满足我们对系统的需求。当数据量变大,第一个要面对的问题是数据再也无法只放在一台机器上了,数据量的变大,意味着会有越来越多的客户端来查询这个数据库,那么就会出现查询的瓶颈,并且如果数据数非常重要的话,那么这些数据时丢不起的,而如果仅依赖于集中式数据库,那么当这个集中式数据库发生一些致命性的状况的时候,数据可能丢失。总而言之,对于集中式数据库来说,如果它出现了什么问题:不可查、丢数据等。那么其实意味着整个业务系统都陷入了不可用的状态。
总结下来,集中式数据库在通信、系统可靠性、可扩展性、性能瓶颈以及设计管理上,存在着明显的缺点:
- 集中数据库会有多个成绩录入员,因为集中数据库只存在于某个服务器上,而成绩录入员却是分散在全国各地的,因此会造成额外的通信开销。
- 由于集中式数据库所有的数据都存在一个点上,那么一旦这个点发生故障,就会导致整个成绩管理系统停止运作,系统的可靠性差。
- 随着数据量的变大,录入的客户端变多,查询的客户端变多,那么存储系统本身的性能可能就会成为瓶颈,包括 CPU计算能力,IO吞吐能力,存储能力,都有可能成为瓶颈。
- 可扩展性差,正由于集中式数据库存在着性能差的问题,因此只能通过升级单机硬件能力的方式,实现数据库服务能力扩展,比如原来采用 MySQL 单机数据库,遇到访问瓶颈时更换磁盘,访问量更高时就需要考虑使用 Oracle 的商用解决方案、高端的存储设备、高端小型机,也就是 IOE 架构,甚至升级 IOE 设备,以换取更高的扩展和服务能力,这个过程就会存在设备升级和数据迁移的成本,其可扩展性的代价会面临巨大的成本问题。此外,根据摩尔定律,单机硬件能力的升级,并不能换来等比的效率加速比例,也就是说,增加多一倍的CPU核数,并不能带来一倍的性能提升,这个提升并不是线性的。
- 当一个系统的功能变得越来越复杂,例如说b不仅记录学生的成绩,还记录学生的奖惩历史,出勤情况,而数据库仍然只有一个点的情况下,集中数据库上承载的业务类型越来越多,导致管理困难。
传统集中式数据库虽然能够很好地保证业务一致性,但其面临高速增长的访问量和数据量时存在性能和处理能力上的瓶颈。
数据分布式存储
分布式数据库虽然引进了复杂性例如分布式事务的问题,但是分布式数据库能解决集中式数据库的大多数痛点。
分布式数据库与集中式数据库的区别主要在数据分布和可扩展性两方面:
- 分布式数据库的数据分散存储,集中式数据库的数据集中存储。
- 分布式数据库的扩展高效并且性价比高,而集中式数据库不能无限扩容并且扩容存在着成本导致的性价比的问题。
总结起来,分布式数据库具有以下的特点:
- 数据分布性,数据可以分布在不同的机器上,不同地理位置上。
- 数据统一性,虽然数据存放在不同的机器上,不同的地理位置上,但从整体上来看,它的系统逻辑应该是一致的
- 数据的透明性,虽然数据分散了,但是无论是查询还是更新,它们都应该有统一的入口
- 数据的安全性,单个数据节点如果出现错误,它不应该影响其它节点,从而数据库整体的安全性
- 数据的可扩展性,当现有集群称为瓶颈时,分布式数据库系统可以通过扩容来解决可扩展性的问题
- 数据的自治性,虽然数据分散存储,但每一个节点它都应该要能够独立管理自己的数据,同时又不影响整体的统一性。
分布式事务
在高速增长的访问量和数据量的背景下,为了解决单机性能瓶颈以及可扩展性等问题,数据库分库分表拆分和服务化(微服务)的运用越来越广泛。完成一个业务功能,可能需要横跨多个服务或者横跨多个数据库节点;也就是说,需要操作的资源位于多个资源服务器上,从业务的角度来看,需要保证对多个资源服务器的操作,要么全部成功,要么全部失败。从本质上来说,分布式事务要保证不同资源服务器上的数据一致性。
场景
典型的分布式事务场景主要有跨库事务、分库分表以及跨服务事务。
跨库事务
分库分表
当对数据库通过中间件代理的形式进行水平拆分后,不可避免的会在一个事务中操作多个分片节点
跨服务
在服务化的架构下,完成业务功能可能涉及到对多个服务的调用,而这些服务分别会操作不同的数据库。需要保证跨服务对数据库的操作要么都成功,要么都失败,这是服务化场景下面临的分布式问题。
X/Open DTP模型与XA规范
DTP 模型
X/Open DTP(X/Open Distributed Transaction Processing Reference Model) 是X/Open 这个组织定义的一套分布式事务的标准,也就是了定义了规范和API接口,由厂商进行具体的实现。
模型元素
- 应用程序(Application Program ,简称AP):用于定义事务边界(即定义事务的开始和结束),并且在事务边界内对资源进行操作。
- 资源管理器(Resource Manager,简称RM):如数据库、文件系统等,并提供访问资源的方式。
- 事务管理器(Transaction Manager ,简称TM):负责分配事务唯一标识,监控事务的执行进度,并负责事务的提交、回滚等。
- 通信资源管理器(Communication Resource Manager,简称CRM):控制一个TM域(TM domain)内或者跨TM域的分布式应用之间的通信。
- 通信协议(Communication Protocol,简称CP):提供CRM提供的分布式应用节点之间的底层通信服务。
一个DTP模型实例,至少有3个组成部分:AP、RMs、TM。如下所示:
AP通过TM来声明一个全局事务,然后操作不同的RM上的资源,最后通知TM来提交或者回滚全局事务。AP 可以和 TM 以及 RM 通信,TM 和 RM 互相之间可以通信,DTP模型里面定义了XA接口,TM 和 RM 通过XA接口进行双向通信,例如:TM通知RM提交事务或者回滚事务,RM把提交结果通知给TM。AP和RM之间则通过RM提供的Native API 进行资源控制,这个没有进行约API和规范,各个厂商自己实现自己的资源控制,比如Oracle自己的数据库驱动程序。
XA 规范
在DTP本地模型实例中,由AP、RMs和TM组成,不需要其他元素。AP、RM和TM之间,彼此都需要进行交互,如下图所示:
上图中(1)表示AP-RM的交互接口,(2)表示AP-TM的交互接口,(3)表示RM-TM的交互接口
XA规范的最主要的作用是,就是定义了RM-TM的交互接口
XA规范中定义的RM 和 TM交互的接口如下图所示:
二阶段提交
XA规范除了定义的RM-TM交互的接口(XA Interface)之外,还对两阶段提交协议进行了优化。 两阶段协议(two-phase commit)是在OSI TP标准中提出的;在DTP参考模型中,指定了全局事务的提交要使用two-phase commit协议;而XA规范只是定义了两阶段提交协议中需要使用到的接口,也就是上述提到的RM-TM交互的接口,因为两阶段提交过程中的参与方,只有TM和RMs。
- 阶段1
TM通知各个RM准备提交它们的事务分支。如果RM判断自己进行的工作可以被提交,那就就对工作内容进行持久化,再给TM肯定答复;要是发生了其他情况,那给TM的都是否定答复。在发送了否定答复并回滚了已经的工作后,RM就可以丢弃这个事务分支信息。
- 阶段2
TM根据阶段1各个RM prepare的结果,决定是提交还是回滚事务。如果所有的RM都prepare成功,那么TM通知所有的RM进行提交;如果有RM prepare失败的话,则TM通知所有RM回滚自己的事务分支。
二阶段提交优化
XA规范对两阶段提交协议有2点优化:
- 只读断言
在阶段1中,RM可以断言“我这边不涉及数据增删改”来答复TM的prepare请求,从而让这个RM脱离当前的全局事务,从而免去了Phase 2。
这种优化发生在其他RM都完成prepare之前的话,使用了只读断言的RM早于AP其他动作(比如说这个RM返回那些只读数据给AP)前,就释放了相关数据的上下文(比如读锁之类的),这时候其他全局事务或者本地事务就有机会去改变这些数据,结果就是无法保障整个系统的可序列化特性,有脏读的风险。
- 一阶段提交
如果需要增删改的数据都在同一个RM上,TM可以使用一阶段提交跳过两阶段提交中的阶段1,直接执行阶段2。
二阶段提交缺点
- 同步阻塞
如果对操作读很敏感,我们需要将事务隔离级别设置为SERIALIZABLE。而对于分布式事务来说,更是如此,可重复读隔离级别不足以保证分布式事务一致性。
- 单点故障
一旦协调者TM发生故障。参与者RM会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。(如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题)
- 数据不一致
在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据不一致性的现象。
三阶段提交
由于二阶段提交存在着诸如同步阻塞、单点问题等缺陷,所以,研究者们在二阶段提交的基础上做了改进,提出了三阶段提交。
三阶段提交(3PC),是二阶段提交(2PC)的改进版本,与两阶段提交不同的是,三阶段提交有两个改动点。
- 引入超时机制,同时在协调者和参与者中都引入超时机制。
- 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。
- CanCommit 阶段
3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求,参与者如果可以提交就返回Yes响应,否则返回No响应。
- PreCommit 阶段
协调者根据参与者的反应情况来决定是否可以继续事务的PreCommit操作。根据响应情况,有以下两种可能。
假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。
- 发送预提交请求。协调者向参与者发送PreCommit请求,并进入Prepared阶段。
- 事务预提交。参与者接收到PreCommit请求后,会执行事务操作,并将undo和redo信息记录到事务日志中。
- 响应反馈。如果参与者成功的执行了事务操作,则返回ACK响应,同时开始等待最终指令。
假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。
- 发送中断请求。协调者向所有参与者发送abort请求。
- 中断事务。参与者收到来自协调者的abort请求之后(或超时之后,仍未收到协调者的请求),执行事务的中断。
- doCommit阶段
该阶段进行真正的事务提交,也可以分为以下两种情况。
情况1:执行提交
- 发送提交请求。协调接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。
- 事务提交。参与者接收到doCommit请求之后,执行正式的事务提交。并在完成事务提交之后释放所有事务资源。
- 响应反馈。事务提交完之后,向协调者发送Ack响应。
- 完成事务。协调者接收到所有参与者的ack响应之后,完成事务。
情况2:中断事务(协调者没有接收到参与者发送的ACK响应)
- 发送中断请求。协调者向所有参与者发送abort请求
- 事务回滚。参与者接收到abort请求之后,利用其在阶段二记录的undo信息来执行事务的回滚操作,并在完成回滚之后释放所有的事务资源。
- 反馈结果。参与者完成事务回滚之后,向协调者发送ACK消息
- 中断事务。协调者接收到参与者反馈的ACK消息之后,执行事务的中断。
在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。
相对于2PC,3PC主要解决的单点故障问题,并减少阻塞,因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
无论是二阶段提交还是三阶段提交都无法彻底解决分布式的一致性问题。
CAP理论与BASE柔性事务
CAP 理论
2000年7月,加州大学伯克利分校的 Eric Brewer 教授在分布式计算原则研究会议上提出 CAP 猜想。直到 2002 年,又麻省理工学院的 Seth Gilbert 和 Nancy Lynch 从理论上证明了 CAP 理论,从而让 CAP 理论正式成为分布式计算领域的公认定理。
CAP理论:一个分布式系统最多只能同时满足一致性(consistency)、可用性(Availability)、分区容错性(partition-tolerance)这三项中的两项。
{% asset_img cap.jpg CAP理论模型 %}
- 一致性
指更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致。
- 可用性
指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
- 分区容错性
指分布式系统在遇到某节点或网络分区故障的时候,仍然能对外提供满足一致性和可用性的服务。
BASE 理论
一个分布式系统无法同时满足一致性、可用性、分区容错性三个特点,需要对其进行取舍。对于一个分布式系统而言,分区容错性是一个最基本的要求,分布式系统中的组件必然需要被部署到不同的节点,网络问题又是一个必定会出现的异常情况,因此分区容错性也就成为了一个分布式系统必然需要面对和解决的问题。
eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结,在ACM上发表文章提出BASE理论。
BASE理论是对CAP理论的延伸,核心思想是即使无法做到强一致性(Strong Consistency,CAP的一致性就是强一致性),但应用可以采用适合的方式达到最终一致性(Eventual Consitency)。
BASE是指Basically Available(基本可用)、Soft state(柔性状态)和Eventually consistent(最终一致性)
- 基本可用
指分布式系统在出现不可预知故障的时候,允许损失部分可用性。 - 柔性状态
指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。
- 最终一致
强调的是所有的数据更新操作,在经过一段时间的同步之后,最终都能够达到一个一致的状态。
BASE理论面向的是大型高可用可扩展的分布式系统,通过牺牲一致性获得高可用性。
柔性事务解决方案一般有:最大努力通知、可靠消息最终一致性以及TCC。
参考资料
Distributed Transaction Processing: Reference Model, Version 3
Distributed Transaction Processing:The XA Specification
Atomic Distributed Transactions: a RESTful Design
田守枝的技术博客