分布式事务的基本理论及解决方案

分布式事务的来源

单体架构


问题:操作不一定成功,也可能失败。这个问题对单体节点内部来说,可以使用『本地事务』来控制。所有操作都成功则提交事务;有任何一个操作失败则回滚事务。数据即使不能进入下一个正确状态,也会回到上一个正确状态。

        但是放在分布式系统中,单一节点内部的操作有可能是一个总体操作的组成部分。如果总体操作中有的部分成功了,有的部分失败了,这仍然会造成数据的不一致。

        所以从这里我们能看到分布式系统的第一个不确定因素:整个分布式系统中单一节点的操作有可能失败。而这个情况是无法彻底消除的。例如:库存是 3 的时候,下单数量是 5,下单就是不能成功,因为库存不能减少到负数。

分布式系统节点之间网络通信


问题:网络通信有丢失数据的可能。哪怕只有 0.00001% 的风险,也不能说是 100% 的保障。这个不确定性因素也是无法彻底消除的。

结论
导致分布式事务的最基本的因素有两个:

• 单个节点内部操作失败:导致我们从整个系统的视角来看数据进入不一致状态。

• 网络传输失败:导致我们无法确认某个节点的操作是否得到了执行。

而最关键的是:从微观上来看,造成分布式事务问题的这两个基本因素无法彻底消除。


所以我们解决分布式事务的问题要有这样一个意识:

• 微观上来看:操作没有办法保证都成功。

• 宏观上来看:即使局部操作失败,整个系统仍然能够正常运行。

• 从程度上来看:分布式事务的问题不是非黑即白的不是成功就是失败,而是有一个程度问题。我们需要根据项目的实际需求,在性能和数据一致性之间取得平衡。

什么是事务

事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元,组成事务的所有操作只有在所有操作均能正常执行的情况下方能提交,只要其中任一操作执行失败,都将导致整个事务的回滚。

不知道你是否遇到过这样的情况,去小卖铺买东西,付了钱,但是店主因为处理了一些其他事,居然忘记你付了钱,又叫你重新付。

又或者在网上购物明明已经扣款,但是却告诉我没有发生交易。这一系列情况都是因为没有事务导致的。这说明了事务在生活中的一些重要性。

有了事务,你去小卖铺买东西,那就是一手交钱一手交货。有了事务,你去网上购物,扣款即产生订单交易。

简单地说,事务提供一种“要么什么都不做,要么做全套(All or Nothing)”机制。

事务特性(ACID)

事务满足ACID特性:

• A:原子性(Atomicity)

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。

事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

就像你买东西要么交钱收货一起都执行,要么发不出货,就退钱。

• C:一致性(Consistency)

事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。

如果事务成功地完成,那么系统中所有变化将正确地应用,系统处于有效状态。

如果在事务中出现错误,那么系统中的所有变化将自动地回滚,系统返回到原始状态。

• I:隔离性(Isolation)

指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。

由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。

打个比方,你买东西这个事情,是不影响其他人的。

• D:持久性(Durability)

指的是只要事务成功结束,它对数据库所做的更新就必须保存下来。即使发生系统崩溃,重新启动数据库系统后,数据库还能恢复到事务成功结束时的状态。

打个比方,你买东西的时候需要记录在账本上,即使老板忘记了那也有据可查。

本地事务


• 什么是本地事务(Local Transaction)?

本地事务也称为数据库事务或传统事务(相对于分布式事务而言,执行的多条sql语句在同一个会话连接中)。它的执行模式就是常见的:

1. begin
2. insert/delete/update
3. insert/delete/update
4. ...
5. commit/rollback


• 本地事务有这么几个特征:

• -一次事务只连接一个支持事务的数据库(一般来说都是关系型数据库)

• 事务的执行结果保证ACID

• 会用到数据库锁

• 起初,事务仅限于对单一数据库资源的访问控制,架构服务化以后,事务的概念延伸到了服务中。倘若将一个单一的服务操作作为一个事务,那么整个服务操作只能涉及一个单一的数据库资源,这类基于单个服务单一数据库资源访问的事务,被称为本地事务(Local Transaction)。


什么是分布式事务


分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于分布式系统的不同节点上,且属于不同的应用。

指一次大的操作由不同的小操作组成的,这些小的操作分布在不同的服务器上,分布式事务需要保证这些小操作要么全部成功,要么全部失败。

本质上来说,分布式事务就是为了保证不同数据库的数据一致性。


上图是一个电商支付流程简化图,整个平台按照微服务进行拆分,涉及到服务模块有订单、商品、用户等模块。用户从浏览器选择好商品后进行支付下单操作,支付下单在订单模块生成。当订单成功生成后就需要分别调用商品模块进行扣减商品库存以及用户模块给用户增加积分等操作。这种架构下的事务就属于分布式事务了,要么全部业务模块都成功执行并提交数据库。只要其中一个微服务出现异常整个调用链路涉及到的操作都需要进行回滚,保障事务的一致性。

分布式事务场景


• 电商系统中的下单扣库存

电商系统中,订单系统*_和_*库存系统是两个系统,一次下单的操作由两个系统协同完成

• 金融系统中的银行卡充值

在金融系统中通过银行卡向平台充值需要通过银行系统*_和_*金融系统协同完成。

分布式事务标准与规范


通过前面的分析我们可以了解到,分布式事务面临的问题无法达到绝对的、100% 可靠,没有办法从微观上、根源上彻底解决。那么问题解决到什么程度?性能和数据准确如何取舍和平衡?等等这些两难问题我们都需要有相关的标准和规范来参照和衡量。

CAP 定理

CAP 定理,又叫做布鲁尔定理。对于设计分布式系统(不仅仅是分布式事务)的架构师来说,CAP 就是你的入门理论。

分布式系统(Distribution System)正变得越来越重要,大型网站几乎都是分布式的。

分布式系统的最大难点就是各节点的状态如何同步。CAP 定理是这方面的基本定理,也是理解分布式系统的起点。

C:一致性(通俗理解:对)
Consistency 一致性:指数据在多个副本之间能够保持一致的特性。

• 强一致性:分布式系统中的数据在任何一个相同的时刻具有同样的值。

• 弱一致性:数据更新后,能够容忍数据在短暂的一段时间内可以不一致。也可称之为:最终一致性。

TIP

数据的『正本』和『副本』:

正本:通常 MySQL 这样的关系型数据库中存储的数据视为正本。

副本:通常 Redis、ElasticSearch 等 NoSQL 数据库中存储的数据视为副本。

A:可用性(通俗理解:快)

Available 可用性:这是在系统工作过程中,衡量系统性能的一个指标。指系统提供的服务必须一直处于可用的状态,每次只要收到用户的请求,服务器就必须给出回应。在合理的时间内返回合理的响应——不是错误和超时的响应。

只有非故障节点才能满足业务正常。

只有在合理的时间内用户才能接受。

只有返回合理的响应,信息才有价值。

P:网络分区容错性(通俗理解:能用)
Partition Toleration 分区容错性

• 分区

网络节点之间无法通信的情况下,节点被隔离,产生了网络分区,此时整个系统仍然是可以工作的。大多数分布式系统都分布在多个子网络。每个子网络就叫做一个区(partition)。

• 分区容错

即使各子网络之间无法通信,甚至某个分区出现了故障,但整个系统对外仍然是可用的。

CAP 定理
CAP 中的三者不可能同时满足。通常来说 P 是要求必须实现的:从外部视角来看,不管系统内部如何,我们无法接受整个系统对外不可用。在满足 P 的基础上,CP 和 AP 二选一。CAP 不可能同时满足。

BASE 定理


BASE = Bsaically Available(基本可用) + Soft state(软状态)+ Eventually consistent(最终一致性)。

理论的提出者是 eBay 的架构师。

BASE 定理是对 CAP 定理的进一步探讨。既然 CAP 中『一致性』和『可用性』无法同时满足,那如何在二者间达到一个可以接受的平衡呢?

BASE 定理的主张是:哪怕是做不到强一致性,也应该尽量做到最终一致性。

基本可用
相对于『用户请求什么就返回什么』的完整功能来说,基本可用是指系统在无法准确返回用户所请求的数据时也返回相关数据,而不是『什么都不返回』。

相对于『收到用户请求就立即返回结果』的优质用户体验来说,基本可用是指系统在无法立即响应用户请求时,稍作延迟仍然能够返回响应,而不是『放弃响应』。

也就是说:服务可以适度『降级』,但不能完全不可用。

软状态
满足 ACID 的事务属性就达到了『硬状态』。在传统的关系型数据库中,事务要么提交,要么回滚——非黑即白。而『软状态』允许系统暂时处于中间状态:从一个操作的发起到它同步到各个分布式节点,允许存在这样一个『正在同步数据』的中间过程。

最终一致性
相对于关系型数据库事务的『立即一致』,分布式系统应该追求『最终一致』,允许系统在同步数据的过程中存在暂时的数据不一致状态,只要数据能够达到『最终一致』,我们就可以认为系统是可靠的。

小结
不管 CAP 还是 BASE,它们都只是标准。它们没有提供分布式事务问题的解决方案,但是它们是解决方案是否成立的衡量标准。

分布式事务解决方案

基于 XA 协议的两阶段提交 (2PC)


X/Open 组织(即现在的 Open Group )定义了分布式事务处理模型

XA协议:XA是一个分布式事务协议。XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。

概念
二阶段提交2PC(Two phase Commit)是指,在分布式系统里,为了保证所有节点在进行事务提交时保持一致性的一种算法。

 背景
在分布式系统里,每个节点都可以知晓自己操作的成功或者失败,却无法知道其他节点操作的成功或失败。

当一个事务跨多个节点时,为了保持事务的原子性与一致性,需要引入一个协调者(Coordinator)来统一掌控所有参与者(Participant)的操作结果,并指示它们是否要把操作结果进行真正的提交(commit)或者回滚(rollback)。

思路
2PC顾名思义分为两个阶段,其实施思路可概括为:

(1)投票阶段(voting phase):参与者将操作结果通知协调者;

(2)提交阶段(commit phase):收到参与者的通知后,协调者再向参与者发出通知,根据反馈情况决定各参与者是否要提交还是回滚;

缺陷
算法执行过程中,所有节点都处于阻塞状态,所有节点所持有的资源(例如数据库数据,本地文件等)都处于封锁状态。

典型场景为:

(1)某一个参与者发出通知之前,所有参与者以及协调者都处于阻塞状态;

(2)在协调者发出通知之前,所有参与者都处于阻塞状态;

另外,如有协调者或者某个参与者出现了崩溃,为了避免整个算法处于一个完全阻塞状态,往往需要借助超时机制来将算法继续向前推进,故此时算法的效率比较低。

总的来说,2PC是一种比较保守的算法。

举例
甲乙丙丁四人要组织一个会议,需要确定会议时间,不妨设甲是协调者,乙丙丁是参与者。

投票阶段:

(1)甲发邮件给乙丙丁,周二十点开会是否有时间;

(2)乙回复有时间;

(3)丙回复有时间;

(4)丁迟迟不回复,此时对于这个活动,甲乙丙均处于阻塞状态,算法无法继续进行;

• 甲:在等待丁的回复

• 乙:在等待甲的下一个通知

• 丙:在等待甲的下一个通知

(5)丁回复有时间(或者没有时间);

• 此时甲收集到了全部反馈信息:乙(成功),丙(成功),丁(失败)

• 甲通知下一步操作:会议取消

提交阶段:

(1)协调者甲将收集到的结果反馈给乙丙丁(什么时候反馈,以及反馈结果如何,在此例中取决与丁的时间与决定);

(2)乙收到;(收到通知,会议取消)

(3)丙收到;(收到通知,会议取消)

(4)丁收到;(收到通知,会议取消)

结论
2PC效率很低,分布式事务很难做

实际应用交互流程
• 2PC两阶段提交的正向流程(最终能够提交事务)

第一阶段:

2PC中包含着两个角色:事务协调者和事务参与者。让我们来看一看他们之间的交互流程:

在分布式事务的第一阶段,作为事务协调者的节点会首先向所有的参与者节点发送Prepare请求。

在接到Prepare请求之后,每一个参与者节点会各自执行与事务有关的数据更新,写入Undo Log和Redo Log。如果参与者执行成功,暂时不提交事务,而是向事务协调节点返回“完成”消息。

当事务协调者接到了所有参与者的返回消息,整个分布式事务将会进入第二阶段。

第二阶段:

在2PC分布式事务的第二阶段,如果事务协调节点在之前所收到都是正向返回,那么它将会向所有事务参与者发出Commit请求。
接到Commit请求之后,事务参与者节点会各自进行本地的事务提交,并释放锁资源。当本地事务完成提交后,将会向事务协调者返回“完成”消息。

当事务协调者接收到所有事务参与者的“完成”反馈,整个分布式事务完成。

TIP

ACK 的含义是:Acknowledge character,确认字符。

• 失败情况的处理流程(事务最终会回滚)

第一阶段:


第二阶段:

在2PC的第一阶段,如果某个事务参与者反馈失败消息,说明该节点的本地事务执行不成功,必须回滚。


于是在第二阶段,事务协调节点向所有的事务参与者发送Abort(中止)请求。接收到Abort请求之后,各个事务参与者节点需要在本地进行事务的回滚操作,回滚操作依照Undo Log来进行。

以上就是2PC两阶段提交协议的详细过程。

2PC两阶段提交究竟有哪些不足呢?

性能问题

2PC遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。

协调者单点故障问题

协调者是整个2PC模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。

丢失消息导致的不一致问题。

2PC的第二个阶段,如果发生局部网络问题,一部分事务参与者收到了提交消息,另一部分事务参与者没收到提交消息,那么就导致了节点之间数据的不一致。

代码补偿事务(TCC)


TCC 的作用主要是解决跨服务调用场景下的分布式事务问题

场景案例
以航班预定的案例,来介绍TCC要解决的事务场景。在这里笔者虚构一个场景,把自己当做航班预定的主人公,来介绍这个案例。从合肥 –> 昆明 –> 大理。

准备从合肥出发,到云南大理去游玩,然后使用美团App(机票代理商)来订机票。发现没有从合肥直达大理的航班,需要到昆明进行中转。如下图:


从图中我们可以看出来,从合肥到昆明乘坐的是四川航空,从昆明到大理乘坐的是东方航空。
由于使用的是美团App预定,当我选择了这种航班预定方案后,美团App要去四川航空和东方航空各帮我购买一张票。如下图:


考虑最简单的情况:美团先去川航帮我买票,如果买不到,那么东航也没必要买了。如果川航购买成功,再去东航购买另一张票。

现在问题来了:假设美团先从川航成功买到了票,然后去东航买票的时候,因为天气问题,东航航班被取消了。那么此时,美团必须取消川航的票,因为只有一张票是没用的,不取消就是浪费我的钱。那么如果取消会怎样呢?如果读者有取消机票经历的话,非正常退票,肯定要扣手续费的。在这里,川航本来已经购买成功,现在因为东航的原因要退川航的票,川航应该是要扣代理商的钱的。

那么美团就要保证,如果任一航班购买失败,都不能扣钱,怎么做呢?

两个航空公司都为美团提供以下3个接口:机票预留接口、确认接口、取消接口。美团App分2个阶段进行调用,如下所示:

在第 1 阶段:
美团分别请求两个航空公司预留机票,两个航空公司分别告诉美团预留成功还是失败。航空公司需要保证,机票预留成功的话,之后一定能购买到。

在第 2 阶段:

如果两个航空公司都预留成功,则分别向两个公司发送确认购买请求。

如果两个航空公司任意一个预留失败,则对于预留成功的航空公司也要取消预留。这种情况下,对于之前预留成功机票的航班取消,也不会扣用户的钱,因为购买并没实际发生,之前只是请求预留机票而已。

通过这种方案,可以保证两个航空公司购买机票的一致性,要不都成功,要不都失败,即使失败也不会扣用户的钱。如果在两个航班都已经已经确认购买后,再退票,那肯定还是要扣钱的。

当然,实际情况肯定这里提到的肯定要复杂,通常航空公司在第一阶段,对于预留的机票,会要求在指定的时间必须确认购买(支付成功),如果没有及时确认购买,会自动取消。假设川航要求10分钟内支付成功,东航要求30分钟内支付成功。以较短的时间算,如果用户在10分钟内支付成功的话,那么美团会向两个航空公司都发送确认购买的请求,如果超过10分钟(以较短的时间为准),那么就不能进行支付。

这个方案提供给我们一种跨服务保证事务一致性的一种解决思路,可以把这种方案当做TCC的雏形。

TCC是Try ( 尝试 ) — Confirm(确认) — Cancel ( 取消 ) 的简称:

操作方法

含义

Try

完成所有业务检查(一致性),预留业务资源(准隔离性) 回顾上面航班预定案例的阶段1,机票就是业务资源,所有的资源提供者(航空公司)预留都成功,try阶段才算成功

Confirm

确认执行业务操作,不做任何业务检查, 只使用Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,美团APP确认两个航空公司机票都预留成功,因此向两个航空公司分别发送确认购买的请求。

Cancel

取消Try阶段预留的业务资源。回顾上面航班预定案例的阶段2,如果某个业务方的业务资源没有预留成功,则取消所有业务资源预留请求。

有哥们立马会想到,TCC与XA两阶段提交有着异曲同工之妙,下图列出了二者之间的对比:

1) 在阶段1:

在XA中,各个RM准备提交各自的事务分支,事实上就是准备提交资源的更新操作(insert、delete、update等);而在TCC中,是主业务活动请求(try)各个从业务服务预留资源。

2) 在阶段2:

XA根据第一阶段每个RM是否都prepare成功,判断是要提交还是回滚。如果都prepare成功,那么就commit每个事务分支,反之则rollback每个事务分支。

TCC中,如果在第一阶段所有业务资源都预留成功,那么confirm各个从业务服务,否则取消(cancel)所有从业务服务的资源预留请求。

TCC两阶段提交与XA两阶段提交的区别
XA是资源层面(直接操作数据库)的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。

XA事务中的两阶段提交内部过程是对开发者屏蔽的 ,其内部会委派给TransactionManager进行真正的两阶段提交,因此开发者从代码层面是感知不到这个过程的。而且事务管理器在两阶段提交过程中,从prepare到commit/rollback过程中,资源实际上一直都是被加锁的。如果有其他人需要更新这两条记录,那么就必须等待锁释放。

TCC是业务层面(操作Java服务)的分布式事务,最终一致性,不会一直持有资源的锁。

TCC中的两阶段提交并没有对开发者完全屏蔽,也就是说从代码层面,开发者是可以感受到两阶段提交的存在。如上述航班预定案例:在第一阶段,航空公司需要提供try接口(机票资源预留)。在第二阶段,航空公司提需要提供confirm/cancel接口(确认购买机票/取消预留)。开发者明显的感知到了两阶段提交过程的存在。try、confirm/cancel在执行过程中,一般都会开启各自的本地事务,来保证方法内部业务逻辑的ACID特性。其中:

1、try过程的本地事务,是保证资源预留的业务逻辑的正确性。

2、confirm/cancel执行的本地事务逻辑确认/取消预留资源,以保证最终一致性,也就是所谓的补偿型事务

由于是多个独立的本地事务,因此不会对资源一直加锁。

TCC 将事务提交分为 Try - Confirm - Cancel 3个操作。这和两阶段提交有点类似,Try为第一阶段,Confirm - Cancel为第二阶段,是一种应用层面侵入业务的两阶段提交。

操作方法

含义

Try

预留业务资源/数据效验-尝试检查当前操作是否可行

Confirm

确认执行业务操作,实际提交数据,不做任何业务检查,try成功,confirm必定成功

Cancel

取消执行业务操作,实际回滚数据

其核心在于将业务分为两个操作步骤完成。不依赖 RM 对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。

例如:A要向 B 转账,思路大概是:
假设用户user表中有两个字段:可用余额(available_money)、冻结余额(frozen_money) A扣钱对应服务A(ServiceA) B加钱对应服务B(ServiceB) 转账订单服务(OrderService) 业务转账方法服务(BusinessService)

ServiceA,ServiceB,OrderService都需分别实现try(),confirm(),cancle()方法,方法对应业务逻辑如下


其中业务调用方BusinessService中就需要调用

ServiceA.try()
ServiceB.try()
OrderService.try()


1、当所有try()方法均执行成功时,对全局事务进行提交,即由事务管理器调用每个微服务的confirm()方法

2、 当任意一个方法try()失败(预留资源不足,抑或网络异常,代码异常等任何异常),由事物管理器调用每个微服务的cancle()方法对全局事务进行回滚

优点:跟2PC(很多第三方框架)比起来,实现以及流程相对简单了一些,但数据的一致性比2PC也要差一些

缺点:缺点还是比较明显的,在2,3步中都有可能失败。TCC属于应用层的一种补偿方式,所以需要程序员在实现的时候多写很多补偿的代码,在一些场景中,一些业务流程可能用TCC不太好定义及处理。

本地消息表(异步确保)- 事务最终一致性

这种实现方式的思路,其实是源于 ebay,后来通过支付宝等公司的布道,在业内广泛使用。其基本的设计思想是将远程分布式事务拆分成一系列的本地事务。如果不考虑性能及设计优雅,借助关系型数据库中的表即可实现。

举个经典的跨行转账的例子来描述。

第一步伪代码如下,扣款 1W,通过本地事务保证了凭证消息插入到消息表中。

第二步,通知对方银行账户加 1W。那问题来了,如何通知到对方呢?

• 通常采用两种方式:

1. 采用定时轮询扫描的方式,去检查消息表的数据

1. 采用时效性高的 MQ,由对方订阅消息并监听,有消息时自动触发事件

• 两种方式其实各有利弊,仅仅依靠 MQ,可能会出现通知失败的问题。而过于频繁的定时轮询,效率也不是最佳的(90% 是无用功)。所以,我们一般会把两种方式结合起来使用。

• 解决了通知的问题,又有新的问题了。万一这消息有重复被消费,往用户帐号上多加了钱,那岂不是后果很严重?

• 仔细思考,其实我们可以在消息消费方,也通过一个“消费状态表”来记录消费状态。在执行“加款”操作之前,检测下该消息(提供标识)是否已经消费过,消费完成后,通过本地事务控制来更新这个“消费状态表”。这样子就避免重复消费的问题。

• 总结:上述的方式是一种非常经典的实现,基本避免了分布式事务,实现了“最终一致性”。但是,关系型数据库的吞吐量和性能方面存在瓶颈,频繁的读写消息会给数据库造成压力。所以,在真正的高并发场景下,该方案也会有瓶颈和限制的。

• 在订单系统新增一条消息表,将新增订单和新增消息放到一个事务里完成,然后通过轮询的方式去查询消息表,将消息推送到 MQ,库存系统去消费 MQ。


• 执行流程:

• 订单系统,添加一条订单和一条消息,在一个事务里提交。

• 订单系统,使用定时任务轮询查询状态为未同步的消息表,发送到 MQ,如果发送失败,就重试发送。

• 库存系统,接收 MQ 消息,修改库存表,需要保证幂等操作。

• 如果修改成功,调用 RPC 接口修改订单系统消息表的状态为已完成或者直接删除这条消息。

• 如果修改失败,可以不做处理,等待重试。

• 订单系统中的消息有可能由于业务问题会一直重复发送,所以为了避免这种情况可以记录一下发送次数,当达到次数限制之后报警,人工接入处理;库存系统需要保证幂等,避免同一条消息被多次消费造成数据不一致。

• 本地消息表这种方案实现了最终一致性,需要在业务系统里增加消息表,业务逻辑中多一次插入的 DB 操作,所以性能会有损耗,而且最终一致性的间隔主要由定时任务的间隔时间决定。

• 优点:一种非常经典的实现,避免了分布式事务,实现了最终一致性。在 .NET中有现成的解决方案。

• 缺点:消息表会耦合到业务系统中,如果没有封装好的解决方案,会有很多杂活需要处理。

MQ 事务消息


有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持,但是Rabbit MQ可以通过可靠消息投递来实现事务消息。

• RocketMQ

• Rabbit MQ


方案选型
• 基于XA 依赖于数据库底层XA支持,拿mysql数据库来说,innodb引擎支持XA协议,可以实现分布式事务。但是使用XA事务需要将事务隔离级别设置为串行,这样整个数据库使用性能将下降严重。

• TCC业务代码侵入性高,需要提供try、commit、cancel多个接口业务实现。现有支持的开源框架有:ByteTCC、tcc-transaction、seata、tx-lcn等

• MQ非金融类业务首选MQ实现。MQ不能及时保障数据一致性,也是一种柔性事务(允许有数据有中间状态,可以保障经过一定时间后最终达到数据一致性)

• 业务2PCXA协议是从资源层面的实现了2PC,如:seata、tx-lcn等是从业务层面实现了2PC。

参考文章

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值