分布式系统一致性理论和实践

ACID 理论

关系型数据库具备解决复琐事务场景的能力,关系型数据库的事务知足 ACID 的特性。数据库

  • Atomicity:原子性(要么都作,要么都不作)后端

  • Consistency:一致性(数据库只有一个状态,不存在未肯定状态)网络

  • Isolation:隔离性(事务之间互不干扰)并发

  • Durability: 永久性(事务一旦提交,数据库记录永久不变)分布式

具备 ACID 特性的数据库支持数据的强一致性,保证了数据自己不会出现不一致。高并发

CAP 理论(强调分区容错性)

CAP 是指在一个分布式系统下, 包含三个要素:Consistency(一致性)、Availability(可用性)、Partition tolerance(分区容错性),而且三者不可得兼。性能

  • C:Consistency,一致性,全部数据变更都是同步的。spa

  • A:Availability,可用性,即在能够接受的时间范围内正确地响应用户请求。3d

  • P:Partition tolerance,分区容错性,即某节点或网络分区故障时,系统仍可以提供知足一致性和可用性的服务。日志

关系型数据库单节点保证了数据强一致性(C)和可用性(A),可是却没法保证分区容错性(P)。

然而在分布式系统下,为了保证模块的分区容错性(P),只能在数据强一致性(C)和可用性(A)之间作平衡。具体表现为在必定时间内,可能模块之间数据是不一致的,可是经过自动或手动补偿后可以达到最终的一致。

BASE 理论(强调可用性)

BASE 理论主要是解决 CAP 理论中分布式系统的可用性和一致性不可兼得的问题。BASE 理论包含如下三个要素:

  • BA:Basically Available,基本可用。

    • 好比咱们在淘宝上搜索商品,正常状况下是在 0.5s 内返回查询结果,可是因为后端的系统故障致使查询响应时间变成了 2s。
    • 好比数据库采用分片模式,100W 个用户数据分在 5 个数据库实例上,若是破坏了一个实例,那么可用性还有 80%,也就是 80% 的用户均可以登陆,系统仍然可用。
    • 电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层也可能只提供降级服务。这就是损失部分可用性的体现。  
  • S:Soft State,软状态,状态能够有一段时间不一样步。

  • E:Eventually Consistent,最终一致,最终数据是一致的就能够了,而不是时时保持强一致。

BASE 模型与 ACID 不一样,知足 CAP 理论,经过牺牲强一致性来保证系统可用性。因为牺牲了强一致性,系统在处理请求的过程当中,数据能够存在短时的不一致。

系统在处理业务时,记录每一步的临时状态。当出现异常时,根据状态判断是否继续处理请求或者退回原始状态,从而达到数据的最终一致。

二阶段提交协议

X/Open DTP(Distributed Transaction Process)是一个分布式事务模型,此模型主要使用二阶段提交(2PC,Two-Phase-Commit)来保证分布式事务的完整性。在这个模型里面,有三个角色:

  • AP:Application,应用程序,业务层。

  • RM:Resource Manager,资源管理器,关系型数据库或支持 XA 接口(XA 规范是 X/Open 组织定义的分布式事务规范)的组件。

  • TM: Transaction Manager ,事务管理器,负责各个 RM 的提交和回滚。

当应用程序(AP)调用了事务管理器(TM)的提交方法时,事务的提交分为两个阶段实行。

第一阶段(准备阶段)

TM 通知全部参与事务的各个 RM,给每一个 RM 发送 prepare 消息。

RM 接收到消息后进入准备阶段后,要么直接返回失败,要么建立并执行本地事务,写本地事务日志(redo 和 undo 日志),可是不提交(此处只保留最后一步耗时最少的提交操做给第二阶段执行)。

第二阶段(提交 / 回滚阶段)

TM 收到 RM 准备阶段的失败消息或者获取 RM 返回消息超时,则直接给 RM 发送回滚(rollback)消息,不然发送提交(commit)消息。

RM 根据 TM 的指令执行提交或者回滚,执行完成后释放全部事务处理过程当中使用的锁(最后阶段释放锁)。

二阶段提交的利弊

考虑数据库事务的执行其实是先将执行操做写入binlog,等到最后经过一个commit指令将binlog的内容一次更新到表中,或者写到一半经过一个rollback指令将binlog中的内容回滚。能够想到使用2个阶段来执行这个过程,第一阶段,写入binlog;第二阶段执行commit或者rollback。这就是著名的两阶段提交协议(2PC)。若是仔细考虑,会发现两阶段协议并无解决问题,只不过下降了出错的几率而已,由于第二阶段一样存在一个提交成功一个被回滚,已提交的没法被回滚的状况。注意最终状态是多台机器的状态&&求与的结果。

优势

2PC 提供了一套完整的分布式事务的解决方案,遵循事务严格的 ACID 特性。

缺点

  • TM 经过 XA 接口与各个 RM 之间进行数据交互,从第一阶段的准备阶段,业务所涉及的数据就被锁定,而且锁定跨越整个提交流程。在高并发和涉及业务模块较多的状况下对数据库的性能影响较大。

  • 二阶段是反可伸缩模式的,业务规模越大,涉及模块越多,局限性越大,系统可伸缩性越差。

  • 在技术栈比较杂的分布式应用中,存储组件有不少不支持 XA 协议

二阶段的诸多弊端,致使分布式系统下没法直接使用此方案来解决数据一致性问题,但它提供了解决分布式系统下数据一致性问题的思路。

可靠消息最终一致性-消息事务

可靠消息最终一致性方案本质上是利用 MQ 组件实现的二阶段提交。此方案涉及 3 个模块:

  • 上游应用,执行业务并发送 MQ 消息。

  • 可靠消息服务和 MQ 消息组件,协调上下游消息的传递,并确保上下游数据的一致性。

  • 下游应用,监听 MQ 的消息并执行自身业务。

上游应用执行业务并发送 MQ 消息(第一阶段)

上游应用将本地业务执行和消息发送绑定在同一个本地事务中,保证本地操做成功并发送 MQ 消息,不然两步操做都失败并回滚。 

  1. 上游应用发送待确认消息到可靠消息系统

  2. 可靠消息系统保存待确认消息并返回

  3. 上游应用执行本地业务

  4. 上游应用通知可靠消息系统确认业务已执行并发送消息。

  5. 可靠消息系统修改消息状态为发送状态并将消息投递到 MQ 中间件。

以上每一步均可能出现失败状况,分析一下这 5 步出现异常后上游业务和消息发送是否一致:

 

上游应用执行完成,下游应用还没有执行或执行失败时,此事务即处于 BASE 理论的 Soft State 状态。

下游应用监听 MQ 消息并执行业务(第二阶段)

下游应用监听 MQ 消息并执行业务,而且将消息的消费结果通知可靠消息服务。

可靠消息的状态须要和下游应用的业务执行保持一致,可靠消息状态不是已完成时,确保下游应用未执行,可靠消息状态是已完成时,确保下游应用已执行。

  1. 下游应用监听 MQ 消息组件并获取消息

  2. 下游应用根据 MQ 消息体信息处理本地业务

  3. 下游应用向 MQ 组件自动发送 ACK 确认消息被消费

  4. 下游应用通知可靠消息系统消息被成功消费,可靠消息将该消息状态更改成已完成。

以上每一步均可能出现失败状况,分析一下这 4 步出现异常后下游业务和消息状态是否一致:

 

经过分析以上两个阶段可能失败的状况,为了确保上下游数据的最终一致性,在可靠消息系统中,须要开发消息状态确认和消息重发两个功能以实现 BASE 理论的 Eventually Consistent 特性。 

消息状态确认:

可靠消息服务定时监听消息的状态,若是存在状态为待确认而且超时的消息,则表示上游应用和可靠消息交互中的步骤 4 或者 5 出现异常。

  1. 可靠消息查询超时的待确认状态的消息

  2. 向上游应用查询业务执行的状况

  3. 业务未执行,则删除该消息,保证业务和可靠消息服务的一致性。业务已执行,则修改消息状态为已发送,并发送消息到 MQ 组件。

消息重发:

消息已发送则表示上游应用已经执行,接下来则确保下游应用也能正常执行。

可靠消息服务发现可靠消息服务中存在消息状态为已发送而且超时的消息,则表示可靠消息服务和下游应用中存在异常的步骤,不管哪一个步骤出现异常,可靠消息服务都将此消息从新投递到 MQ 组件中供下游应用监听。

下游应用监听到此消息后,在保证幂等性的状况下从新执行业务并通知可靠消息服务此消息已经成功消费,最终确保上游应用、下游应用的数据最终一致性。

  1. 可靠消息服务定时查询状态为已发送并超时的消息

  2. 可靠消息将消息从新投递到 MQ 组件中

  3. 下游应用监听消息,在知足幂等性的条件下,从新执行业务。

  4. 下游应用通知可靠消息服务该消息已经成功消费。

经过消息状态确认和消息重发两个功能,能够确保上游应用、可靠消息服务和下游应用数据的最终一致性。

在实际接入过程当中,须要引入人工干预功能。好比引入重发次数限制,超太重发次数限制的将消息修改成死亡消息,等待人工干预。

TCC(Try-Confirm-Cancel)-业务补偿类型

业务补偿类型,其基本思想是对每个业务操做作一个逆操做,一旦成功了,就作正向业务,一旦失败了就作业务的逆操做。一般在业务逻辑简单而且正逆操做清晰的时候用比较好。在技术栈统一的状况下,可选择 TCC 来解决数据一致的方法。

TCC 方案是二阶段提交的另外一种实现方式,它涉及 3 个模块,主业务、从业务和活动管理器(协做者)。

第一阶段:主业务服务分别调用全部从业务服务的 try 操做,并在活动管理器中记录全部从业务服务。当全部从业务服务 try 成功或者某个从业务服务 try 失败时,进入第二阶段。

第二阶段:活动管理器根据第一阶段从业务服务的 try 结果来执行 confirm 或 cancel 操做。若是第一阶段全部从业务服务都 try 成功,则协做者调用全部从业务服务的 confirm 操做,不然,调用全部从业务服务的 cancel 操做。

在第二阶段中,confirm 和 cancel 一样存在失败状况,因此须要对这两种状况作异常处理以保证数据一致性。

  • Confirm 失败:则回滚全部 confirm 操做并执行 cancel 操做。

  • Cancel 失败:从业务服务须要提供自动 cancel 机制,以保证 cancel 成功。

基于 HTTP 协议的 TCC 实现:

  1. 主业务服务调用从业务服务的 try 操做,并获取 confirm/cancel 接口和超时时间。

  2. 若是从业务都 try 成功,主业务服务执行本地业务,并将获取的 confirm/cancel 接口发送给活动管理器,活动管理器会顺序调用从业务 1 和从业务 2 的 confirm 接口并记录请求状态,若是请求成功,则通知主业务服务提交本地事务。若是 confirm 部分失败,则活动管理器会顺序调用从业务 1 和从业务 2 的 cancel 接口来取消 try 的操做。

  3. 若是从业务部分或所有 try 失败,则主业务直接回滚并结束,而 try 成功的从业务服务则经过定时任务来处理处于 try 完成但超时的数据,将这些数据作回滚处理保证主业务服务和从业务服务的数据一致。

一般在核心业务上有不少附加业务,好比当用户支付完成后,须要经过短信通知用户支付成功。这一类业务的成功或者失败不会影响核心业务,甚至不少大型互联网平台在并高并发的状况下会主动关闭这一类业务以保证核心业务的顺利执行。那么怎么处理这类状况呢,来看看最大努力通知方案。

最大努力通知-消息重试&查询补偿

最大努力通知方案涉及三个模块:

  • 上游应用,发消息到 MQ 队列。

  • 下游应用(例如短信服务、邮件服务),接受请求,并返回通知结果。

  • 最大努力通知服务 监听消息队列,将消息存储到数据库中,并按照通知规则调用下游应用的发送通知接口。

最大努力通知服务表示在不影响主业务的状况下,尽量地确保数据的一致性。它须要开发人员根据业务来指定通知规则,在知足通知规则的前提下,尽量的确保数据的一致,以尽到最大努力的目的。

  1. 上游应用发送 MQ 消息到 MQ 组件内,消息内包含通知规则和通知地址

  2. 最大努力通知服务监听到 MQ 内的消息,解析通知规则并放入延时队列等待触发通知

  3. 最大努力通知服务调用下游的通知地址,若是调用成功,则该消息标记为通知成功,若是失败则在知足通知规则(例如 5 分钟发一次,共发送 10 次)的状况下从新放入延时队列等待下次触发。

典型的场景是向银行发送了转帐请求未获得明确的成功失败返回码,此时先作业务结果的查询,根据结果作相应处理,好比查询结果成功,则置状态为成功,查询结果失败,则作相应的业务补偿,查询结果为未知,则继续查询。

相关文章
相关标签/搜索