本篇博客参阅:余春龙的《架构规划2.0》

在可落地的分布式事务处理方案这篇博客中,我介绍了四种依据MQ的分布式事务处理方案,这四种分布式事务处理方案是“可落地”的,并非“空中楼阁”“虚无缥缈”的,本篇博客,我将继续介绍可落地的分布式事务处理方案,并且不需求结构支撑,不过在此之前,先要简单的介绍下一些已经被说烂的概念、理论知识。

两阶段提交 2PC

在MySQL InnoDB中,为了确保Bin Log和Redo Log的一致性,便选用了两阶段提交;ZooKeeper、ETCD集群为了确保数据一致性,也选用了两阶段提交,RocketMQ的事务音讯也选用了两阶段提交,可见两阶段提交是分布式事务中比较常用的处理方案。

MySQL InnoDB中的两阶段提交

咱们看下在MySQL InnoDB是怎样选用两阶段提交是确保Bin Log和Redo Log的一致性的:

可落地的、不基于框架的分布式事务解决方案

  1. 收到客户端的数据操作恳求后,MySQL InnoDB会在内存中完成数据更新;
  2. 写Redo Log,此刻Redo Log的状况为Prepare;
  3. 写Bin Log,此刻Redo Log的状况不变,仍是为Prepare;
  4. 提交事务,此刻Redo Log的状况为Commit;

因为本篇博客,重点不在于MySQL,所以就不介绍为什么需求Bin Log和Redo Log两种日志了,只需求知道只有这样才能够确保数据的一致性,并且能够看出Redo Log有两种状况:Prepare、Commit,这两种状况便是两阶段提交的核心。有了这个基础,咱们就能够来看看两阶段提交的概念了。

两阶段提交 2PC

可落地的、不基于框架的分布式事务解决方案
两阶段提交把一个操作分为了Prepare、Commit/Rollback两个阶段:

  1. 事务和谐器向多个本地资源管理器建议Prepare恳求;
  2. 本地资源管理器收到事务和谐器的Prepare恳求,会履行本身的操作,可是不会真实收效,随后会呼应事务和谐器;
  3. 假如事务和谐器在必守时刻内,收到一切本地资源管理器的Prepare呼应,会向本地资源管理器建议Commit恳求,假如事务和谐器在必守时刻内,没有收到一切本地资源管理器的Prepare呼应,会向本地资源管理器建议Rollback恳求;
  4. 假如本地资源管理器收到的是事务和谐器的Commit恳求,会提交本身的操作,使其真实收效;假如本地资源管理器收到的是事务和谐器的Rollback恳求,会回滚本身的操作;
  5. 不论本地资源管理器履行的是Commit操作,仍是Rollback操作,都会呼应事务和谐器。

事务和谐器也被称之为“TC”,本地资源管理器也被称之为“RM”。

关于两阶段提交,网上有许多说法都不太相同,因为这仅仅是一个思想,是一个理论,不同的人有不同的了解,真实做起来,也会有不同的完成,比方【本地资源管理器收到事务和谐器的Prepare恳求,会履行本身的操作】,【本地资源管理器收到事务和谐器的Commit恳求后,会提交本身的操作】这两个操作:

  • 本地资源管理器收到事务和谐器的Prepare恳求,能够是敞开一个本地事务,履行本身的事务逻辑,可是不提交事务,当本地资源管理器收到事务和谐器的Commit恳求后,才会提交事务;当本地资源管理器收到事务和谐器的Rollback恳求后,会回滚事务。这种是传统的两阶段提交完成方法,事务或许比较长,也会长时刻占用数据库资源,功能比较低下,可是牢靠性比较高。
  • 本地资源管理器收到事务和谐器的Prepare恳求,能够将事务逻辑的反操作记载下来(相当于Undo Log),随后开释数据库资源,当资源管理器收到事务和谐器的Commit恳求后,这条反操作记载就无效了(因为操作成功了,不会使用这反操作记载进行数据的康复了);当本地资源管理器收到事务和谐器的Rollback恳求,能够使用反操作记载来进行数据的回滚。假如是这种完成,不会长时刻占用数据库资源,功能或许会有所提高,牢靠性会相对差一些(这便是Seata AT模式的完成)。

尽管两阶段提交有不同的完成,可是整体上必定分为Prepare、Commit/Rollback这两个阶段。

传统的两阶段提交存鄙人以下几个问题:

  1. 功能低下:传统的两阶段提交会长时刻占用数据库资源,直到Commit/Rollback才开释;
  2. 事务和谐器毛病:假如事务和谐器挂了,怎样办?特别是在第二阶段,本地资源管理器在等待事务和谐器的Commit/Rollback恳求,此刻事务和谐器挂了,那本地资源管理器就会处于“悬而未决”的“懵逼”状况;
  3. 某个本地资源管理器Commit/Rollback失利,怎样办?
  4. 使用场景约束:一般来说,传统的两阶段提交是依据事务的,所以需求数据库支撑事务,假如事务逻辑中有对Redis、Mongo、ES的操作,传统的两阶段提交就不太适用了。

讲道理,在介绍完两阶段提交后,应该要介绍三阶段提交了,可是两阶段提交存在的问题,在三阶段提交中,并没有改进多少(也有或许是我没有领会出来),所以就不介绍三阶段提交了。

TCC

TCC是付出宝提出的,是两阶段提交的变种,TCC是三个单词的缩写:Try、Confirm、Cancel。Confirm对应两阶段提交中的Commit,Cancel对应两阶段提交中的Rollback。

可落地的、不基于框架的分布式事务解决方案

可落地的、不基于框架的分布式事务解决方案

如图所示:

  1. 调用方会向一切服务供给方建议Try恳求,服务供给方依据事务要求,做一系列操作:比方查看数据、确定资源等,然后会呼应调用方;
  2. 假如在必守时刻内,调用方收到了一切服务供给方的Try呼应,会向一切服务供给方建议Confirm恳求,服务供给方收到Confirm恳求后,履行本身的事务逻辑。

可是“天佑不测风云,人有祸福旦夕”,不或许总是一往无前,假如在必守时刻内,调用方没有收到服务供给方的Try呼应或许收到了服务供给方的“Try-No”呼应,调用方会向一切服务供给方建议Cancel恳求:

可落地的、不基于框架的分布式事务解决方案

以前我一向不太理解,假如在必守时刻内,调用方没有收到服务供给方的Try呼应或许收到了服务供给方的“Try-No”呼应,为什么调用方还要向一切服务供给方建议Cancel恳求,直接不论不就能够了,横竖服务供给方还没有真实履行事务逻辑?因为在Try阶段,服务供给方或许做了确定资源的操作,所以需求告诉服务供给方,让其开释资源。

TCC在必定程度上处理2PC的缺点:

  1. 假如某个服务供给方Confirm、Cancel失利了,通过TCC结构能够不断的重试,直到成功;
  2. TCC并非是依据数据库事务的,所以没有使用场景的约束;
  3. TCC并非是依据数据库事务的,不会长时刻占用数据库资源。

那TCC是不是完美的处理方案呢?也不是,TCC在必定程度上处理2PC的缺点的一起,又新增了缺点:

  1. 服务供给方需求供给Try、Confirm、Cancel三个接口,代码侵入性比较强,比较繁琐;
  2. 因为没有数据库事务的概念,所以难以确保数据的一致性。

不论是选用2PC,仍是TCC,简直都会引入结构,一旦引入结构,复杂度、学习本钱、保护本钱就成倍上升了,那有没有不需求结构,就能够处理分布式事务问题的方案呢?

依据事情状况表的查看+过后补偿

可落地的、不基于框架的分布式事务解决方案

调用方依赖多个服务,怎样做到一致性呢?能够新建一张事情状况表,核心字段如下:

  • state:事务状况(履行中、履行成功、履行失利)
  • content:事务内容
  1. 调用方先往事情状况表刺进一条数据,state为“履行中”;
  2. 调用方调用各个接口;
  3. 假如调用一切正常,万事大吉,修正state为“履行成功”;
  4. 假如其中一个接口出现反常,修正state为“履行失利”;
  5. 通过守时使命的方法,扫描事情状况表,拿到需求重试(state为“履行中”/“履行失利”)的事务,调用接口,进行重试,重试成功,修正state为“履行成功”,重试失利,state不变。

你或许会问,假如调用接口正常,修正state失利,怎样办?大不了守时使命重试一次,可是需求事先和服务供给方沟通好,确保幂等性。

后台对账

  1. 异步音讯对账,这个方案在可落地的分布式事务处理方案这篇博客中,已经介绍过了;
  2. 全量对账:守时使命比照多个库的全量数据;
  3. 增量对账:依据更新时刻,比照多个库的增量数据。

还有一种比较“隐性”的场景,比方订单从“付出完成”到“商家承认”,一般不超过24小时,那就意味着,假如超过了24小时,或许就触发了反常场景,守时使命能够针对这种数据进行处理。

前台对账

举个比方,一般来说电商网站,热门产品的库存数据在数据库中有一份,在Redis也有一份,为了功能,产品详情中展示的库存,或许是Redis中的,到了扣减库存环节,既要扣除数据库中的库存,又要扣除Redis中的库存,数据库中的库存才是最牢靠的,所以在履行扣减库存的操作的时候,会比照Redis中的库存和数据库中的库存,假如不一致,就用数据库中的库存掩盖Redis中的库存(当然也能够不论三十二十一,直接用数据库中的库存福掩盖Redis中的库存)。这种方案比较合适轻量级的事务操作,比方查库存、修正库存,都是很快、很轻的操作。

退让方案:依据事务特性的弱一致性+依据事情状况表的查看+过后补偿

  • TCC是一个同步的方案,有两个阶段,功能比较差;
  • 依据事情状况表的查看+过后补偿,写事情状况表是一个同步的操作,并且需求两次操作事情状况表,功能比较差,过后补偿是一个过后的操作,及时性比较差;
  • 后台对账是一个过后的操作,及时性比较差。

假如既要让体系之间最大限度的确保一致性,功能又不能太受影响,应该怎样办?

举个创立订单的比方,创立订单分为两步:创立订单、扣减库存。

一般来说,电商是不允许超卖产生的,因为超卖导致发不了货,会影响渠道诺言,用户体验,渠道或许还需求进行赔偿,所以甘愿多扣减库存,也不能少扣减库存,咱们就能够使用这个特性,来规划咱们的弱一致性方案:先扣减库存,再创立订单:

  • 扣减库存成功,创立订单成功,没有任何问题;
  • 扣减库存成功,创立订单失利,多扣减了库存;
  • 扣减库存失利,不再创立订单。

可是库存多扣减了,怎样进行补偿呢?

一般来说,库存扣减会生成库存扣减记载,库存扣减记载有状况、时刻两个字段,扣减库存后,库存扣减记载的状况为“占用中”,付出完成后,库存扣减记载的状况为“开释”。守时使命能够扫描长时刻处于“占用中”的库存扣减记载,进行库存的回收,一起还能够取消订单。

退让方案:重试+报警+人工补偿

调用方调用接口失利,重试必定的次数,假如最终仍是失利的,就发送一条告诉,人工干预。尽管这种方案很“丑恶”,可是十分有用,毕竟调用接口失利,是十分少见的。

本篇博客到这里就完毕了。

本文正在参与「金石方案 . 分割6万现金大奖」