乐趣区

关于java:分布式事务处理方案大-PK

首先先说一个大准则:分布式事务能不必就不要用,毕竟这个用起来还是有一些麻烦的。当然,不必和不会用可是两码事。

  1. 分布式事务基础理论
    学习分布式事务,有一些基础理论须要咱们先来理解下。
    1.1 本地事务
    本地事务是指将多条语句作为一个整体进行操作的性能,通过数据库事务能够确保该事务范畴内的所有操作都能够全副胜利或者全副失败,如果事务失败,那么成果就和没有执行这些 SQL 一样,不会对数据库数据有任何改变。也就是事务具备原子性,一个事务中的一系列操作要么全副胜利,要么全副失败。一般来说,事务具备 4 个属性:

Atomic:原子性,将一个事务中的所有 SQL 作为原子工作单元执行,要么全副执行,要么全副不执行;
Consistent:一致性,事务实现后,所有数据的状态都是统一的,以银行转帐为例,如果 A 账户减去了 100,则 B 账户则必然加上了 100;
Isolation:隔离性,如果有多个事务并发执行,每个事务作出的批改必须与其余事务隔离;
Duration:持久性,即事务实现后,对数据库数据的批改被长久化存储。

这四个属性通常称为 ACID 个性。
这块松哥之前专门录过相干的视频,这里就不再赘述了。

www.bilibili.com/video/BV1Eq…

1.2 分布式事务
当咱们的我的项目上了微服务之后,分布式事务就是一个比拟常见的问题了,咱们也会遇到很多相干的场景。
就拿咱们前两天讲的商品下单的分布式事务的案例来说,像上面这样,一共有五个服务,架构如下图:

eureka:这是服务注册核心。
account:这是账户服务,能够查问 / 批改用户的账户信息(次要是账户余额)。
order:这是订单服务,能够下订单。
storage:这是一个仓储服务,能够查问 / 批改商品的库存数量。
bussiness:这是业务,用户下单操作将在这里实现。

当用户想要下单的时候,调用了 bussiness 中的接口,bussiness 中的接口又调用了它本人的 service,在 service 中,通过 feign 调用 storage 中的接口去扣库存,而后再通过 feign 调用 order 中的接口去创立订单(order 在创立订单的时候,不仅会创立订单,还会扣除用户账户的余额)。
这三个操作,咱们心愿他们可能同时胜利或者同时失败。然而如上图所示,三个微服务都有本人的 DB,这是三个齐全不同的 DB,相当于三个不同的本地事务,依照传统的本地事务规定,咱们显然是无奈实现三个操作同时胜利或者同时失败的。
想要实现 storage、order 以及 account 中的操作同时胜利或者同时失败,就得思考分布式事务了。
最初,咱们再来看看分布式事务的概念:分布式事务是指事务的参与者、反对事务的服务器、资源服务器以及事务管理器别离位于的不同节点之上,数据库的操作执行胜利与否,不仅取决于本地 DB 的执行后果,也取决于第三方零碎的执行后果。而分布式事务就保障这些操作要么全副胜利,要么全副失败。实质上,分布式事务就是为了保障不同数据库的数据一致性。
1.3 CAP
CAP 定理(CAP theorem),有时候又被称作布鲁尔定理(Brewer’s theorem),它指出对于一个分布式计算零碎来说,不可能同时满足以下三点:

一致性(Consistency):在分布式系统中的所有数据备份,在同一时刻是否具备同样的值。(等同于所有节点拜访同一份最新的数据正本)。
可用性(Availability):在集群中一部分节点故障后,集群整体是否还能响应客户端的读写申请。(对数据更新具备高可用性)。
分区容错性(Partition tolerance):这个我感觉可能对有的小伙伴来说有点难以了解,我就简略说一下,先来说分区:因为咱们是分布式系统,分布式系统中不同的微服务位于不同的网络节点上,当产生网络故障或者节点故障的时候,不同的服务之间就无奈通信了,也就是说产生了分区;再来看分区容错性:这是说,当咱们的零碎中呈现分区的时候,零碎还要能运行,不能罢工!一般来说,在一个分布式系统中,分区产生的概率还是比拟大的,不会产生分区的零碎,那就不是分布式系统了,而是单体利用了。

CAP 准则的精华就是要么 AP,要么 CP,要么 AC,然而不存在 CAP。因为在分布式系统内,P 是必然的产生的,不选 P,一旦产生分区,整个分布式系统就齐全无奈应用了,这样的零碎就太软弱了。所以对于分布式系统,咱们只能能思考当产生分区谬误时,如何抉择一致性和可用性(抉择一致性,意味着服务在某段时间内不可用,抉择了可用性,意味着服务尽管始终可用然而返回的数据却不统一)。
而依据一致性和可用性的抉择不同,开源的分布式系统往往又被分为 CP 零碎和 AP 零碎。
当一套零碎在产生分区故障后,客户端的任何申请都被卡死或者超时,然而零碎的每个节点总是会返回统一的数据,则这套零碎就是 CP 零碎,经典的比方 Zookeeper。
如果一套零碎产生分区故障后,客户端仍然能够拜访零碎,然而获取的数据有的是新的数据,有的还是老数据,那么这套零碎就是 AP 零碎,经典的比方 Eureka。
1.4 BASE
因为无奈同时满足 CAP,所以又有了 BASE 实践,BASE 实践指的是:

根本可用 Basically Available:分布式系统在呈现故障的时候,容许损失局部可用性,即保障外围可用。
软状态 Soft State:容许零碎存在中间状态,而该中间状态不会影响零碎整体可用性。
终一致性 Eventual Consistency:零碎中的所有数据正本通过肯定工夫后,最终可能达到统一的状态。

BASE 实践的核心思想是即使无奈做到强一致性,但应该采纳适宜的形式保障最终一致性。
BASE 实践实质上是对 CAP 实践的延长,是对 CAP 中 AP 计划的一个补充。
1.5 刚柔并济
事务有刚性事务和柔性事务之分。
刚性事务(如单数据库中的本地事务)齐全遵循 ACID 标准,即数据库事务正确执行的四个基本要素:

原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)

柔性事务,次要就是只分布式事务了,柔性事务为了满足可用性、性能与降级服务的须要,升高一致性(Consistency)与隔离性(Isolation)的要求,恪守 BASE 实践:

根本业务可用性(Basic Availability)
柔性状态(Soft state)
最终一致性(Eventual consistency)

当然,柔性事务也局部遵循 ACID 标准:

原子性:严格遵循
一致性:事务实现后的一致性严格遵循;事务中的一致性可适当放宽
隔离性:并行事务间不可影响;事务两头后果可见性容许平安放宽
持久性:严格遵循

柔性事务有不同的分类,不过基本上都能够看作是分布式事务的解决方案:

两阶段型:分布式事务二阶段提交,对应技术上的 XA、JTA/JTS,这是分布式环境下事务处理的典型模式。
弥补型:咱们之前文章介绍的 TCC,就算是一种弥补型事务,在 Try 胜利的状况下,如果事务要回滚,Cancel 将作为一个弥补机制,回滚 Try 操作;TCC 各操作事务本地化,且尽早提交(没有两阶段束缚);当全局事务要求回滚时,通过另一个本地事务实现“弥补”行为。TCC 是将资源层的二阶段提交协定转换到业务层,成为业务模型中的一部分。
异步确保型:将一些有同步抵触的事务操作变为异步操作,防止对数据库事务的争用,如音讯事务机制。
最大致力告诉型:通过告诉服务器(音讯告诉)进行,容许失败,有补充机制。

  1. 分布式事务实际
    2.1 XA
    先来说说 XA。
    XA 是一种典型的两阶段提交(2PC,Two-phase commit protocol),而两阶段提交是一种强一致性设计,在两阶段提交中,个别会引入一个事务协调者的角色来协调治理各个事务参与者,例如咱们之前文章中应用的 seata-server 其实是就是一个事务协调者。所谓的两阶段别离指的是筹备和提交两个阶段。
    XA 标准 是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)规范。
    XA 标准形容了全局的事务管理器与部分的资源管理器之间的接口。XA 标准的目标是容许多个资源(如数据库,应用服务器,音讯队列等)在同一事务中拜访,这样能够使 ACID 属性逾越应用程序而放弃无效。
    XA 标准应用两阶段提交来保障所有资源同时提交或回滚任何特定的事务。
    XA 标准在上世纪 90 年代初就被提出。目前,简直所有支流的数据库如 MySQL、Oracle、MSSQL 等都对 XA 标准提供了反对。
    XA 事务的根底是两阶段提交协定。须要有一个事务协调者来保障所有的事务参与者都实现了筹备工作(第一阶段)。如果协调者收到所有参与者都筹备好的音讯,就会告诉所有的事务都能够提交了(第二阶段)。MySQL 在这个 XA 事务中表演的是参与者的角色,而不是协调者(事务管理器)。
    MySQL 的 XA 事务分为外部 XA 和内部 XA。内部 XA 能够参加到内部的分布式事务中,须要应用层染指作为协调者;外部 XA 事务用于同一实例下跨多引擎事务,由 Binlog 作为协调者,比方在一个存储引擎提交时,须要将提交信息写入二进制日志,这就是一个分布式外部 XA 事务,只不过二进制日志的参与者是 MySQL 自身。MySQL 在 XA 事务中表演的是一个参与者的角色,而不是协调者。
    XA 事务的特点是:

简略易了解,开发较容易。
对资源进行了长时间的锁定,并发度低。

2.2 3PC
3PC 次要是为了补救 2PC 的有余而产生的,2PC 有哪些有余呢?

同步阻塞:2PC 在执行过程中,所有参加节点(也就是一个分支事务)都是事务阻塞型的,当参与者占有公共资源时,其余第三方节点拜访公共资源不得不处于阻塞状态,也就是在 2PC 执行的过程中,资源是被锁住的。
单点故障:在 2PC 中,事务协调者表演了无足轻重的作用,因为事务协调者的重要性,一旦事务协调者产生故障,事务的参与者就会始终阻塞上来。尤其是在第二阶段,如果协调者产生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无奈持续实现事务操作。还有一个问题,就是当事务协调者收回 commit 指令之前,如果宕机了,此时尽管能够从新选举一个新的协调者进去,然而还是无奈解决因为事务协调者宕机导致的事务参与者处于阻塞状态的问题。

3PC 则尝试解决 2PC 的这些问题。3PC 次要是把 2PC 中的第一阶段再次一分为二,这样 3PC 就有 CanCommit、PreCommit 以及 DoCommit 三个不同的阶段。不过 3PC 并不能解决 2PC 的所有问题,3PC 次要解决了单点故障问题,并且缩小了阻塞。一旦事务参与者(分支事务)无奈及时收到来自事务协调者的信息,那么分支事务会默认执行 commit,而不会始终持有事务资源并处于阻塞状态,不过这种机制也带来了新的问题,假如事务协调者发送了 abort 指令给各个分支事务,然而因为网络问题导致分支事务没有及时接管到该指令,那么分支事务在期待超时之后执行了 commit 操作,这样就和其余接到 abort 命令并执行回滚的分支事务之间存在数据不统一的状况。
咱们来看看 3PC 的流程:

CanCommit 阶段:这个阶段所做的事很简略,就是事务协调者询问各个分支事务,你是否有能力实现此次事务?如果都返回 yes,则进入第二阶段;有一个返回 no 或期待响应超时,则中断事务,并向所有分支事务发送 abort 申请。
PreCommit 阶段:此时事务协调者会向所有的分支事务发送 PreCommit 申请,分支事务收到后开始执行事务操作,并将 Undo 和 Redo 信息记录到事务日志中。分支执行完事务操作后(此时属于未提交事务的状态),就会向事务协调者反馈“Ack”示意我曾经筹备好提交了,并期待协调者的下一步指令。
DoCommit 阶段:在阶段二中如果所有的分支事务节点都能够进行 PreCommit 提交,那么事务协调者就会从“预提交状态”转变为“提交状态”,而后向所有的分支事务节点发送 ”doCommit” 申请,分支事务节点在收到提交申请后就会各自执行事务提交操作,并向协调者节点反馈“Ack”音讯,协调者收到所有参与者的 Ack 音讯后实现事务。

相同,如果有一个分支事务节点未实现 PreCommit 的反馈或者反馈超时,那么协调者都会向所有的参与者节点发送 abort 申请,从而中断事务。
2.3 TCC
对于 TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。
TCC 模式次要有如下一些优缺点:
长处:

性能晋升:通过具体业务来实现管制资源锁的粒度变小,不会锁定整个资源。
数据最终一致性:基于 Confirm 和 Cancel 的幂等性,保障事务最终实现确认或者勾销,保证数据的一致性。
可靠性:解决了 XA 协定的协调者单点故障问题,由主业务方发动并管制整个业务流动,业务流动管理器也变成多点,引入集群。

毛病:

对微服务的侵入性强,微服务的每个事务都必须实现 try,confirm,cancel 等 3 个办法,开发成本高,今后保护革新的老本也高。
为了达到事务的一致性要求,try,confirm、cancel 接口必须实现等幂性操作,这在肯定水平上减少了开发工作量。

TCC 次要是两个阶段,步骤如下:

Try 阶段(一阶段):尝试执行,实现所有业务查看(一致性), 预留必须业务资源(准隔离性)。
Confirm 阶段(二阶段):确认执行真正执行业务,不作任何业务查看,只应用 Try 阶段预留的业务资源,Confirm 操作满足需要满足幂等性,Confirm 执行失败后须要进行重试。
Cancel 阶段:勾销执行,开释 Try 阶段预留的业务资源,Cancel 操作也须要满足幂等性。Cancel 阶段的异样和 Confirm 阶段异样解决计划基本上统一。

在咱们之前的文章中,松哥也给大家举了 TCC 的例子了,这里就不再赘述了。
2.4 SAGA
SAGA 最后呈现在 1987 年 Hector Garcaa-Molrna & Kenneth Salem 发表的论文 SAGAS 里。这篇论文的核心思想是将长事务拆分为多个短事务,由 Saga 事务协调器协调,如果每个短事务都胜利提交实现,那么全局事务就失常实现,如果某个步骤失败,则依据相同程序一次调用弥补操作。
Saga 事务的特点是:

并发度高,不必像 XA 事务那样长期锁定资源。
须要定义失常操作以及弥补操作(回滚),开发量工作量比 XA 大。
一致性较弱,对于转账,可能产生 A 用户已扣款,最初转账又失败的状况

SAGA 实用的场景较多,实用于长事务或者对两头后果不敏感的业务场景。
2.5 本地音讯表
本地音讯表这个计划最后是 ebay 架构师 Dan Pritchett 在 2008 年发表给 ACM 的文章中提出。
顾名思义,本地音讯表就是会有一张寄存本地音讯的表,个别都是放在数据库中,而后在执行业务的时候将业务的执行和将音讯放入音讯表中的操作放在同一个事务中,这样就能保障音讯放入本地表以及业务必定是一起执行胜利的。
当一个操作执行胜利之后,再去执行下一个操作,如果下一个操作调用胜利了好说,音讯表的音讯状态能够间接改为已胜利;如果下一个工作调用失败也没关系,会有后台任务定时去读取本地音讯表,筛选出还未胜利的音讯再调用对应的服务(重试),服务更新胜利了再变更音讯的状态。
重试就得保障对应服务的办法是幂等的,而且个别重试会有最大次数,超过最大次数能够记录下报警让人工解决。
依据下面的形容,小伙伴们其实能够看到,本地音讯表其实实现的是最终一致性,容忍了数据临时不统一的状况。
本地音讯表的特点:

长事务仅须要分拆成多个工作,应用简略。
生产者须要额定的创立音讯表。
每个本地音讯表都须要进行轮询(如果有失败的要重试)。
消费者的逻辑如果无奈通过重试胜利,那么还须要更多的机制,来回滚操作。

依据本地音讯表的特点咱们能够发现,本地音讯表实用于可异步执行且后续操作无需回滚的业务。
2.6 音讯事务
这种计划的外围思路,其实就是通过消息中间件来将全局事务转为本地事务,通过消息中间件来确保各个分支事务最终都能调用胜利。
松哥之前写过一篇文章是利用 RabbitMQ 实现的:

利用 RabbitMQ 解决分布式事务

不过起初发现利用 Alibaba 的 RocketMQ(4.3 之后)能够更好的实现分布式事务。
RocketMQ 是一种最终一致性的分布式事务,就是说它保障的是音讯最终一致性,而不是像 2PC、3PC、TCC 那样强统一分布式事务,在 RocketMQ 中有一种音讯叫做 Half Message,Half Message 是指暂不能被 Consumer 生产的音讯,尽管 Producer 曾经把音讯胜利发送到了 Broker 端,但此音讯被标记为暂不能投递状态,处于该种状态下的音讯称为半音讯,此时须要 Producer 对音讯进行二次确认后,Consumer 能力去生产它。
RocketMQ 就是基于 Half Message 来实现的分布式事务,举一个转账的例子:

A 服务先发送个 Half Message 给 Brock 端,音讯中携带 B 服务行将要 +100 元的信息。
当 A 服务晓得 Half Message 发送胜利后,那么开始本地事务。
执行本地事务(会有三种状况 1、执行胜利;2、执行失败;3、网络等起因导致没有响应)
3.1 如果本地事务胜利,那么 A 向 Broker 服务器发送 Commit, 这样 B 服务就能够生产该 message。
3.2 如果本地事务失败,那么 A 向 Broker 服务器发送 Rollback,那么就会间接删除下面这条半音讯。
3.3 如果因为网络或者生产者利用重启等起因。导致 A 始终没有对 Half Message 进行二次确认,此时 Broker 服务器会定时扫描长期处于半音讯的音讯,会被动询问 A 端该音讯的最终状态(Commit 或者 Rollback),这个操作也就是所谓的音讯回查。

可能有小伙伴会说,那要是 B 最终执行失败怎么办?对于这种状况,咱们简直能够判定就是代码有问题所以才引起异样,因为生产端 RocketMQ 有重试机制,如果不是代码问题个别重试几次就能胜利。
如果是代码的起因引起多次重试失败后,也没有关系,将该异样记录下来,由人工解决,人工兜底解决后,就能够让事务达到最终的一致性。
2.7 最大致力告诉
发动告诉方通过肯定的机制最大致力将业务处理结果告诉到接管方。具体包含:

有肯定的音讯重试机制。因为接管告诉方可能没有接管到告诉,此时要有肯定的机制对音讯进行重试。
音讯校对机制。如果尽最大致力也没有告诉到接管方,或者接管方生产音讯后要再次生产,此时可由接管方被动向告诉方查问音讯信息来满足需要。

在后面两个大节介绍的的本地音讯表和事务音讯都属于可靠消息,这与咱们这里介绍的最大致力告诉有什么不同?

可靠消息一致性:音讯发起方须要保障将音讯收回去,并且将音讯发到接管方,音讯的可靠性要害由发起方来保障。
最大致力告诉:音讯发起方尽最大致力将业务处理结果告诉给接管方,然而可能音讯接管不到,此时须要接管方被动调用发起方的接口查问业务处理结果,此时音讯的可靠性要害在接管方。

仅此而已。
在具体的解决方案上,最大致力告诉须要音讯发起方提供接口,让被告诉方可能通过接口查问业务处理结果。
最大致力告诉实用于业务告诉类型,最常见的场景就是领取回调,领取服务收到第三方服务领取胜利告诉后,先更新本人库中订单领取状态,而后同步告诉订单服务领取胜利。如果此次同步告诉失败,会通过异步脚步一直重试地调用订单服务的接口。
最大致力告诉更多是业务上的设计,在基础设施层,能够间接应用二阶段音讯,或者事务音讯、本地音讯表等来实现。

  1. 小结
    好啦,学习分布式事务解决方案,最大的感触就是:没有银弹!
退出移动版