美文网首页
分布式事务

分布式事务

作者: 925781609 | 来源:发表于2020-04-11 19:37 被阅读0次

    事务与分布式事务

    事务是将一组操作作为一个不可分割的执行单元,组成事务的所有操作要么全部成功,事务提交;要么某一操作执行失败,整个事务的回滚。简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”的机制。事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolatio)和持久性(durability)等特点, 简称ACID。

    传统单机应用部署在一台机器上,事务的所有操作都在一个实例内,可以通过关系型数据库(如MySQL)提供事务支持。但是在分布式环境下,事务参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点之上,就需要分布式事务来保证这些操作要么一起成功,要么一起失败。

    提到分布式事务,就不得不说CAP理论:

    • C (一致性):对某个指定的客户端来说,读操作能返回最新的写操作的结果。
    • A (可用性):非故障的节点在合理的时间内返回合理的响应(不是错误或超时的响应)。
    • P (分区容错性):当出现网络分区后,系统能够继续工作。

    分布式系统中,网络无法100%可靠,分区其实是一个必然现象,如果我们选择了CA而放弃了P,那么当发生分区现象时,为了保证一致性,这个时候必须拒绝请求,但是A又不允许,所以分布式系统理论上不可能选择CA架构,只能选择CP或者AP架构。常用的Zookeeper就是保证CP, 而Eureka是保证AP。

    由于无法同时满足CAP,因此引出了BASE理论(Basically Available基本可用、Soft state软状态和 Eventually consistent 最终一致性 三个短语的缩写),是对CAP中AP的一个扩展。所以分布式事务都是追求最终一致性,利用重试、补偿等方式,保证事务要么提交、要么回滚。分布式算法有2PC(/Open XA)、TCC(Try-Confirm-Cancel)、本地消息表、MQ事务等,下面重点对几个常用的方案进行展开。

    1. TCC

    关于TCC(Try-Confirm-Cancel)的概念,最早是由Pat Helland于2007年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出的。目前开源方案有ByteTCCtcc-transactionseata,具体代码可查看官方示例。其思想是将业务分为try与confirm/cancel两阶段完成,下面以“账户A给账户B转账30元”为例进行说明,具体可参考下图:

    • Try 操作:资源的检查和预留。
      Try 操作要做的事情就是先检查 A 账户的余额是否充足,再冻结要扣款的 30 元(预留资源),此阶段不会发生真正地扣款。
    • Confirm 操作:执行真正业务的提交。
      Confirm 阶段做的事情就是发生真正地扣款,把 A 账户中已经冻结的 30 元钱扣掉。
    • Cancel 操作:预留资源的释放。
      如果下游服务有异常,扣款取消,Cancel 操作执行的任务是释放 Try 操作冻结的 30 元钱,使 A 账户回到初始状态。
    图片来源于《蚂蚁金服分布式事务开源以及实践》

    使用TCC方案需要注意:

    1. 并发控制
      用户在实现 TCC 时,应当考虑并发性问题,将锁的粒度降到最低,以最大限度提高分布式事务的并发性。
      以下还是以 A 账户扣款为例,“账户 A 上有 100 元,事务 T1 要扣除其中的 30 元,事务 T2 也要扣除 30 元,出现并发”。在一阶段 Try 操作中,分布式事务 T1 和分布式事务 T2 分别冻结资金的那一部分资金,相互之间无干扰。这样在分布式事务的二阶段,无论 T1 是提交还是回滚,都不会对 T2 产生影响,这样 T1 和 T2 可以在同一笔业务数据上并行执行。
    并发控制,图片来源于《蚂蚁金服分布式事务开源以及实践》
    1. 允许空回滚
      如下图所示,事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因为丢包而导致的网络超时。此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,而 Cancel 操作调用未出现超时。
      TCC 服务在未收到 Try 请求的情况下收到 Cancel 请求,这种场景被称为空回滚。空回滚在生产环境经常出现,用户在实现 TCC 服务时,应允许空回滚的执行,即收到空回滚时返回成功。
    空回滚出现场景,图片来源于《蚂蚁金服分布式事务开源以及实践》
    1. 防悬挂控制
      如下图所示,事务协调器在调用 TCC 服务的一阶段 Try 操作时,可能会出现因网络拥堵而导致的超时。此时事务管理器会触发二阶段回滚,调用 TCC 服务的 Cancel 操作,Cancel 调用未超时。在此之后,拥堵在网络上的一阶段 Try 数据包被 TCC 服务收到,出现二阶段 Cancel 请求比一阶段 Try 请求先执行的情况,此 TCC 服务在执行晚到的 Try 之后,将永远不会再收到二阶段的 Confirm 或者 Cancel,造成 TCC 服务悬挂。
      用户在实现 TCC 服务时,要允许空回滚,但是要拒绝执行空回滚之后 Try 请求,要避免出现悬挂。
    出现悬挂场景,图片来源于《蚂蚁金服分布式事务开源以及实践》
    1. 幂等控制
      无论是网络数据包重传,还是异常事务的补偿执行,都会导致 TCC 服务的 Try、Confirm 或者 Cancel 操作被重复执行;用户在实现 TCC 服务时,需要考虑幂等控制,即 Try、Confirm、Cancel 执行一次和执行多次的业务结果是一样的。
    幂等控制,图片来源于《蚂蚁金服分布式事务开源以及实践》

    : 更多地可以参考蚂蚁金服分布式事务开源以及实践, 本章节也主要是参考这篇文章。

    2. 本地消息表

    本地消息表这个方案最初是由Ebay提出的完整方案In partitioned databases, trading some consistency for availability can lead to dramatic improvements in scalability.。此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。

    图片来源于《再有人问你分布式事务,把这篇扔给他》

    对于本地消息表其核心思想是保证消费者至少发送一次消息(重试机制),消费者至多处理一次(幂等机制)。仍以“账户A给账户B转账30元”为例

    1. 上游服务在执行账户A扣款时,需要新增一个本地消息表,将“数据库中账户A的余额减去30元”和“保存发送给下游服务的消息(将B账户增加30元)到本地消息表”作为一个事务来执行,通过数据库的事务来实现;
    2. 定时查询本地消息表,将没有成功发送的消息发送到MQ上。
    • 如果已经发送成功,将对应的消息标为已发送;
    • 如果消息未成功发送,下次定时任务依旧会将该消息再次发送,直到发送成功;
    1. 下游服务在接收到消息后,将B账户余额增加30元,并告诉MQ该消息已经处理,否则MQ会再次重发消息直到消息被成功处理,因此下游服务要做幂等处理。

    MQ事务

    Rocket MQ4.3.0版本增加了对事务消息的支持,实际上是对本地消息表的封装,具体代码示例可以参考transaction-example,实现原理是两阶段提交,下面仍以“账户A给账户B转账30元”为例进行说明:

    1. 预提交
      上游服务发送个Half Message给MQ的Brock端,消息中携带“将B账户增加30元”的消息。
      所谓的半消息是指该消息会持久化在Rocket MQ的broker中,但不会被消费者消费
      当上游服务知道Half Message发送成功后,那么开始第2步执行本地事务。
      执行本地事务会有三种情况1.执行成功。2.执行失败。3,网络等原因导致没有响应
    2. Commit
      如果本地事务成功,那么上游服务向Brock服务器发送Commit信号, 这样下游服务就可以消费之前半消息。
    3. Rollback
      如果本地事务失败,那么上有服务向Brock服务器发送Rollback信号, 那么就会直接删除之前的半消息。
    4. 定时回查
      如果因为网络等原因迟迟没有返回失败还是成功,那么会执行RocketMQ的回调接口,来进行事务的回查。
      具体过程如下图所示, 其中下游服务在处理完消息之后,提交Ack信号给broker,broker不会再重发这个消息, 否则会不停地重试,因此下游服务也需要做幂等处理。
      图片来源于《RocketMQ 4.3 正式发布,支持分布式事务》

    总结

    本文主要阐述了TCC与本地消息表(MQ事务) 两种主要的分布式事务实现方式, 其实现原理都是对于分布式事务相关的两个服务或多个服务,通过重试机制,上游服务至少通知下游服务一次, 下游服务通过幂等处理,保证最多只处理一次。
    两者的主要区别在于:

    1. TCC
    • 优点: 实时性较高、服务下游如果因为资源不足(比如库存不足),服务上游会知道的,并且服务上游会立即失败
    • 缺点: 对代码的侵入较高,原来一次执行的,现在需要分两阶段执行
    1. 本地消息表
    • 优点: 实现简单
    • 缺点: 实时性较低,且上游服务很难知道下游服务的执行情况,一旦消息提交了,下游服务会不停地重试,直到能够处理完消息。但对于下游服务如果,因为库存不足而执行失败,理论上上游服务也不应该执行成功,因此这种情况并不适合。

    需要注意的是无论是TCC还是通过本地消息表来实现分布式事务,都需要有对账机制进行兜底。且在使用分布式事务之前,需要考虑清楚是否真的需要分布式事务,不要为了使用而使用。

    本文是站在巨人的肩膀上对分布式事务进行的总结,参考了包括并不限于以下文章,在此一并感谢!

    参考:

    1. 再有人问你分布式事务,把这篇扔给他
    2. 蚂蚁金服分布式事务开源以及实践
    3. 布式事务(3)—RocketMQ实现分布式事务原理

    相关文章

      网友评论

          本文标题:分布式事务

          本文链接:https://www.haomeiwen.com/subject/pdwvphtx.html