一位小伙伴准备了许久的阿里Java面试,原以为能够顺利拿下offer,但在第三面还是被摁在地上反复摩擦,丧气一段时间后,小伙伴调整了心态重新尝试了一下,最终拿下了offer,今天小编把这位小伙伴遇到的面试题分享出来,希望能对即将面试的小伙伴有所帮助。
分布式事务其实就在我们身边,你一直在用,但是你却一直不注意它。
扣你账户的余额,增加别人账户余额,如果只扣了你的,别人没增加这是失败;如果没扣你的钱别人也增加了那银行的赔钱。
电商系统中这是很常见的一个场景,用户下单成功了,店家没收到单,不发货;用户取消了订单,但是店家却看到了订单,发了货。
当我们的数据量大了之后,我们可能会部署很多独立的数据库,但是你的一个逻辑可能会同时操作很多个数据库的表,这时候该如何保证所有的操作要么成功,要么失败。
微服务的拆分让各个系统各司其职,但是带来的也有很多痛苦,一个操作可能会伴随很多的外部请求,如果某一个外部系统挂掉了,之前操作已完成的这些是否需要回滚。
针对上面这些问题,我们前面学过的数据库4大特性:ACID 似乎在这里想要达到就变得很困难,单机情况下你还可以通过锁和日志机制来控制数据,在分布式场景又该如何实现呢?在不同的分布式应用架构下,实现一个分布式事务要考虑的问题并不完全一样,比如对多资源的协调、事务的跨服务传播等,实现机制也是复杂多变。尽管有这么多工程细节需要考虑,但分布式事务最核心的还是其 ACID 特性,只是这种 ACID 变换了场景。
传统的 ACID 模型肯定无法解决分布式环境下的挑战,基于此加州大学伯克利分校 Eric Brewer教授提出 CAP 定理,他指出 现代 WEB 服务无法同时满足以下 3 个属性:
关于一致性的理解后面分出来三个方向:
关于一致性的理解不同,后面对于 CAP 理论的实现就有所不同。
另外 Eric Brewer教授指出现代 WEB 服务无法同时满足这 3 个属性,说的是无法同时满足,那这是为什么呢?
如果在某个分布式系统中无副本,那么必然满足强一致性,同时也满足可用性,但是如果这个数据宕机了,那么可用性就得不到保证。
如果一个系统满足 AP,那么一致性又得不到保证。所以 CAP 原则的精髓就是要么 AP,要么 CP,要么 AC,但是不存在 CAP。
基于前面提到的 CAP,反正我们都无法同时满足CAP,设计系统的最高目的就是让他跑下去不出错,那么是不是可以不要求强一致性,最终让他一致即可。所以后面又提出来了 BASE 定理:
基于现在大型分布式系统的复杂性,我们无法保证服务永远达到999,那么是否可以取舍,核心服务达到999,非核心服务允许挂为了保全核心服务。另外在非核心服务 down 机过程中允许系统暂时出现不一致但是这个不一致并不影响系统的核心功能使用。
最终系统恢复,所有服务一起修复数据,最终达到一致的状态。
业内通常把严格遵循 ACID 的事务称为刚性事务,而基于 BASE 思想实现的事务称为柔性事务。柔性事务并不是完全放弃了 ACID,仅仅是放宽了一致性要求:事务完成后的一致性严格遵循,事务中的一致性可适当放宽。
分布式事务实现方案从类型上去分刚性事务、柔型事务。刚性事务:通常无业务改造,强一致性,原生支持回滚/隔离性,低并发,适合短事务。柔性事务:有业务改造,最终一致性,实现补偿接口,实现资源锁定接口,高并发,适合长事务。
与本地事务一样,分布式事务场景下也可以采用两阶段提交的方案来实现。XA 的全称是 eXtended Architecture,它是一个分布式事务协议,通过二阶段提交协议保证强一致性。
XA 规范中定义了分布式事务处理模型,这个模型中包含四个核心角色:
XA 协议大概的两个流程为:
XA 协议是如何满足 ACID 的呢?
原子性和持久性我们就不用说,我们看看隔离性和一致性。
隔离性
XA 协议中没有描述如何实现分布式事务的隔离性,但是 XA 协议要求每个资源管理器都要实现本地事务,也就是说基于 XA 协议实现的分布式事务的隔离性是由每个资源管理器本地事务的隔离性来保证的,当一个分布式事务的所有子事务都是隔离的,那么这个分布式事务天然的就实现了隔离性。
一致性
在单机环境下的一致性就是保证当前服务器数据一致即可。事务执行完毕数据最终一致,不同的隔离级别下事务执行过程的中间状态不能被别的事务观察到。
事务执行完毕最终一致这个好保证,但是在RR 隔离级别下不可见一个未提交事务的中间态在分布式情况该如何做到呢?单机上 MySQL 提供了MVCC机制,采用多版本控制来处理,那分布式事务场景也是否也可以提供这样的机制呢?XA 协议并没有定义怎么实现全局的快照,一个基本思路是用一个集中式或者逻辑上单调递增的东西来控制生成全局 Snapshot,每个事务或者每条 SQL 执行时都去获取一次,从而实现不同隔离级别下的一致性。当然开发的难度还是挺大。
存在的问题:
同步阻塞:当参与事务者存在占用公共资源的情况,其中一个占用了资源,其他事务参与者就只能阻塞等待资源释放,处于阻塞状态。
单点故障:一旦事务管理器出现故障,整个系统不可用。
数据不一致:在阶段二,如果事务管理器只发送了部分 commit 消息,此时网络发生异常,那么只有部分参与者接收到 commit 消息,也就是说只有部分参与者提交了事务,使得系统数据不一致。
不确定性:当事务管理器发送 commit 之后,并且此时只有一个参与者收到了 commit,那么当该参与者与事务管理器同时宕机之后,重新选举的事务管理器无法确定该条消息是否提交成功。
总体来说 XA 方案实现简单,但是带来的问题如果放在数据一致性要求严格的场景是无法保证数据正确性的。另外事务管理器单点会带来隐患,同步阻塞模型也致使并发能力弱。
关于 TCC(Try-Confirm-Cancel)的概念,最早是由 Pat Helland 于 2007 年发表的一篇名为《Life beyond Distributed Transactions:an Apostate’s Opinion》的论文提出。 TCC 事务机制相比于上面介绍的 XA,解决了其几个缺点:
TCC 其实就是采用的补偿机制,其核心思想是:针对每个操作,都要注册一个与其对应的确认和补偿(撤销)操作。TCC 模型完全交由业务实现,每个子业务都需要实现 Try-Confirm-Cancel 三个接口,对业务侵入大,资源锁定交由业务方。
一个完整的业务活动由一个主业务服务与若干子业务服务组成:
比如一个转账操作:
基于 TCC 实现分布式事务,会将原来只需要一个接口就可以实现的逻辑拆分为 Try、Confirm、Cancel 三个接口,所以代码实现复杂度相对较高,需要在业务中写很多的补偿机制代码。
TCC将事务提交划分成两个阶段,Try即为一阶段,Confirm 和 Cancel 是二阶段并行的两个分支,二选一。从阶段划分上非常像2PC,我们是否可以说TCC是一种2PC或者2PC变种呢?
对比一下 XA 事务模型,TCC 的两阶段提交与 XA 还是有一些区别:
方案通过在事务主动发起方额外新建事务消息表,事务发起方处理业务和记录事务消息在本地事务中完成,轮询事务消息表的数据发送事务消息,事务被动方基于消息中间件消费事务消息表中的事务。
基于本地消息表的方案,每个事务发起方都需要额外新建事务消息记录表,用于记录分布式事务的消息的发生、处理状态。
事务发起方在处理完业务逻辑之后需要将当前事务保存在消息表中,之后将消息发送到消息中间件中,并将消息的状态设置为 “发送中”。
如果消息在投递过程中丢失怎么办呢?事务发起方可以设置一个定时任务主动扫描状态为 “发送中” 的消息重新投送。只有消息被业务方消费完毕返回消费成功的结果才确认成功并将消息状态改为“已发送”。
这里因为网络异常或者发送异常导致一个消息可能会被重复发送,所以要求接收方要做到幂等性,允许重复消费。
这种方案的好处就是方案简单,成本较低,实现也不复杂。
但是坏处也有很多,比如通过消息的方式延迟不好控制;本地消息表与业务耦合在一起没有做到通用性;本地消息表基于数据库来实现,有一定的瓶颈。
上面说的本地消息表的模式无法支持本地事务执行和消息发送一致性的问题,如果能在本地事务执行和发消息这两个操作上加上事务,那岂不是完美。
基于这个思路, 在 MQ 中存储消息的状态才是真理,消息生产者先把消息发送给MQ,此时消息状态为“待确认”,接着生产者去执行本地事务,如果执行成功就给MQ发送消息让他更改消息状态为 “待发送”并发送消息,如果执行失败则删除消息。
这样就保证了本地事务和消息发送一致性问题。
待发送
的消息,接着给事务发起方返回处理完成的 ACK;事务发起方收到处理完成的 ACK 之后开始执行本地事务。注意点:由于MQ通常都会保证消息能够投递成功,因此,如果业务没有及时返回ACK结果,那么就有可能造成MQ的重复消息投递问题。因此,对于消息最终一致性的方案,消息的消费者必须要对消息的消费支持幂等,不能造成同一条消息的重复消费的情况。
Saga是什么?Saga的定义是 “长时间活动的事务 ”(Long Lived Transaction,后文简称为LLT)。他是普林斯顿大学 HECTOR GARCIA-MOLINA 教授在1987年的一篇关于分布式数据库的论文中提出来的概念。
Long Lived 从字面意义上不清晰,Long 到底意味着多长?事务持续时间是一个小时、一天甚至一周吗?其实都不是,时间跨度并不重要。重要的是什么?关键的是跨系统的多次“事务”,Saga 往往由多个外部子事务构成,需要通过多次外部系统的消息交互,才能将整体事务从开始迁移到结束状态,这和我们原来常见的在一个数据库的短事务不一样。比如一个旅行的订单,是由机票、旅馆、租车三个子订单构成,都需要外部的确认,缺任何一个步骤,不能成行,这就是一个典型的 LLT。
看起来 Sage 的定义与别的分布式事务没有什么不同。分布式事务不就是多个不同的子事务构成一个整体吗?再来看看 补偿机制:
每个本地事务有相应的执行模块和补偿模块,当 Sage 事务中的任意一个本地事务出错, 可以通过调用相关事务对应的补偿方法恢复,达到事务的最终一致性。
Saga 模型是把一个分布式事务拆分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块(对应TCC 中的 Confirm 和 Cancel),当 Saga 事务中任意一个本地事务出错时,可以通过调用相关的补偿方法恢复之前的事务,达到事务最终一致性。
由于 Saga 模型中没有 Prepare 阶段,因此事务间不能保证隔离性,当多个 Saga 事务操作同一资源时,就会产生更新丢失、脏数据读取等问题,这时需要在业务层控制并发,例如:
Saga 恢复方式
Saga 支持向前和向后恢复:
虽然 Saga 和 TCC 都是补偿事务,但是由于提交阶段不同,所以两者也是有不同的:
因为也是采用补偿机制,那么必然要求服务保持幂等性,如果服务调用超时需要通过幂等性来避免多次请求带来的问题。
事务特性的满足:
原子性:Saga 协调器保证整体事务要么全部执行成功,要么全部回滚。
一致性:Sage 保证最终一致性。
持久性:Saga 将整体事务拆分成独立的本地事务,所以持久性在本地事务中很好实现。
但是隔离性 Saga 无法实现,因为大事务被拆分为多个小事务,每个事务提交的时机不同很难保证已提交的小事务不被别人可见。
目前业界提供两类 Saga 的实现方式:
一种是集中式协调的实现方式。
集中式协调方式就是通过一个 Saga 对象来追踪所有的 Saga 子任务的调用,由它来管理,追踪整个事务是否应该提交或补偿。
这种方式带来的缺点就是这种协调方式必然要与第一个Saga 事务耦合,即与业务耦合在一起。
一种是分布式实现方式。
分布式协调方式肯定就能避免耦合的问题。分布式实现的方案也很多,比如通过事件机制来实现,一条 Saga 事务链上的所有事务都订阅同一个事件,如果失败则通过失败对应的事件消息来回滚即可。
这种方式带来的好处肯定是显而易见的,但是也会有另一个问题,多个事件带来的肯定是高并发的处理,那么会不会因为多个事件处理相关的问题带来一些循环依赖的问题。
Seata(Simple Extensible Autonomous Transaction Architecture,简单可扩展自治事务框架)是 2019 年 1 月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案。
Seata 会有 4 种分布式事务解决方案,分别是 AT 模式、TCC 模式、Saga 模式和 XA 模式。
XA 模式
XA 模式是 Seata 将会开源的另一种无侵入的分布式事务解决方案,任何实现了 XA 协议的数据库都可以作为资源参与到分布式事务中,目前主流数据库,例如 MySql、Oracle、DB2、Oceanbase 等均支持 XA 协议。
XA 协议有一系列的指令,分别对应一阶段和二阶段操作。“xa start” 和 “xa end” 用于开启和结束XA 事务;“xa prepare” 用于预提交 XA 事务,对应一阶段准备;“xa commit”和“xa rollback”用于提交、回滚 XA 事务,对应二阶段提交和回滚。
在 XA 模式下,每一个 XA 事务都是一个事务参与者。分布式事务开启之后,首先在一阶段执行“xa start”、“业务 SQL”、“xa end”和 “xa prepare” 完成 XA 事务的执行和预提交;二阶段如果提交的话就执行 “xa commit”,如果是回滚则执行“xa rollback”。这样便能保证所有 XA 事务都提交或者都回滚。
XA 模式下,用户只需关注自己的“业务 SQL”,Seata 框架会自动生成一阶段、二阶段操作;XA 模式的实现如下:
在 XA 模式的一阶段,Seata 会拦截“业务 SQL”,在“业务 SQL”之前开启 XA 事务(“xa start”),然后执行“业务 SQL”,结束 XA 事务“xa end”,最后预提交 XA 事务(“xa prepare”),这样便完成 “业务 SQL”的准备操作。
执行“xa commit”指令,提交 XA 事务,此时“业务 SQL”才算真正的提交至数据库。
执行“xa rollback”指令,回滚 XA 事务,完成“业务 SQL”回滚,释放数据库锁资源。
XA 模式下,用户只需关注“业务 SQL”,Seata 会自动生成一阶段、二阶段提交和二阶段回滚操作。XA 模式和 AT 模式一样是一种对业务无侵入性的解决方案;但与 AT 模式不同的是,XA 模式将快照数据和行锁等通过 XA 指令委托给了数据库来完成,这样 XA 模式实现更加轻量化。
AT 模式
AT 模式是一种无侵入的分布式事务解决方案。在 AT 模式下,用户只需关注自己的“业务 SQL”,用户的 “业务 SQL” 作为一阶段,Seata 框架会自动生成事务的二阶段提交和回滚操作。
AT 模式的一阶段、二阶段提交和回滚
均由 Seata 框架自动生成,用户只需编写“业务 SQL”,便能轻松接入分布式事务,AT 模式是一种对业务无任何侵入的分布式事务解决方案。
TCC 模式
2019 年 3 月份,Seata 开源了 TCC 模式,该模式由蚂蚁金服贡献。TCC 模式需要用户根据自己的业务场景实现 Try、Confirm 和 Cancel 三个操作;事务发起方在一阶段 执行 Try 方式,在二阶段提交执行 Confirm 方法,二阶段回滚执行 Cancel 方法。
TCC 三个方法描述:
用户接入 TCC 模式,最重要的事情就是考虑如何将业务模型拆成 2 阶段,实现成 TCC 的 3 个方法,并且保证 Try 成功 Confirm 一定能成功。相对于 AT 模式,TCC 模式对业务代码有一定的侵入性,但是 TCC 模式无 AT 模式的全局行锁,TCC 性能会比 AT 模式高很多。
Saga 模式
Saga 模式是 Seata 即将开源的长事务解决方案,将由蚂蚁金服主要贡献。在 Saga 模式下,分布式事务内有多个参与者,每一个参与者都是一个冲正补偿服务,需要用户根据业务场景实现其正向操作和逆向回滚操作。
分布式事务执行过程中,依次执行各参与者的正向操作,如果所有正向操作均执行成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
给大家送一个小福利
资料都是免费分享的,附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。点我免费领取!!!
成功,那么分布式事务提交。如果任何一个正向操作执行失败,那么分布式事务会去退回去执行前面各参与者的逆向回滚操作,回滚已提交的参与者,使分布式事务回到初始状态。
给大家送一个小福利
[外链图片转存中…(img-KpiFQAeC-1619322805366)]
资料都是免费分享的,附送高清脑图,高清知识点讲解教程,以及一些面试真题及答案解析。送给需要的提升技术、准备面试跳槽、自身职业规划迷茫的朋友们。点我免费领取!!!
[外链图片转存中…(img-03ZZjcqM-1619322805367)]