分布式系统事务一致性

一 分布式系统特色

现今互联网界,分布式系统和微服务架构盛行。业界著名的CAP理论也告诉咱们,在设计和实现一个分布式系统时,须要将数据一致性、系统可用性和分区容忍性放在一块儿考虑。算法

一、CAP理论数据库

在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容忍性(Partition Tolerance)3 个要素最多只能同时知足两个,不可兼得。其中,分区容忍性又是不可或缺的。网络

图片

  • 一致性:分布式环境下多个节点的数据是否强一致。架构

  • 可用性:分布式服务能一直保证可用状态。当用户发出一个请求后,服务能在有限时间内返回结果。并发

  • 分区容忍性:特指对网络分区的容忍性。app

举例:Cassandra、Dynamo 等,默认优先选择AP,弱化C;HBase、MongoDB 等,默认优先选择CP,弱化A。异步

二、BASE 理论分布式

核心思想:ide

  • 基本可用(Basically Available):指分布式系统在出现故障时,容许损失部分的可用性来保证核心可用。微服务

  • 软状态(Soft State):指容许分布式系统存在中间状态,该中间状态不会影响到系统的总体可用性。

  • 最终一致性(Eventual Consistency):指分布式系统中的全部副本数据通过必定时间后,最终可以达到一致的状态。

二 一致性模型

数据的一致性模型能够分红如下 3 类:

  1. 强一致性:数据更新成功后,任意时刻全部副本中的数据都是一致的,通常采用同步的方式实现。

  2. 弱一致性:数据更新成功后,系统不承诺当即能够读到最新写入的值,也不承诺具体多久以后能够读到。

  3. 最终一致性:弱一致性的一种形式,数据更新成功后,系统不承诺当即能够返回最新写入的值,可是保证最终会返回上一次更新操做的值。

分布式系统数据的强一致性、弱一致性和最终一致性能够经过Quorum NRW算法分析。

三 分布式事务

分布式事务的目的是保障分布式存储中数据一致性,而跨库事务会遇到各类不可控制的问题,如个别节点宕机,像单机事务同样的ACID是没法奢望的。一、Two/Three Phase Commit2PC,中文叫两阶段提交。在分布式系统中,每一个节点虽然能够知晓本身的操做时成功或者失败,却没法知道其余节点的操做的成功或失败。当一个事务跨越多个节点时,为了保持事务的ACID特性,须要引入一个做为协调者的组件来统一掌控全部节点(称做参与者)的操做结果并最终指示这些节点是否要把操做结果进行真正的提交。两阶段提交的算法以下:第一阶段:

  1. 协调者会问全部的参与者结点,是否能够执行提交操做。
  2. 各个参与者开始事务执行的准备工做:如:为资源上锁,预留资源。
  3. 参与者响应协调者,若是事务的准备工做成功,则回应“能够提交”,不然回应“拒绝提交”。

第二阶段:

  • 若是全部的参与者都回应“能够提交”,那么,协调者向全部的参与者发送“正式提交”的命令。参与者完成正式提交,并释放全部资源,而后回应“完成”,协调者收集各结点的“完成”回应后结束这个Global Transaction。
  • 若是有一个参与者回应“拒绝提交”,那么,协调者向全部的参与者发送“回滚操做”,并释放全部资源,而后回应“回滚完成”,协调者收集各结点的“回滚”回应后,取消这个Global Transaction。

两段提交最大的问题就是第3)项,若是第一阶段完成后,参与者在第二阶没有收到决策,那么数据结点会进入“不知所措”的状态,这个状态会block住整个事务。也就是说,协调者Coordinator对于事务的完成很是重要,Coordinator的可用性是个关键。因些,咱们引入三段提交,三段提交在Wikipedia上的描述以下,他把二段提交的第一个段break成了两段:询问,而后再锁资源。最后真正提交。三段提交的核心理念是:在询问的时候并不锁定资源,除非全部人都赞成了,才开始锁资源。但三阶段提交也存在一些缺陷,要完全从协议层面避免数据不一致,能够采用Paxos或者Raft 算法。目前两阶段提交、三阶段提交存在以下的局限性,并不适合在微服务架构体系下使用:

  • 全部的操做必须是事务性资源(好比数据库、消息队列、EJB组件等),存在使用局限性(微服务架构下多数使用HTTP协议),比较适合传统的单体应用;
  • 因为是强一致性,资源须要在事务内部等待,性能影响较大,吞吐率不高,不适合高并发与高性能的业务场景;

二、Try Confirm Cancel(TCC)

一个完整的TCC业务由一个主业务服务和若干个从业务服务组成,主业务服务发起并完成整个业务活动,TCC模式要求从服务提供三个接口:Try、Confirm、Cancel。

  1. Try:完成全部业务检查,预留必须业务资源。

  2. Confirm:真正执行业务,不做任何业务检查;只使用Try阶段预留的业务资源;Confirm操做知足幂等性。

  3. Cancel:释放Try阶段预留的业务资源;Cancel操做知足幂等性。

整个TCC业务分红两个阶段完成:

第一阶段:主业务服务分别调用全部从业务的try操做,并在活动管理器中登记全部从业务服务。当全部从业务服务的try操做都调用成功或者某个从业务服务的try操做失败,进入第二阶段。第二阶段:活动管理器根据第一阶段的执行结果来执行confirm或cancel操做。若是第一阶段全部try操做都成功,则活动管理器调用全部从业务活动的confirm操做。不然调用全部从业务服务的cancel操做。与2PC比较:

  • 位于业务服务层而非资源层。
  • 没有单独的准备(prepare)阶段,Try操做兼备资源操做与准备能力。
  • Try操做能够灵活选择业务资源的锁定粒度。
  • 开发成本较高。

缺点:

  • Canfirm和Cancel的幂等性很难保证。
  • 这种方式缺点比较多,一般在复杂场景下是不推荐使用的,除非是很是简单的场景,很是容易提供回滚Cancel,并且依赖的服务也很是少的状况。
  • 这种实现方式会形成代码量庞大,耦合性高。并且很是有局限性,由于有不少的业务是没法很简单的实现回滚的,若是串行的服务不少,回滚的成本实在过高。

三、基于消息的分布式事务

核心思想:

eBay 的架构师Dan Pritchett,曾在一篇解释BASE 原理的论文《Base:An Acid Alternative》中提到一个eBay 分布式系统一致性问题的解决方案。它的核心思想是将须要分布式处理的任务经过消息或者日志的方式来异步执行,消息或日志能够存到本地文件、数据库或消息队列,再经过业务规则进行失败重试,它要求各服务的接口是幂等的。

基于消息的分布式事务模式核心思想是经过消息系统来通知其余事务参与方本身事务的执行状态。消息系统的引入更有效的将事务参与方解耦,各个参与方能够异步执行。

该种模式的难点在于解决本地事务执行和消息发送的一致性:二者要同时执行成功或者同时取消执行。

实现上主要有两种方式:

  • 基于事务消息的方案

  • 基于本地消息的方案

1)基于事务消息的分布式事务普通消息是没法解决本地事务执行和消息发送的一致性问题的。由于消息发送是一个网络通讯的过程,发送消息的过程就有可能出现发送失败、或者超时的状况。超时有可能发送成功了,有可能发送失败了,消息的发送方是没法肯定的,因此此时消息发送方不管是提交事务仍是回滚事务,都有可能不一致性出现。解决这个问题,须要引入事务消息,事务消息和普通消息的区别在于事务消息发送成功后,处于 prepared 状态,不能被订阅者消费,等到事务消息的状态更改成可消费状态后,下游订阅者才能够监听到次消息。本地事务和事务消息的发送的处理流程以下:

  • 事务发起者预先发送一个事务消息。
  • MQ 系统收到事务消息后,将消息持久化,消息的状态是“待发送”,并给发送者一个 ACK 消息。
  • 事务发起者若是没有收到 ACK 消息,则取消本地事务的执行;若是收到了 ACK 消息,则执行本地事务,并给 MQ 系统再发送一个消息,通知本地事务的执行状况。
  • MQ 系统收到消息通知后,根据本地事务的执行状况更改事务消息的状态,若是成功执行,则将消息更改成“可消费”并择机下发给订阅者;若是事务执行失败,则删除该事务消息。
  • 本地事务执行完毕后,发给 MQ 的通知消息有可能丢失了。因此支持事务消息的 MQ 系统有一个定时扫描逻辑,扫描出状态仍然是“待发送”状态的消息,并向消息的发送方发起询问,询问这条事务消息的最终状态如何并根据结果更新事务消息的状态。所以事务的发起方须要给 MQ 系统提供一个事务消息状态查询接口。
  • 若是事务消息的状态是“可发送”,则 MQ 系统向下游参与者推送消息,推送失败会不停重试。
  • 下游参与者收到消息后,执行本地事务,本地事务若是执行成功,则给 MQ 系统发送 ACK 消息;若是执行失败,则不发送 ACK 消息,MQ 系统会持续推送给消息。

图片

2)基于本地消息的分布式事务

基于事务消息的模式对 MQ 系统要求较高,并非全部 MQ 系统都支持事务消息的,RocketMQ 是目前为数很少的支持事务消息的 MQ 系统。若是所依赖的 MQ 系统不支持事务消息,那么能够采用本地消息的分布式模式。该种模式的核心思想是:上游服务:

  • 事务的发起方维护一个本地消息表,业务执行和本地消息表的执行处在同一个本地事务中。业务执行成功,则同时记录一条“待发送”状态的消息到本地消息表中。
  • 系统中启动一个定时任务定时扫描本地消息表中状态为“待发送”的记录,并将其发送到 MQ 系统中,若是发送失败或者超时,则一直发送,直到发送成功后,从本地消息表中删除该记录(或修改状态为“已发送”)。
  • 消息会重试发送,可能会重复,因此每条消息须要一个惟一ID。

下游服务:

  • 后续的消息订阅者从MQ消费消息,进行下游的本地事务操做。
  • 为了不消息重复消费,下游服务能够维护一个本地的“消息记录表”记录已经处理消费过的消息,每次处理消息前经过该表检查消息是否消费过。

图片

基于消息的分布式事务能够将分布式系统之间更有效的解耦,各个事务参与方之间的调用再也不是同步调用。

对MQ系统的要求较高,对业务实现也有必定的侵入性,要么提供事务消息状态查询接口,要么须要维护本地消息表。而且原则上只接受下游分支事务的成功,不接受事务的回滚,若是失败就要一直重试,适用于对最终一致性敏感度较低的业务场景,例如跨企业的系统间的调用,适用的场景有限。

总结

阅读了很多这方面的文章,在此基础上,总结一下分布式事务一致性的解决方案。分布式系统的事务一致性自己就是一个技术难题,目前没有一种很简单很完美的方案可以应对全部场景。分布式系统的一个难点就是由于“网络通讯的不可靠”,只能经过“确认机制”、“重试机制”、“补偿机制”等各方面来解决问题。在综合考虑可用性、性能、实现复杂度等各方面的状况上,比较好的选择是“异步消息确保最终一致性”,只是具体实现方式上有一些差别。

相关文章
相关标签/搜索