03.柔性事务

柔性事务

1.介绍

最终一致性事务模型并不要求参与事务的各个节点数据时刻保持一致,允许其存在中间状态,只要一段时间后,能够达到数据的最终一致状态即可,最终一致性事务模型是基于Base理论的

2.分类和方案

  • 通知型:可靠消息最终⼀致性解决⽅案,最⼤努⼒通知型解决⽅案
  • 补偿型:TCC最终一致性解决方案,Saga最终一致性解决方案

3.优缺点

4.TCC方案

介绍

TCC(Try-Confirm-Cancel)的概念来源于 Pat Helland 发表的一篇名为“Life beyond Distributed Transactions:an Apostate’s Opinion”的论文

TCC 提出了一种新的事务模型,基于业务层面的事务定义,锁粒度完全由业务自己控制,目的是解决复杂业务中,跨表跨库等大颗粒度资源锁定的问题

从本质上讲,TCC是⼀种应⽤层实现的⼆阶段提交协议

TCC 把事务运行过程分成 Try、Confirm / Cancel 两个阶段,每个阶段的逻辑由业务代码控制,避免了长事务,可以获取更高的性能

思想

TCC 模型认为对于业务系统中一个特定的业务逻辑,其对外提供服务时,必须接受一些不确定性,即对业务逻辑初步操作的调用仅是一个临时性操作,调用它的主业务服务保留了后续的取消权。如果主业务服务认为全局事务应该回滚,它会要求取消之前的临时性操作,这就对应从业务服务的取消操作。而当主业务服务认为全局事务应该提交时,它会放弃之前临时性操作的取消权,这对应从业务服务的确认操作。每一个初步操作,最终都会被确认或取消

原理简述

TCC是Try、Confirm、Cancel三个词语的缩写,TCC要求每个分支事务实现三个操作:预处理Try、确认Confirm、撤销Cancel。Try操作做业务检查及资源预留,Confirm做业务确认操作,Cancel实现一个与Try相反的操作即回滚操作。TM首先发起所有的分支事务的try操作,任何一个分支事务的try操作执行失败,TM将会发起所有分支事务的Cancel操作,若try操作全部成功,TM将会发起所有分支事务的Confirm操作,其中Confirm/Cancel操作若执行失败,TM会进行重试

主要角色

  1. 主业务服务:主业务服务为整个业务活动的发起方,服务的编排者,负责发起并完成整个业务活动
  2. 从业务服务:从业务服务是整个业务活动的参与方,负责提供TCC业务操作,实现初步操作(Try),确认操作(Confirm),取消操作(Cancel)三个接口,供主业务服务调用
  3. 业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护TCC全局事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的Confirm操作,在业务活动取消时调用所有从业务服务的Cancel操作

与其他方案的区别

TCC(Try-Confirm-Cancel)分布式事务模型相对于 XA 等传统模型,其特征在于它不依赖资源管理器(RM)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务

主流的TCC框架

框架名称Gitbub地址或官网地址
tcc-transactionhttps://github.com/changmingxie/tcc-transaction
Hmilyhttps://github.com/yu199195/hmily
ByteTCChttps://github.com/liuyangming/ByteTCC
EasyTransactionhttps://github.com/QNJR-GROUP/EasyTransaction
Seatahttps://seata.io/zh-cn/
LCN

适用场景

TCC方案主要适用于具有强隔离性、严格一致性的业务场景并且适用于执行时间比较短的业务中

工作流程

图示






2大阶段

  1. Try 阶段:调用 Try 接口,尝试执行业务,完成所有业务检查,预留业务资源
  2. Confirm 或 Cancel 阶段:两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试
  3. Confirm 操作:对业务系统做确认提交,确认执行业务操作,不做其他业务检查,只使用 Try 阶段预留的业务资源
  4. Cancel 操作:在业务执行错误,需要回滚的状态下执行业务取消,释放预留资源

如果2阶段失败怎么办

Try 阶段失败可以 Cancel,如果 Confirm 和 Cancel 阶段失败了怎么办?

TCC 中会添加事务日志,如果 Confirm 或者 Cancel 阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等

优缺点

优点
  • 在应⽤层实现具体逻辑,锁定资源的粒度变⼩,不会锁定所有资源,提升了系统的性能
  • TCC分布式事务解决⽅案由主业务发起整个事务,⽆论是主业务还是分⽀事务所在的业务,都能部署为集群模式,从⽽解决了XA规范的单点故障问题
  • 不需要像XA一样需要数据库的支持
缺点
  • 代码需要耦合到具体业务中,每个参与分布式事务的业务⽅法都要拆分成 Try、Confirm和Cancel三个阶段的⽅法,提⾼了开发成本
  • 还要注意三大问题:空回滚、幂等和悬挂

三大问题

  1. 空回滚
  2. 幂等
  3. 悬挂

空回滚问题

定义

空回滚指的是在一个分布式事务中,在没有调用参与方的 Try 方法的情况下,TM(业务活动管理器) 驱动二阶段回滚调用了参与方的 Cancel 方法

产生原因

出现空回滚的原因是⼀个分⽀事务所在的服务器宕机或者⽹络发⽣异常,此分⽀事务调⽤失败,此时并未执⾏此分⽀事务Try阶段的⽅法。当服务器或者⽹络恢复后,TCC分布式事务执⾏回滚操作,会调⽤分⽀事务Cancel阶段的⽅法,如果Cancel阶段的⽅法不能处理这种情况,就会出现空回滚问题

解决方案

识别是否出现了空回滚操作的⽅法是判断是否执⾏了Try阶段的⽅法。如果执⾏了Try阶段的⽅ 法,就没有空回滚,否则,就出现了空回滚

具体解决⽅案是在主业务发起全局事务时,⽣成全局事务记录,并为全局事务记录⽣成⼀个全局唯⼀的ID,叫作全局事务ID。这个全局事务ID会贯穿整个分布式事务的执⾏流程。再创建⼀张分⽀事务记录表,⽤于记录分⽀事务,将全局事务ID和分⽀事务ID保存到分⽀事务表中。执⾏Try阶段的⽅法时,会向分⽀事务记录表中插⼊⼀条记录,其中包含全局事务ID和分⽀事务ID,表示执⾏了Try阶段。当事务回滚执⾏Cancel阶段的⽅法时,⾸先读取分⽀事务表中的数据,如果存在Try阶段插⼊的数据,则执⾏正常操作回滚事务,否则为空回滚,不做任何操作

幂等问题

定义

幂等问题指的是 TCC 重复进行二阶段提交,因此 Confirm/Cancel 接口需要支持幂等处理,即不会产生资源重复提交或者重复释放

产生原因

由于服务器宕机、应⽤崩溃或者⽹络异常等原因,可能会出现⽅法调⽤超时的情况,为了保证⽅法的正常执⾏,往往会在TCC⽅案中加⼊超时重试机制。因为超时重试有可能导致数据不⼀致的问题,所以需要保证分⽀事务的执⾏以及TCC⽅案的Confirm阶段和Cancel阶段具备幂等性

解决方案

解决⽅案是在分⽀事务记录表中增加事务的执⾏状态,每次执⾏分⽀事务的Confirm阶段和Cancel阶段的⽅法时,都查询此事务的执⾏状态,以此判断事务的幂等性

二阶段 Confirm/Cancel 方法执行后,将状态改为 committed 或 rollbacked 状态。当重复调用二阶段 Confirm/Cancel 方法时,判断事务状态即可解决幂等问题

悬挂问题

定义

悬挂指的是二阶段 Cancel 方法比 一阶段 Try 方法优先执行,由于允许空回滚的原因,在执行完二阶段 Cancel 方法之后直接空回滚返回成功,此时全局事务已结束,但是由于 Try 方法随后执行,这就会造成一阶段 Try 方法预留的资源永远无法提交和释放了

出现原因

在TCC分布式事务中,通过RPC调⽤分⽀事务Try阶段的⽅法时,会先注册分⽀事务,再执⾏RPC调⽤。如果此时发⽣服务器宕机、应⽤崩溃或者⽹络异常等情况,RPC调⽤就会超时。如果RPC调⽤超时,事务管理器会通知对应的资源管理器回滚事务。可能资源管理器回滚完事务后,RPC请求达到了参与分⽀事务所在的业务⽅法,因为此时事务已经回滚,所以在Try阶段预留的资源就⽆法释放了。这种情况,就称为悬挂。总之,悬挂问题就是预留业务资源后,⽆法继续往下处理

在执行参与者 A 的一阶段 Try 方法时,出现网路拥堵,由于 Seata 全局事务 有超时限制,执行 Try 方法超时后,TM 决议全局回滚,回滚完成后如果此时 RPC 请求才 到达参与者 A,执行 Try 方法进行资源预留,从而造成悬挂

解决方案

解决⽅案的思路是如果执⾏了Confirm阶段或者Cancel阶段的⽅法,则Try阶段的⽅法就不能再 执⾏。具体⽅案是在执⾏Try阶段的⽅法时,判断分⽀记录表中是否已经存在同⼀全局事务下 Confirm阶段或者Cancel阶段的事务记录,如果存在,则不再执⾏Try阶段的⽅法

当执行二阶段 Cancel 方法时,如果发现 TCC 事务控制表有相关记录,说明二阶段 Cancel 方法优先一阶段 Try 方法执行,因此插入一条 status=4 状态的记录,当一阶段 Try 方法后面执行时,判断 status=4 ,则说明有二阶段 Cancel 已执行,并返回 false 以阻止一阶段 Try 方法执行成功

5.可靠消息最终一致性

介绍

可靠消息最终一致性方案是指当事务发起方执行完成本地事务后并发出一条消息,事务参与方(消息消费者)定能够接收消息并处理事务成功,此方案强调的是只要消息发给事务参与方最终事务要达到一致

此方案是利用消息中间件完成

执行流程

可靠消息服务实现方案

  1. 本地消息表
  2. MQ事务消息

问题

可靠消息最终一致性要解决一下几个问题才可以实现

  • 本地事务与消息发送的原子性
  • 消息发送的可靠性
  • 消息消费的的幂等

适用场景

消息数据能够独立存储,能够降低系统之前耦合度,并且业务对数据一致性的时间敏感度高的场景

实现方案对比

实现方案共性

  1. 事务消息都依赖MQ进行事务通知,所以都是异步的
  2. 事务消息在投递方都是存在重复投递的可能,需要有配套的机制去降低重复投递率,实现更友好的消息投递去重
  3. 事务消息的消费方,因为投递重复的无法避免,因此需要进行消费去重设计或者服务幂等设计

本地消息表

介绍

本地消息表最初由eBay提出来解决分布式事务的问题。是目前业界使用的比较多的方案之一,它的核心思想就是将分布式事务拆分成本地事务进行处理,此方案的核心是通过本地事务保证业务操作和消息的一致性,然后通过定时任务将消息发送至消息中间件,待确认消息发送给消费方成功再将消息删除

工作流程



消息发送方
  1. 需要有一个消息表,记录着消息状态相关信息
  2. 业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。直接利用本地事务,将业务数据和事务消息直接写入数据库
  3. 在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使用专门的投递工作线程进行事务消息投递到MQ,根据投递ACK去删除事务消息表记录
  4. 消息会发到消息消费方,如果发送失败,即进行重试
消息消费方
  1. 处理消息队列中的消息,完成自己的业务逻辑
  2. 如果本地事务处理成功,则表明已经处理成功了
  3. 如果本地事务处理失败,那么就会重试执行
  4. 如果是业务层面的失败,给消息生产方发送一个业务补偿消息,通知进行回滚等操作
优缺点
优点
  • 本地消息表建设成本比较低,实现了可靠消息的传递确保了分布式事务的最终一致性
  • 无需提供回查方法,进一步减少的业务的侵入
  • 在某些场景下,还可以进一步利用注解等形式进行解耦,有可能实现无业务代码侵入式的实现
缺点
  • 本地消息表与业务耦合在一起,难于做成通用性,不可独立伸缩
  • 本地消息表是基于数据库来做的,而数据库是要读写磁盘IO的,因此在高并发下是有性能瓶颈的

MQ事务消息

介绍

基于MQ的事务消息方案主要依靠MQ的半消息机制来实现投递消息和参与者自身本地事务的一致性保障。半消息机制实现原理其实借鉴的2PC的思路,是二阶段提交的广义拓展

半消息

暂不能投递的消息,发送方已经将消息成功发送到了MQ服务端,但是服务端未收到生产者对该消息的二次确认,此时该消息被标记成“暂不能投递”状态,处于该种状态下的消息即半消息

半消息回查

由于网络闪断、生产者应用重启等原因,导致某条事务消息的二次确认丢失,MQ服务端通过扫描发现某条消息长期处于“半消息”时,需要主动向消息生产者询问该消息的最终状态(Commit或是Rollback),该过程即消息回查

流程



  1. 事务发起方首先发送半消息到MQ
  2. MQ通知发送方消息发送成功
  3. 在发送半消息成功后执行本地事务
  4. 根据本地事务执行结果返回commit或者是rollback
  5. 如果消息是rollback,MQ将丢弃该消息不投递;如果是commit,MQ将会消息发送给消息订阅方
  6. 订阅方根据消息执行本地事务
  7. 订阅方执行本地事务成功后再从MQ中将该消息标记为已消费
  8. 如果执行本地事务过程中,执行端挂掉,或者超时,MQ服务器端将不停的询问producer来获取事务状态
  9. Consumer端的消费成功机制由MQ保证

6.最大努力通知

介绍

最大努力通知型(Best-effort delivery)是最简单的一种柔性事务,适用于一些最终一致性时间敏感度低的业务,且被动方处理结果 不影响主动方的处理结果

最大努力通知方案的目标,就是发起通知方通过一定的机制,最大努力将业务处理结果通知到接收方

本质是通过引入定期校验机制实现最终一致性,对业务的侵入性较低,适合于对最终一致性敏感度比较低、业务链路较短的场景

适用场景

最大努力通知事务主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,比如充值平台与运营商、支付对接、商户通知等等跨平台、跨企业的系统间业务交互场景

而异步确保型事务主要适用于内部系统的数据最终一致性保障,因为内部相对比较可控,比如订单和购物车、收货与清算、支付与结算等等场景

优缺点

分类

需要注意的问题

  • 消息重复通知:业务主动方发送消息通知后,业务被动方不一定能接收到消息,所以需要按照一定的阶梯型规则重复向业务被动方发送消息通知,这样就可能产生消息重复通知,业务被动方的方法就可能执行多次,所以消息被动方要保证幂等性
  • 消息通知丢失:业务主动方多次通知都没有通知到业务被动方,消息主动方可以提供查询消息的接口来让业务被动方主动查询消息

特点

  1. 不可靠消息:业务活动主动方,在完成业务处理之后,向业务活动的被动方发送消息,直到通知N次后不再通知,允许消息丢失(不可靠消息)
  2. 定期校对:业务活动的被动方,根据定时策略,向业务活动主动方查询(主动方提供查询接口),恢复丢失的业务消息,对业务数据进行兜底;防止业务被动方无法履行责任时进行业务回滚,确保数据最终一致性
  3. 业务主动方提供递增多挡位时间间隔(5min、10min、30min、1h、24h),用于失败重试调用业务被动方的接口;在通知N次之后就不再通知,报警+记日志+人工介入
  4. 业务被动方提供幂等的服务接口,防止通知重复消费

案例

业务背景

一个短信发送平台,背景是公司内部有多个业务都有发送短信的需求,如果每个业务独立实现短信发送功能,存在功能实现上的重复。因此专门做了一个短信平台项目,所有的业务方都接入这个短信平台,来实现发送短信的功能。简化后的架构如下所示:

流程
  1. 业务方将短信发送请求提交给短信平台
  2. 短信平台接收到要发送的短信,记录到数据库中,并标记其状态为”已接收"
  3. 短信平台调用外部短信发送供应商的接口,发送短信。外部供应商的接口也是异步将短信发送到用户手机上,因此这个接口调用后,立即返回,进入第4步
  4. 更新短信发送状态为"已发送"
  5. 短信发送供应商异步通知短信平台短信发送结果。而通知可能失败,因此最多只会通知N次
  6. 短信平台接收到短信发送结果后,更新短信发送状态,可能是成功,也可能失败(如手机欠费)。到底是成功还是失败并不重要,重要的是我们知道了这调短信发送的最终结果
  7. 如果最多只通知N次,如果都失败了的话,那么短信平台将不知道短信到底有没有成功发送。因此短信发送供应商需要提供一个查询接口,以方便短信平台驱动的去查询,进行定期校对
总结

在这个案例中,短信发送供应商通知短信平台短信发送结果的过程中,就是最典型的最大努力通知型方案,通知了N次就不再通知。通过提供一个短信结果查询接口,让短信平台可以进行定期的校对。而由于短信发送业务的时间敏感度并不高,比较适合采用这个方案

需要注意的是,短信结果查询接口很重要,必须要进行定期校对。因为后期要进行对账,这个项目一个月的短信发送总量在高峰期可以达到1亿条左右,即使一条短信只要5分钱,一个月就有500W

最大努力通知事务 VS 异步确保型事务

最大努力通知事务在我认知中,其实是基于异步确保型事务发展而来适用于外部对接的一种业务实现。他们主要有的是业务差别,如下:

  1. 从参与者来说:最大努力通知事务适用于跨平台、跨企业的系统间业务交互;异步确保型事务更适用于同网络体系的内部服务交付
  2. 从消息层面说:最大努力通知事务需要主动推送并提供多档次时间的重试机制来保证数据的通知;而异步确保型事务只需要消息消费者主动去消费
  3. 从数据层面说:最大努力通知事务还需额外的定期校验机制对数据进行兜底,保证数据的最终一致性;而异步确保型事务只需保证消息的可靠投递即可,自身无需对数据进行兜底处理

本地消息表方案实现

介绍

要实现最大努力通知,可以采用定期检查本地消息表的机制,事务消息也可以实现模式类似就不列举了

发送消息方
  1. 需要有一个消息表,记录着消息状态相关信息
  2. 业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。直接利用本地事务,将业务数据和事务消息直接写入数据库
  3. 在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使用专门的投递工作线程进行事务消息投递到MQ,根据投递ACK去删除事务消息表记录
  4. 消息会发到消息消费方,如果发送失败,即进行重试
  5. 生产方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的

7.Saga方案

历史

1987年普林斯顿大学的Hector Garcia-Molina和Kenneth Salem发表了一篇Paper Sagas,讲述的是如何处理long lived transaction(长活事务)。Saga是一个长活事务可被分解成可以交错运行的子事务集合。其中每个子事务都是一个保持数据库一致性的真实事务

论文地址:https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf

其他事务模型的缺点/Saga产生背景

在之前所有的分布式事务模型中所使用的微服务全部都可以根据开发者的需要进行修改,但是在一些特殊的环境下,如果要与一些封闭的系统(无法修改,同时更没有分布式事务),或者是老旧系统进行分布式业务对接时,那么其他的分布式事务模型将全部失效,所以有了Saga模型,比如在如下的例子中的总业务逻辑完成分布式事务

介绍

Saga模型是把一个分布式事务拆分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块(对应TCC中的Confirm和Cancel),当Saga事务中任意一个本地事务出错时,可以通过调用相关的补偿方法恢复之前的事务,达到事务最终一致性

这样的SAGA事务模型,是牺牲了一定的隔离性和一致性的,但是提高了long-running事务的可用性

Saga可以用来解决长事务和异构系统事务统一处理

Saga的回滚和提交靠我们编码并且在流程图中指定提交或回滚时要执行的方法

阶段

  • 一阶段:直接提交本地事务
  • 二阶段成功:啥都不做
  • 二阶段失败:编写补偿业务来回滚

使用条件

  • Saga只允许两个层次的嵌套,顶级的Saga和简单子事务
  • 在外层,全原子性不能得到满足。也就是说,sagas可能会看到其他sagas的部分结果
  • 每个子事务应该是独立的原子行为
  • 在我们的业务场景下,各个业务环境(如:航班预订、租车、酒店预订和付款)是自然独立的行为,而且每个事务都可以用对应服务的数据库保证原子操作

对于ACID的保证

  • 原子性(Atomicity):正常情况下保证
  • 一致性(Consistency):在某个时间点,会出现A库和B库的数据违反一致性要求的情况,但是最终是一致的
  • 隔离性(Isolation):在某个时间点,A事务能够读到B事务部分提交的结果
  • 持久性(Durability):和本地事务一样,只要commit则数据被持久

使用流程

  • 画流程图
  • 把流程图变成JSON语法
  • 使用Seata引擎读取和解析JSON语法执行对应的业务逻辑和反向补偿

三大问题

TCC的三大问题Saga也有

优缺点

两种恢复策略

向后恢复(Backward Recovery)

撤销掉之前所有成功子事务。如果任意本地子事务失败,则补偿已完成的事务。如异常情况的执行顺序T1,T2,T3,…Ti,Ci,…C3,C2,C1

向前恢复(Forward Recovery)

即重试失败的事务,适用于必须要成功的场景,该情况下不需要Ci。执行顺序:T1,T2,…,Tj(失败),Tj(重试),…,Ti

显然,向前恢复没有必要提供补偿事务,如果你的业务中,子事务(最终)总会成功,或补偿事务难以定义或不可能,向前恢复更符合你的需求

理论上补偿事务永不失败,然而,在分布式世界中,服务器可能会宕机,网络可能会失败,甚至数据中心也可能会停电。在这种情况下我们能做些什么? 最后的手段是提供回退措施,比如人工干预

  • 15
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值