阿里面试:Seata 如何实现 RC ?保证事务的隔离性?
文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 :
免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备
免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,完成职业升级, 薪酬猛涨!加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷1)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷2)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 经典图书:《Java高并发核心编程(卷3)加强版》 面试必备 + 大厂必备 +涨薪必备 加尼恩免费领
免费赠送 资源宝库: Java 必备 百度网盘资源大合集 价值>10000元 加尼恩领取
阿里面试:Seata 如何实现 RC ?保证事务的隔离性?
尼恩说在前面
在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:
Seata 如何实现 RC ?保证事务的隔离性?
最近有小伙伴在面试阿里,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。
所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。
当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V167版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。
《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】获取
本文目录
Seata 事务隔离级别解读
先来回顾一下,数据库事务的隔离级别,目前数据库事务的隔离级别一共有 4 种,由低到高分别为:
事务的四个隔离级别:
- 未提交读(READ UNCOMMITTED):所有事务都可以看到其他事务未提交的修改。一般很少使用;
- 读已提交(READ COMMITTED):Oracle默认隔离级别,事务之间只能看到彼此已提交的变更修改;
- 可重复读(REPEATABLE READ):MySQL默认隔离级别,同一事务中的多次查询会看到相同的数据行;可以解决不可重复读,但可能出现幻读;
- 可串行化(SERIALIZABLE):最高的隔离级别,事务串行的执行,前一个事务执行完,后面的事务会执行。读取每条数据都会加锁,会导致大量的超时和锁争用问题;
数据库一般默认的隔离级别为 读已提交 RC ,比如 Oracle,
也有一些数据的默认隔离级别为 可重复读 RR,比如 Mysql。"可重复读"(Repeatable Read)这个级别确保了对同一字段的多次读取结果是一致的,除非数据是被本身事务自己所修改。它能够防止脏读、不可重复读,但可能会遇到幻读的情况。
参考 文章:
MySQL默认的Repeatable Read隔离级别,被改成了RC , 具体请参考 《尼恩Java 面试宝典》 MYSQL 专题:
一般而言,数据库的读已提交(READ COMMITTED)能够满足业务绝大部分场景了。
但是 seata 却做不到 RC, 只能做到RU。
Seata 的事务是一个全局事务,它包含了若干个分支本地事务,在全局事务执行过程中(全局事务还没执行完),某个本地事务提交了,如果 Seata 没有采取任务措施,则会导致已提交的本地事务被读取,造成脏读,如果数据在全局事务提交前已提交的本地事务被修改,则会造成脏写。
由此可以看出,传统意义的脏读是读到了未提交的数据,Seata 脏读是读到了全局事务下未提交的数据,全局事务可能包含多个本地事务,某个本地事务提交了不代表全局事务提交了。
从这个意义上说, seata 的隔离级别是 读未提交。 而实际上,这当中又有绝大多数的应用场景,实际上工作在读未提交的隔离级别下同样没有问题。
如何实现 读已提交?
默认情况下,Seata 的全局事务是工作在读未提交隔离级别的,保证绝大多数场景的高效性。
但是,在极端场景下,应用如果需要达到全局的读已提交,Seata 也提供了全局锁机制实现全局事务读已提交 RC。
Seata 全局锁实现 RC
AT 模式下,会使用 Seata 内部数据源代理 DataSourceProxy,全局锁的实现就是隐藏在这个代理中。
接下来看看, seata at分别在执行、提交的过程都做了什么。
如上所示,一个分布式事务的锁获取流程是这样的
- 先获取到本地锁即可修改本地数据,但不能直接提交本地事务
- 为何不能直接提交本地事务呢?因为还未获取全局锁
- 本地锁和全局锁都获得了,才具有全局事务写隔离的保障,本地事务才可以提交,之后释放本地锁
- 当分布式事务执行 2 阶段提交后释放全局锁;这样就可以让其它事务获取全局锁,并提交它们对本地数据的修改了。
至此已明确 Seata 通过将传统 XA 方案的 2 个阶段的本地 DB 锁,拆分成了 本地锁(DB 锁)+ 全局锁的模式 。
如果一个应用所有的事务都用Seata 分布式事务, 性能一定是很低的。 具体请参见 尼恩《Seata 学习圣经 的性能部分内容》。
所以,在大部分业务场景,并不是所有的 操作都要用分布式操作,,并不是所有的 操作都要用分布式事务, 所以生产场景基本上会是 Seata事务 + 独立事务 组合模式。
那么,在 Seata事务 + 独立事务 场景下,Seata 的全局锁对于 外部的 独立事务失效了,对于外部事务,本质上是Seata默认的全局事务是读未提交,能读到seata 未提交(Seata分支已提交的分支事务的数据)的数据,于是,本地事务会发生 脏读。
如何实现 Seata事务 + 独立事务 场景的 RC? 如何解决 独立本地事务的 脏读问题?
Seata事务 + 独立事务 场景的 脏读、脏写
AT 模式是一种非侵入式的分布式事务解决方案,Seata 在内部做了对数据库操作的代理层,我们使用 Seata AT 模式时,实际上用的是 Seata 自带的数据源代理 DataSourceProxy。
Seata 在这层DataSourceProxy 代理中加入了很多逻辑,比如:
- 插入回滚 undo_log 日志,
- 检查全局锁等。
为什么要检查全局锁呢,这是由于 Seata AT 模式的事务隔离是建立在支事务的本地隔离级别基础之上的,
在数据库本地隔离级别读已提交或以上的前提下,Seata 设计了由TC维护的全局写排他锁,来保证事务间的写隔离,实现分布式事务的 RC。
先来看一下Seata事务 + 独立事务 场景,使用 Seata AT 模式是怎么产生脏读的:
业务一开启seata 分布式事务,其中包含分支事务A(修改 A)和分支事务 B(修改 B),
业务二开启本地事务,仅仅修改 A,
其中业务一执行分支事务 A 先获取本地锁,
业务二则等待业务一执行完分支事务 A 之后,获得本地锁修改 A 并提交,
业务一在执行分支事务时发生异常了,由于分支事务 A 的数据被业务二修改,导致业务一的全局事务无法回滚,需要人工干预。
这里发生了两种错误的现象:
- 业务2发生了脏读
- 业务1 发生了脏写
如何防止脏读 、脏写? 帮助 Seata+ 独立事务 场景实现 RC。
方案有两种:
- 独立事务 升级为分布式事务
- 业务二查询 A 时加 @GlobalLock 注解 + select for update语句
如何防止脏读/脏写
Seata AT 模式的脏读是指在全局事务未提交前,被其它业务读到已提交的分支事务的数据,本质上是Seata默认的全局事务是读未提交。
那么怎么避免脏读、脏写现象呢?
方案一:独立事务执行时加 @GlobalTransactional注解:
业务二在执行全局事务过程中,分支事务 A 提交前注册分支事务获取全局锁时,发现业务业务一全局锁还没执行完,因此业务二提交不了,抛异常回滚,所以不会发生脏写。
方案二:独立事务业务二执行时加 @GlobalLock注解:
全局事务 很重。
因此,在不需要全局事务、而又需要检查全局锁避免脏读脏写的场景,可以使用@GlobalLock注解实现隔离的功能,@GlobalLock是一个更加轻量的操作。
与 @GlobalTransactional注解效果类似,只不过 业务二不需要开启全局事务,只在本地事务提交前,检查全局锁是否存在。 如果存在,就抛出异常,从而本地事务回滚。
@GlobalTransactional注解 开启全局事务是一个比较重的操作,需要向 TC 发起开启全局事务等 RPC 过程,而@GlobalLock注解只会在执行过程中查询全局锁是否存在,不会去开启全局事务,
方案三:业务二执行时加 @GlobalLock 注解 + select for update语句:
如果独立事务希望最终能够 提交, 在发现全局锁的过程中, 独立事务希望等待和重试,则可以使用下面的方案:
加 @GlobalLock 注解 + select for update语句
如果加了select for update语句,则会在 update 前检查全局锁是否存在,只有当全局锁释放之后,业务二才能开始执行 updateA 操作。
加select for update语句会在执行 SQL 前检查全局锁是否存在,只有当全局锁完成之后,才能继续执行 SQL,这样就彻底防止了脏读+脏写, 也能保护 独立事务不会 直接回滚。
所以@GlobalLock 注解 加 select for update 也有个好处,就是可以重试。
此面试题,收录于尼恩编著 《Seata 学习圣经》,PDF 全文可以找尼恩获取。
说在最后:有问题找老架构取经
以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。
最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。
在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。很多小伙伴刷完后, 吊打面试官, 大厂横着走。
在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。
另外,如果没有面试机会,可以找尼恩来改简历、做帮扶。
遇到职业难题,找老架构取经, 可以省去太多的折腾,省去太多的弯路。
尼恩指导了大量的小伙伴上岸,前段时间,刚指导一个40岁+被裁小伙伴,拿到了一个年薪100W的offer。
狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” 。
技术自由的实现路径:
实现你的 架构自由:
《阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了》
《峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?》
… 更多架构文章,正在添加中
实现你的 响应式 自由:
这是老版本 《Flux、Mono、Reactor 实战(史上最全)》
实现你的 spring cloud 自由:
《Spring cloud Alibaba 学习圣经》 PDF
《分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)》
实现你的 linux 自由:
实现你的 网络 自由:
《网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!》
实现你的 分布式锁 自由:
实现你的 王者组件 自由:
《队列之王: Disruptor 原理、架构、源码 一文穿透》
《缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)》
《Java Agent 探针、字节码增强 ByteBuddy(史上最全)》