阿里面试: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如何实现RR级隔离时,不会幻读?

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分别在执行、提交的过程都做了什么。

如上所示,一个分布式事务的锁获取流程是这样的

  1. 先获取到本地锁即可修改本地数据,但不能直接提交本地事务
  2. 为何不能直接提交本地事务呢?因为还未获取全局锁
  3. 本地锁和全局锁都获得了,才具有全局事务写隔离的保障,本地事务才可以提交,之后释放本地锁
  4. 当分布式事务执行 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自由” 。

技术自由的实现路径:

实现你的 架构自由:

吃透8图1模板,人人可以做架构

10Wqps评论中台,如何架构?B站是这么做的!!!

阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了

峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?

100亿级订单怎么调度,来一个大厂的极品方案

2个大厂 100亿级 超大流量 红包 架构方案

… 更多架构文章,正在添加中

实现你的 响应式 自由:

响应式圣经:10W字,实现Spring响应式编程自由

这是老版本 《Flux、Mono、Reactor 实战(史上最全)

实现你的 spring cloud 自由:

Spring cloud Alibaba 学习圣经》 PDF

分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)

一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)

实现你的 linux 自由:

Linux命令大全:2W多字,一次实现Linux自由

实现你的 网络 自由:

TCP协议详解 (史上最全)

网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!

实现你的 分布式锁 自由:

Redis分布式锁(图解 - 秒懂 - 史上最全)

Zookeeper 分布式锁 - 图解 - 秒懂

实现你的 王者组件 自由:

队列之王: Disruptor 原理、架构、源码 一文穿透

缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)

缓存之王:Caffeine 的使用(史上最全)

Java Agent 探针、字节码增强 ByteBuddy(史上最全)

实现你的 面试题 自由:

4800页《尼恩Java面试宝典 》 40个专题

免费获取11个技术圣经PDF:

posted @ 2024-02-05 09:33  疯狂创客圈  阅读(306)  评论(0编辑  收藏  举报