资讯详情

分布式事务七种解决方案,最后一种经典了!

随着业务的快速发展和业务的复杂性,几乎每个公司的系统都将从单一转向分布式,特别是转向微服务架构。分布式事务的问题是不可避免的。本文总结了分布式事务最经典的解决方案,并与您分享。

在解释具体计划之前,让我们先了解分布式公司涉及的基本理论知识。

以转账为例,A需要转100元给B,然后需要给A余额-100元,给B余额 100元,保证整个转账,A-100和B 100同时成功或失败。看看如何在各种场景中解决这个问题。

事务

将多个句子作为一个整体操作功能称为数据库事务。数据库事务可以确保事务范围内的所有操作都能成功或失败。

事务具有 4 个性:原子性、一致性、隔离性和持久性。这四个属性通常被称为 ACID 特性。

  • Atomicity(原子):一个事务中的所有操作要么完成,要么不完成,不会在中间链接结束。如果事务在执行过程中出现错误,它将恢复到事务开始前的状态,就像事务从未执行过一样。

  • Consistency(一致性):数据库的完整性不会在事务开始前后被破坏。完整性,包括外键约束和应用定义,不会被破坏。

  • Isolation(隔离性):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。

  • Durability(持久性):事务处理后,对数据的修改是永久的,即使系统故障也不会丢失。

分布式事务

银行跨行转账业务是典型的分布式业务场景,假设A需要跨行转账B,因此,涉及两家银行的数据无法通过数据库的本地事务保证转账ACID,只能通过分布式事务来解决。

分布式事务是指位于分布式系统不同节点上的发起人、资源和资源管理器和事务协调员。在上述转账业务中,用户A-100操作和用户B 100操作不在同一节点上。本质上,分布式事务是为了确保数据操作在分布式场景中的正确执行。

在分布式环境下,为了满足可用性、性能和降级服务的需要,降低一致性和隔离性的要求,一方面遵循 BASE 理论(BASE相关理论,涉及内容非常多,感兴趣的同学,可以参考BASE理论):

基本业务可用性(Basic Availability)柔性状态(Soft state)最终一致性(Eventual consistency)同样,分布式事务也部分遵循 ACID 规范:

原子:严格遵循一致性:严格遵循事务完成后的一致性;适当放宽事务中的一致性隔离:平行事务间不受影响;事务中间结果的可见性允许安全放宽耐久性:严格遵循

两阶段提交/两阶段提交/XA

XA是由X/Open组织提出的分布式事务规范,XA规范主要定义(全局)事务管理器(TM)和(局部)资源管理器(RM)接口。本地数据库,如mysql在XA中扮演的是RM角色

XA分为两个阶段:

第一阶段(prepare):也就是所有的参与者RM准备执行事务并定所需资源。ready时,向TM报告准备就绪。第二阶段 (commit/rollback):事务经理(TM)确认所有参与者(RM)都ready之后,将它发送给所有参与者commit命令。目前主流数据库基本支持XA事务,包括mysql、oracle、sqlserver、postgre

XA 一个或多个资源管理器的事务(RM)、一个事务管理器(TM)应用程序(ApplicationProgram)组成。

以上转账为例,成功完成XA事务时序图如下:

35b866cfc4284a68903610140abb8563.png

如果有任何参与者prepare失败,那么TM将通知所有完成prepare参与者回滚。

XA事务的特点是:

  • 易于理解,易于开发

  • 长期锁定资源,发度低

如果读者想要进一步研究XA,go语言可参考DTM,java语言可参考seata

SAGA

Saga是这篇数据库论文saga提到的计划。其核心思想是将长事务分为多个本地短事务,由Saga如果事务协调器协调正常,则正常完成。如果某一步骤失败,则按相反顺序调用补偿操作。

以上转账为例,成功完成SAGA事务时序图如下:

SAGA事务特点:

  • 并发度高,不需要像XA事务长期锁定资源

  • 正常操作和补偿操作需要定义,开发量比XA大

  • 一致性弱,对于转账,A用户可能已经扣除,最终转账失败

论文里面的SAGA内容较多,包括两种恢复策略,包括分支事务并发执行,我们这里的讨论,仅包括最简单的SAGA

SAGA适用场景多,长事务适用,对中间结果不敏感的业务场景适用

如果读者想要进一步研究SAGA,go语言可参考DTM,java语言可参考seata

TCC

关于 TCC(Try-Confirm-Cancel)概念,最早是由 Pat Helland 于 2007 年发表的一篇文章叫《Life beyond Distributed Transactions:an Apostate’s Opinion》论文提出。

  • Try 阶段:尝试实施,完成所有业务检查(一致性), 必须预留业务资源(准隔离)

  • Confirm 阶段:确认业务实施,不进行任何业务检查,只使用 Try 阶段预留的业务资源,Confirm 操作要求有幂等设计,Confirm 失败后需要重试。

  • Cancel 阶段:取消执行,释放 Try 阶段预留的业务资源。Cancel 阶段异常和 Confirm 阶段异常处理方案基本一致,要求满足功率等设计。

以上转账为例,通常是Try内部冻结金额,但不扣除,Confirm里面扣款,Cancel解冻金额,成功完成TCC事务时序图如下:

  • 并发度高,无长期资源锁定。

  • 开发量大,需要提供Try/Confirm/Cancel接口。

  • 一致性好,不会发生SAGA扣款最后转账失败的情况

  • TCC适用于订单业务,对中间状态有约束力

如果读者想要进一步研究TCC,go语言可参考DTM,java语言可参考seata

本地消息表

本地消息表的计划最初是 ebay 架构师 Dan Pritchett 在 2008 年发表给 ACM 文章。设计的核心是通过信息异步确保需要分布式处理的任务的执行。p

大致流程如下:

写本地消息和业务操作放在一个事务里,保证了业务和发消息的原子性,要么他们全都成功,要么全都失败。

  • 扣减余额事务 失败时,事务直接回滚,无后续步骤

  • 轮序生产消息失败, 增加余额事务失败都会进行重试

  • 长事务仅需要分拆成多个任务,使用简单

  • 生产者需要额外的创建消息表

  • 每个本地消息表都需要进行轮询

  • 消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作

适用于可异步执行的业务,且后续操作无需回滚的业务

◆  

在上述的本地消息表方案中,生产者需要额外创建消息表,还需要对本地消息表进行轮询,业务负担较重。阿里开源的RocketMQ 4.3之后的版本正式支持事务消息,该事务消息本质上是把本地消息表放到RocketMQ上,解决生产端的消息发送与本地事务执行的原子性问题。

事务消息发送及提交:

  • 发送消息(half消息)

  • 服务端存储消息,并响应消息的写入结果

  • 根据发送结果执行本地事务(如果写入失败,此时half消息对业务不可见,本地逻辑不执行)

  • 根据本地事务状态执行Commit或者Rollback(Commit操作发布消息,消息对消费者可见)

正常发送的流程图如下:

对没有Commit/Rollback的事务消息(pending状态的消息),从服务端发起一次“回查”Producer收到回查消息,返回消息对应的本地事务的状态,为Commit或者Rollback事务消息方案与本地消息表机制非常类似,区别主要在于原先相关的本地表操作替换成了一个反查接口

  • 长事务仅需要分拆成多个任务,并提供一个反查接口,使用简单

  • 消费者的逻辑如果无法通过重试成功,那么还需要更多的机制,来回滚操作

适用于可异步执行的业务,且后续操作无需回滚的业务

如果读者想要进一步研究事务消息,可参考rocketmq,为了方便大家学习事务消息,DTM也提供了简单实现

发起通知方通过一定的机制最大努力将业务处理结果通知到接收方。具体包括:

有一定的消息重复通知机制。因为接收通知方可能没有接收到通知,此时要有一定的机制对消息重复通知。消息校对机制。如果尽最大努力也没有通知到接收方,或者接收方消费消息后要再次消费,此时可由接收方主动向通知方查询消息信息来满足需求。前面介绍的的本地消息表和事务消息都属于可靠消息,与这里介绍的最大努力通知有什么不同?

可靠消息一致性,发起通知方需要保证将消息发出去,并且将消息发到接收通知方,消息的可靠性关键由发起通知方来保证。

最大努力通知,发起通知方尽最大的努力将业务处理结果通知为接收通知方,但是可能消息接收不到,此时需要接收通知方主动调用发起通知方的接口查询业务处理结果,通知的可靠性关键在接收通知方。

  • 提供接口,让接受通知放能够通过接口查询业务处理结果

  • 消息队列ACK机制,消息队列按照间隔1min、5min、10min、30min、1h、2h、5h、10h的方式,逐步拉大通知间隔 ,直到达到通知要求的时间窗口上限。之后不再通知

最大努力通知适用于业务通知类型,例如微信交易的结果,就是通过最大努力通知方式通知各个商户,既有回调通知,也有交易查询接口

AT事务模式

这是阿里开源项目seata中的一种事务模式,在蚂蚁金服也被称为FMT。优点是该事务模式使用方式,类似XA模式,业务无需编写各类补偿操作,回滚由框架自动完成,缺点也类似AT,存在较长时间的锁,不满足高并发的场景。有兴趣的同学可以参考seata-AT

在分布式事务的各个环节都有可能出现网络以及业务故障等问题,这些问题需要分布式事务的业务方做到防空回滚,幂等,防悬挂三个特性,下面以TCC事务说明这些异常情况:

空回滚:

  在没有调用 TCC 资源 Try 方法的情况下,调用了二阶段的 Cancel 方法,Cancel 方法需要识别出这是一个空回滚,然后直接返回成功。

  出现原因是当一个分支事务所在服务宕机或网络异常,分支事务调用记录为失败,这个时候其实是没有执行Try阶段,当故障恢复后,分布式事务进行回滚则会调用二阶段的Cancel方法,从而形成空回滚。

幂等:

  由于任何一个请求都可能出现网络异常,出现重复请求,所以所有的分布式事务分支,都需要保证幂等性

悬挂:

  悬挂就是对于一个分布式事务,其二阶段 Cancel 接口比 Try 接口先执行。

  出现原因是在 RPC 调用分支事务try时,先注册分支事务,再执行RPC调用,如果此时 RPC 调用的网络发生拥堵,RPC 超时以后,TM就会通知RM回滚该分布式事务,可能回滚完成后,RPC 请求才到达参与者真正执行。

下面看一个网络异常的时序图,更好的理解上述几种问题

业务处理请求4的时候,Cancel在Try之前执行,需要处理空回滚业务处理请求6的时候,Cancel重复执行,需要幂等业务处理请求8的时候,Try在Cancel后执行,需要处理悬挂

面对上述复杂的网络异常情况,目前看到各家建议的方案都是业务方通过唯一键,去查询相关联的操作是否已完成,如果已完成则直接返回成功。相关的判断逻辑较复杂,易出错,业务负担重。

在项目DTM中,出现了一种子事务屏障技术,使用该技术,能够达到这个效果,看示意图:

所有这些请求,到了子事务屏障后:不正常的请求,会被过滤;正常请求,通过屏障。开发者使用子事务屏障之后,前面所说的各种异常全部被妥善处理,业务开发人员只需要关注实际的业务逻辑,负担大大降低。子事务屏障提供了方法ThroughBarrierCall,方法的原型为:

func ThroughBarrierCall(db *sql.DB, transInfo *TransInfo, busiCall BusiFunc)

业务开发人员,在busiCall里面编写自己的相关逻辑,调用该函数。ThroughBarrierCall保证,在空回滚、悬挂等场景下,busiCall不会被调用;在业务被重复调用时,有幂等控制,保证只被提交一次。

子事务屏障会管理TCC、SAGA、XA、事务消息等,也可以扩展到其他领域

子事务屏障技术的原理是,在本地数据库,建立分支事务状态表sub_trans_barrier,唯一键为全局事务id-子事务id-子事务分支名称(try|confirm|cancel)

  • 开启事务

  • 如果是Try分支,则那么insert ignore插入gid-branchid-try,如果成功插入,则调用屏障内逻辑

  • 如果是Confirm分支,那么insert ignore插入gid-branchid-confirm,如果成功插入,则调用屏障内逻辑

  • 如果是Cancel分支,那么insert ignore插入gid-branchid-try,再插入gid-branchid-cancel,如果try未插入并且cancel插入成功,则调用屏障内逻辑

  • 屏障内逻辑返回成功,提交事务,返回成功

  • 屏障内逻辑返回错误,回滚事务,返回错误

在此机制下,解决了网络异常相关的问题

  • 空补偿控制--如果Try没有执行,直接执行了Cancel,那么Cancel插入gid-branchid-try会成功,不走屏障内的逻辑,保证了空补偿控制

  • 幂等控制--任何一个分支都无法重复插入唯一键,保证了不会重复执行

  • 防悬挂控制--Try在Cancel之后执行,那么插入的gid-branchid-try不成功,就不执行,保证了防悬挂控制

对于SAGA事务,也是类似的机制。

子事务屏障技术,为DTM首创,它的意义在于设计简单易实现的算法,提供了简单易用的接口,在首创,它的意义在于设计简单易实现的算法,提供了简单易用的接口,在这两项的帮助下,开发人员彻底的从网络异常的处理中解放出来。

该技术目前需要搭配DTM事务管理器,目前SDK已经提供给go语言的开发者。其他语言的sdk正在规划中。对于其他的分布式事务框架,只要提供了合适的分布式事务信息,能够按照上述原理,快速实现该技术。

总结

本文介绍了分布式事务的一些基础理论,并对常用的分布式事务方案进行了讲解,在文章的后半部分还给出了事务异常的原因、分类以及优雅的解决方案。

往期推荐

2 w+深入详解 Kafka,从源码到架构全部讲透

Nginx 高可用秘籍

高性能缓存Nginx+Redis,就是这么能打!

Spring 事务失效的 8 大场景,看看你都遇到过几个?

项目经理:Elasticsearch举例说明分布式系统架构怎样设计?

】获取精选干货视频教程

】加入疑难问题攻坚交流群

】获取内存溢出问题分析详细文档教程

】获取用java写一个能赚钱的微信机器人

】获取程序员副业攻略一份

戳这儿

标签: 集成电路mc10h210mg

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台