亿级流量架构之分布式事务思路及方法

分布式事务以及分布式锁是分布式中难点,分布式事务一篇文章可能写不完,个人习惯时从基本概念出发,一步一步开始介绍,前面会先梳理事务中一些基本概念,对基本概念十分清楚的话能够直接看"一致性讨论"以及后面的部分。予己方便总结回顾、与他交流分享。html

什么是分布式事务

在平常生活中,不少事要么所有作,要么所有不作,不能只作一部分,否则就会产生其余复杂的问题,不少人喜欢举转帐的例子,对于同一个帐号,A在湖北往出转500,B在广东取钱500,那么A转出去以后要将A帐号的钱数目扣除,B帐号数目增长: 事务 = (A帐号扣除500,B帐号增长500)数据库

看到没,像这样多个步骤放在一块儿,就是事务,要么都执行,要么都不执行,若是咱们的数据存储在多个数据库中,也就是存在跨库调用,因为网络具备不安全性以及延时性,如何保证事务分布式执行呢?若是执行到一半断电又该如何处理?在讲解分布式事务以前先简单回顾事务的一些特色,俗称ACID,下面逐一讲解:安全

原子性(Atomic)

在化学中,分子构成的物质,分子是保持化学特性的最小单位,如\(H_2O,CO_2\)等,由原子构成的物质,原子保持物质特性,像\(Fe\)啥的,意思就是不可分割,再分红质子中子啥的就不是咱们认为的物质了,这儿的原子性也是这个道理,就是事务不能够再拆分,例如上面的事务,看着能够是由两个过程组成的事务,可是你拆开就不是咱们认为该有的过程,因此,事务不可再分,具备原子性。服务器

一致性(Consistency)

一致性也很好理解,对于上面的两个帐户,若是银行想知道本身这儿被存了多少钱,那么这个事务执行前,A帐号有500块,B帐号没有钱,银行帐户总共500块,事务执行后A帐号没有钱,B帐号有500块,也就是这个500块是必定的,不可能出现A帐号有500块,B帐号也有500块, 那就数据不一致了,这样的话,说明事务中某些步骤执行出现了问题,产生中间数据,那么就不一致。网络

在分布式中,对于一个结果,多处同时查询,得出的结果应该是一致的。架构

隔离性(Isolation)

一个事务在未完成时,另外一个事务不会影响到它,也就是若是B还给C转帐1000,记为事务2:并发

事务1 = (A帐号扣除500,B帐号增长500)分布式

事务2 = (B帐号扣除1000,C帐号增长1000)微服务

这两个事务之间不会产生影响,也就是不会发生A转出的500块到达C帐号这种状况。高并发

持久性(Durability)

持久化,通常是意味着将数据写入磁盘,不会轻易改变的意思,这儿是事务提交以后,会影响到数据库,不会丢失。这也就意味着,随着系统愈来愈庞大,咱们为了提升可用性、维护性、吞吐量等等技术指标,就算改善原有架构,业务计算的问题解决后,数据库仍是会成为整个系统中的瓶颈。

一致性的讨论

ACID本质而言都是为了保护数据的一致性,而数据数据持久化时会触发数据库操做,形成效率低小,因此围绕一致性(效率)产生了一些讨论,分别是强一致性、弱一致性、最终一致性。

强一致性

任何一次读都能读到某个数据的最近一次写的数据。系统中的全部进程,看到的操做顺序,都和全局时钟下的顺序一致。简言之,在任意时刻,全部节点中的数据是同样的,这就要求数据一有改变就写到数据库。

弱一致性

数据更新后,不要求及时写会数据库以及同步到全部节点,也就是这时候数据与真实数据可能有一些出入,对于架构而言,若是能容忍后续的访问只能访问到部分或者所有访问不到,则是弱一致性。

最终一致性

不保证在任意时刻任意节点上的同一份数据都是相同的,也就是有些节点数据多是准确的,有的多是不许确的, 可是随着时间的迁移,不一样节点上的同一份数据老是在向趋同的方向变化。简单说,就是在一段时间后,节点间的数据会最终达到一致状态。

三种一致性中,强一致性数据更加可靠,可是因为时时刻刻要求全部数据库保持数据一致,因此效率低下,数据没有统一完,请求就无法获得响应,高并发场景下,体验不太好,因此在实际使用中,根据不一样的业务选择是一致性也不一样,购物时帐号付钱确定是强一致性,可是商品库存数据就不必定非要强一致性,至于商品下面的评论啥的,甚至能够选择弱一致性。

分库分表

前面讲过集群的AKF拆分原则(Redis集群拆分原则之AKF),大概意思是硬件性能是由上限的,当硬件无法支撑请求流量时,能够将流量分发到不一样的服务器上,AKF拆分之Y轴、Z轴拆分是业务拆分与数据拆分,那也就会涉及到将数据库中的数据拆分存储在不一样的地方,这就叫分库分表,不一样类型数据存储在不一样数据库中作多机存储和负载,这样一来,传统的事务机制ACID便没法正常运行。

分库分表内容是数据切分(Sharding),以及切分后对数据的定位、整合。具体来讲, 数据切分就是将数据分散存储到多个数据库中,使得单一数据库中的数据量变小,经过扩充主机的数量缓解单一数据库性能问题,从而达到提高数据库操做性能的目的。

数据切分根据其切分类型,能够分为两种方式:垂直(纵向)切分和水平(横向)切分。

垂直拆分

垂直切分常见有垂直分库和垂直分表两种,两种含义相似。

垂直分库就是根据业务耦合性,将关联度低的不一样表存储在不一样的数据库。作法与大系统拆分为多个小系统相似,按业务分类进行独立划分。与"微服务治理"的作法类似,每一个微服务使用单独的一个数据库。如图:

垂直分表相似,例如将一张表包含一我的全部信息,例如姓名、身份证、性别、身高、体重、省、市、区、村、专业、G点等等,那么能够拆分红三个表:

第一个表只包含基本信息(姓名、身份证、性别、身高、体重);

第二个表包含籍贯信息(省、市、区、村);

第三个表包含学习信息(专业、G点)。

垂直拆分优缺点

垂直切分的优势:

  • 解决业务系统层面的耦合,业务清晰
  • 与微服务的治理相似,也能对不一样业务的数据进行分级管理、维护、监控、扩展等
  • 高并发场景下,垂直切分必定程度的提高IO、数据库链接数、单机硬件资源的瓶颈

垂直切分的缺点:

  • 部分表没法join,只能经过接口聚合方式解决,提高了开发的复杂度
  • 分布式事务处理复杂
  • 依然存在单表数据量过大的问题(须要水平切分)

水平拆分

上面对数据库垂直拆分以后,若是某个库仍是好大,好比存储的数据极其庞大,那么能够再对数据库进行水平的拆分:

上面的水平拆分时按照ID区间来切分。例如:将userId为1~10000的记录分到第一个库,10001~20000的分到第二个库,以此类推。某种意义上,某些系统中使用的"冷热数据分离",将一些使用较少的历史数据迁移到其余库中,业务功能上只提供热点数据的查询,也是相似的实践。

除了上面按照用户ID区间拆分,也能够作Hash运算拆分,这儿就不详细展开了。

水平拆分优缺点

水平拆分优势在于:

  • 单表大小可控
  • 自然便于水平扩展,后期若是想对整个分片集群扩容时,只须要添加节点便可,无需对其余分片的数据进行迁移
  • 使用分片字段进行范围查找时,连续分片可快速定位分片进行快速查询,有效避免跨分片查询的问题。

水平拆分缺点:

  • 热点数据成为性能瓶颈。连续分片可能存在数据热点,例如按时间字段分片,有些分片存储最近时间段内的数据,可能会被频繁的读写,而有些分片存储的历史数据,则不多被查询

分库分表带来的问题

分库分表能有效的缓解单机和单库带来的性能瓶颈和压力,突破网络IO、硬件资源、链接数的瓶颈,同时也带来了一些问题,前面说过,事务包含一组子操做,这些造做要么所有执行,要么所有不执行,可是分库以后,一个事务可能涉及多个数据库或者多个表扩库执行,而网络具备不稳定性,也就是事务执行难度加大,分表分库后事务为了与传统事务作出区别,叫作分布式事务(跨分片事务)。

跨分片事务也是分布式事务,没有简单的方案,通常可以使用"XA协议"和"两阶段提交"处理。

分布式事务能最大限度保证了数据库操做的原子性。但在提交事务时须要协调多个节点,推后了提交事务的时间点,延长了事务的执行时间。致使事务在访问共享资源时发生冲突或死锁的几率增高。随着数据库节点的增多,这种趋势会愈来愈严重,从而成为系统在数据库层面上水平扩展的枷锁。

最终一致性

对于那些性能要求很高,但对一致性要求不高的系统,每每不苛求系统的实时一致性,只要在容许的时间段内达到最终一致性便可,可采用事务补偿的方式。与事务在执行中发生错误后当即回滚的方式不一样,事务补偿是一种过后检查补救的措施,一些常见的实现方法有:对数据进行对帐检查,基于日志进行对比,按期同标准数据来源进行同步等等。事务补偿还要结合业务系统来考虑。

分布式事务解决思路

讲这个以前须要先简单回顾CAP原则和Base理论,由于分布式事务不一样于 ACID 的刚性事务,在分布式场景下基于 BASE 理论,提出了柔性事务的概念。要想经过柔性事务来达到最终的一致性,就须要依赖于一些特性,这些特性在具体的方案中不必定都要知足,由于不一样的方案要求不同;可是都不知足的话,是不可能作柔性事务的。

CAP原则

CAP通常人可能听了不下一百遍了,不少人都说CAP是"三选二"的关系,让人误觉得有AC这种状况,可是实际CAP是二选一的关系,这个在2012年已经有一篇论文进行解释:CAP Twelve Years Later: How the "Rules" Have Changed

至关因而对以前三选二说法进行修正,CAP中P(分区容错性)是必须具有的,在知足P的前提下,很难同时知足A(可用性)和C(一致性),可是在以后,又有一篇文章:Harvest, yield, and scalable tolerant systems,这篇论文是基于上面那篇“CAP 12年后”的论文写的,它主要提出了 Harvest 和 Yield 概念,并把上面那篇论文中所讨论的东西讲得更为仔细了一些。简单来讲就是知足P以后,C和A在放宽约束后能够获得兼顾,并非非此即彼的关系,说远了。

为何P是必须的?

为何CAP原则中分区容错性是必须的呢,首先要理解什么是分区容错性,分区,这儿说的是网络,网络集群设计到不少的服务器,某一瞬间网络不稳定,那么至关于将网络分红了不一样的区,假设分红了两个区,这时候若是有一笔交易:
对分区一发出消息:A给B转帐100元,对分区二发出消息:A给B转帐200元

那么对于两个分区而言,有两种状况:

a)无可用性,即这两笔交易至少会有一笔交易不会被接受;

b)无一致性,一半看到的是 A给B转帐100元而另外一半则看到 A给B转帐200元。
因此,分区容忍性必需要知足,解决策略是一个数据项复制到多个节点上,那么出现分区以后,这一数据项就可能分布到各个区里。容忍性就提升了。

Base理论

在不少时候,咱们并不须要强一致性的系统,因此后来,人们争论关于数据一致性和可用性时,主要是集中在强一致性的 ACID 或最终一致性的 BASE中, BASE是对CAP中一致性和可用性权衡的结果,其来源于对大规模互联网分布式系统实践的总结,是基于CAP定律逐步演化而来。其核心思想是即便没法作到强一致性,但每一个应用均可以根据自身业务特色,才用适当的方式来使系统打到最终一致性。

BASE理论是Basically Available(基本可用),Soft State(软状态)和Eventually Consistent(最终一致性)三个短语的缩写。

基本可用

假设系统,出现了不可预知的故障,但仍是能用,相比较正常的系统而言:

  1. 响应时间上的损失:正常状况下的搜索引擎0.5秒即返回给用户结果,而基本可用的搜索引擎能够在2秒做用返回结果。
  2. 功能上的损失:在一个电商网站上,正常状况下,用户能够顺利完成每一笔订单。可是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。

这就叫基本可用

软状态

相对于原子性而言,要求多个节点的数据副本都是一致的,这是一种“硬状态”。软状态指的是:容许系统中的数据存在中间状态,并认为该状态不影响系统的总体可用性,即容许系统在多个不一样节点的数据副本存在数据延时。

最终一致性

上面说软状态,而后不可能一直是软状态,必须有个时间期限。在期限事后,应当保证全部副本保持数据一致性,从而达到数据的最终一致性。这个时间期限取决于网络延时、系统负载、数据复制方案设计等等因素。

Base其核心思想是

既然没法作到强一致性(Strong consistency),但每一个应用均可以根据自身的业务特色,采用适当的方式来使系统达到最终一致性(Eventual consistency)。有了Base理论就能够开始讲述分布式事务的处理思路了。

二阶段提交协议

二阶段提交(2PC:Two-Phase Commit),顾名思义,该协议将一个分布式的事务过程拆分红两个阶段: 投票事务提交 。为了让整个数据库集群可以正常的运行,该协议指定了一个 协调者 单点,用于协调整个数据库集群各节点的运行。为了简化描述,咱们将数据库集群中的各个节点称为 参与者 ,三阶段提交协议中一样包含协调者和参与者这两个角色定义,后面再说。

第一阶段:投票

该阶段的主要目的在于打探数据库集群中的各个参与者是否可以正常的执行事务,具体步骤以下:

  1. 协调者向全部的参与者发送事务执行请求,并等待参与者反馈事务执行结果;
  2. 事务参与者收到请求以后,执行事务但不提交,并记录事务日志;
  3. 参与者将本身事务执行状况反馈给协调者,同时阻塞等待协调者的后续指令。

第二阶段:事务提交

在通过第一阶段协调者的询盘以后,各个参与者会回复本身事务的执行状况,这时候存在 3 种可能性:

  1. 全部的参与者都回复可以正常执行事务。
  2. 一个或多个参与者回复事务执行失败。
  3. 协调者等待超时。

对于第 1 种状况,协调者将向全部的参与者发出提交事务的通知,具体步骤以下:

  1. 协调者向各个参与者发送 commit 通知,请求提交事务;
  2. 参与者收到事务提交通知以后执行 commit 操做,而后释放占有的资源;
  3. 参与者向协调者返回事务 commit 结果信息。

对于第 2 和第 3 种状况,协调者均认为参与者没法成功执行事务,为了整个集群数据的一致性,因此要向各个参与者发送事务回滚通知,具体步骤以下:

  1. 协调者向各个参与者发送事务 rollback 通知,请求回滚事务;
  2. 参与者收到事务回滚通知以后执行 rollback 操做,而后释放占有的资源;
  3. 参与者向协调者返回事务 rollback 结果信息。

两阶段提交协议解决的是分布式数据库数据强一致性问题,实际应用中更多的是用来解决事务操做的原子性,下图描绘了协调者与参与者的状态转换。

站在协调者的角度,在发起投票以后就进入了 WAIT 等待状态,等待全部参与者回复各自事务执行状态,并在收到全部参与者的回复后决策下一步是发送 commit提交 或 rollback回滚信息。

站在参与者的角度,当回复完协调者的投票请求以后便进入 READY 状态(可以正常执行事务),接下去就是等待协调者最终的决策通知,一旦收到通知即可依据决策执行 commit 或 rollback 操做。

两阶段提交协议原理简单、易于实现,可是缺点也是显而易见的,包含以下:

  • 单点问题

协调者在整个两阶段提交过程当中扮演着举足轻重的做用,一旦协调者所在服务器宕机,就会影响整个数据库集群的正常运行。好比在第二阶段中,若是协调者由于故障不能正常发送事务提交或回滚通知,那么参与者们将一直处于阻塞状态,整个数据库集群将没法提供服务。

  • 同步阻塞

两阶段提交执行过程当中,全部的参与者都须要遵从协调者的统一调度,期间处于阻塞状态而不能从事其余操做,这样效率极其低下。

  • 数据不一致性

两阶段提交协议虽然是分布式数据强一致性所设计,但仍然存在数据不一致性的可能性。好比在第二阶段中,假设协调者发出了事务 commit 通知,可是由于网络问题该通知仅被一部分参与者所收到并执行了commit 操做,其他的参与者则由于没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。

针对上述问题能够引入 超时机制 和 互询机制在很大程度上予以解决。

超时机制

对于协调者来讲若是在指定时间内没有收到全部参与者的应答,则能够自动退出 WAIT 状态,并向全部参与者发送 rollback 通知。对于参与者来讲若是位于 READY 状态,可是在指定时间内没有收到协调者的第二阶段通知,则不能武断地执行 rollback 操做,由于协调者可能发送的是 commit 通知,这个时候执行 rollback 就会致使数据不一致。

互询机制

此时,咱们能够介入互询机制,让参与者 A 去询问其余参与者 B 的执行状况。若是 B 执行了 rollback 或 commit 操做,则 A 能够大胆的与 B 执行相同的操做;若是 B 此时尚未到达 READY 状态,则能够推断出协调者发出的确定是 rollback 通知;若是 B 一样位于 READY 状态,则 A 能够继续询问另外的参与者。只有当全部的参与者都位于 READY 状态时,此时两阶段提交协议没法处理,将陷入长时间的阻塞状态。

三阶段提交协议

三阶段提交协议(3PC:Three-Phase Commit), 针对两阶段提交存在的问题,三阶段提交协议经过引入一个 预询盘 阶段,以及超时策略来减小整个集群的阻塞时间,提高系统性能。三阶段提交的三个阶段分别为:预询盘(can_commit)、预提交(pre_commit),以及事务提交(do_commit)。

第一阶段:预询盘

该阶段协调者会去询问各个参与者是否可以正常执行事务,参与者根据自身状况回复一个预估值,相对于真正的执行事务,这个过程是轻量的,具体步骤以下:

  1. 协调者向各个参与者发送事务询问通知,询问是否能够执行事务操做,并等待回复;
  2. 各个参与者依据自身情况回复一个预估值,若是预估本身可以正常执行事务就返回肯定信息,并进入预备状态,不然返回否认信息。

第二阶段:预提交

本阶段协调者会根据第一阶段的询盘结果采起相应操做,询盘结果主要有 3 种:

  1. 全部的参与者都返回肯定信息。
  2. 一个或多个参与者返回否认信息。
  3. 协调者等待超时。

针对第 1 种状况,协调者会向全部参与者发送事务执行请求,具体步骤以下:

  1. 协调者向全部的事务参与者发送事务执行通知;
  2. 参与者收到通知后执行事务但不提交;
  3. 参与者将事务执行状况返回给客户端。

在上述步骤中,若是参与者等待超时,则会中断事务。 针对第 2 和第 3 种状况,协调者认为事务没法正常执行,因而向各个参与者发出 abort 通知,请求退出预备状态,具体步骤以下:

  1. 协调者向全部事务参与者发送 abort 通知;
  2. 参与者收到通知后中断事务。

第三阶段:事务提交

若是第二阶段事务未中断,那么本阶段协调者将会依据事务执行返回的结果来决定提交或回滚事务,分为 3 种状况:

  1. 全部的参与者都能正常执行事务。
  2. 一个或多个参与者执行事务失败。
  3. 协调者等待超时。

针对第 1 种状况,协调者向各个参与者发起事务提交请求,具体步骤以下:

  1. 协调者向全部参与者发送事务 commit 通知;
  2. 全部参与者在收到通知以后执行 commit 操做,并释放占有的资源;
  3. 参与者向协调者反馈事务提交结果。

针对第 2 和第 3 种状况,协调者认为事务没法成功执行,因而向各个参与者发送事务回滚请求,具体步骤以下:

  1. 协调者向全部参与者发送事务 rollback 通知;
  2. 全部参与者在收到通知以后执行 rollback 操做,并释放占有的资源;
  3. 参与者向协调者反馈事务回滚结果。

在本阶段若是由于协调者或网络问题,致使参与者迟迟不能收到来自协调者的 commit 或 rollback 请求,那么参与者将不会如两阶段提交中那样陷入阻塞,而是等待超时后继续 commit,相对于两阶段提交虽然下降了同步阻塞,但仍然没法彻底避免数据的不一致。两阶段提交协议中所存在的长时间阻塞状态发生的概率仍是很是低的,因此虽然三阶段提交协议相对于两阶段提交协议对于数据强一致性更有保障,可是由于效率问题,两阶段提交协议在实际系统中反而更加受宠。

TCC模式

TCC是Try、Confirm 和 Cancel三个单词首字母缩写,它们分别的职责是:

Try:负责预留资源(好比新建一条状态=PENDING的订单);
作业务检查,简单来讲就是不能预留已经被占用的资源;
隔离预留资源。

Confirm:负责落地所预留的资源
真正的执行业务使用try阶段预留的资源,幂等。

Cancel: 负责撤销所预留的资源

须要用户根据本身的业务场景实现 Try、Confirm 和 Cancel 三个操做;事务发起方在一阶段执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。

关于预留资源要多说两句,资源都是有限的,所以预留资源都是有时效的,若是当预留资源迟迟得不到Confirm——咱们将这种状况称为timeout——参与方会自行将其Cancel。也就是说参与方对于资源具备自我管理能力,这样能够避免因发起方的问题致使资源被长期占用。

TCC增长了业务检查和撤销事务的功能。同时,TCC将2PC数据库层面的动做提高到了服务层面,不一样的是TCC的全部动做都是一个本地事务,每一个本地事务都在动做完成后commit到数据库:

  • Try至关于2PC的Commit request phase,外加了业务检查逻辑
  • Confirm至关于2PC的Commit phase的commit动做
  • Cancel至关于2PC的Commit phase的rollback动做

流程步骤:

  1. 发起方发送Try到全部参与方
  2. 每一个参与方执行Try,预留资源
  3. 发起方收到全部参与方的Try结果
  4. 发起方发送Confirm/Cancel到全部参与房
  5. 每一个参与方执行Confirm/Cancel
  6. 发起方收到全部参与方的Confirm/Cancel结果

流程和两阶段提交很是相似。

相关文章
相关标签/搜索