阿里面试:Seata如何实现RC?保证事务的隔离性?

尼恩说在前面

在40岁老架构师 尼恩的读者交流群(50+)中,最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格,遇到很多很重要的面试题:

Seata 如何实现 RC ?保证事务的隔离性?

最近有小伙伴在面试阿里,又遇到了相关的面试题。小伙伴懵了,因为没有遇到过,所以支支吾吾的说了几句,面试官不满意,面试挂了。

所以,尼恩给大家做一下系统化、体系化的梳理,使得大家内力猛增,可以充分展示一下大家雄厚的 “技术肌肉”,让面试官爱到 “不能自已、口水直流”,然后实现”offer直提”。

当然,这道面试题,以及参考答案,也会收入咱们的 《尼恩Java面试宝典PDF》V167版本,供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》的PDF,请到文末公号【技术自由圈】获取

文章目录

    • 尼恩说在前面
    • Seata 事务隔离级别解读
    • Seata 全局锁实现 RC
    • Seata事务 + 独立事务 场景的 脏读、脏写
    • 如何防止脏读/脏写
      • 方案一:独立事务执行时加 @GlobalTransactional注解
      • 方案二:独立事务业务二执行时加 @GlobalLock注解
      • 方案三:业务二执行时加 @GlobalLock 注解 + select for update语句
    • 说在最后:有问题找老架构取经
    • 尼恩技术圣经系列PDF

Seata 事务隔离级别解读

先来回顾一下,数据库事务的隔离级别,目前数据库事务的隔离级别一共有 4 种,由低到高分别为:

事务的四个隔离级别:

  • 未提交读(READ UNCOMMITTED):所有事务都可以看到其他事务未提交的修改。一般很少使用;
  • 读已提交(READ COMMITTED):Oracle默认隔离级别,事务之间只能看到彼此已提交的变更修改;
  • 可重复读(REPEATABLE READ):MySQL默认隔离级别,同一事务中的多次查询会看到相同的数据行;可以解决不可重复读,但可能出现幻读;
  • 可串行化(SERIALIZABLE):最高的隔离级别,事务串行的执行,前一个事务执行完,后面的事务会执行。读取每条数据都会加锁,会导致大量的超时和锁争用问题;

阿里面试:Seata如何实现RC?保证事务的隔离性?_第1张图片

数据库一般默认的隔离级别为 读已提交 RC ,比如 Oracle,

也有一些数据的默认隔离级别为 可重复读 RR,比如 Mysql。“可重复读”(Repeatable Read)这个级别确保了对同一字段的多次读取结果是一致的,除非数据是被本身事务自己所修改。它能够防止脏读、不可重复读,但可能会遇到幻读的情况。

参考 文章:

Mysql如何实现RR级隔离时,不会幻读?

MySQL默认的Repeatable Read隔离级别,被改成了RC , 具体请参考 《尼恩Java 面试宝典》 MYSQL 专题:

阿里面试:Seata如何实现RC?保证事务的隔离性?_第2张图片

一般而言,数据库的读已提交(READ COMMITTED)能够满足业务绝大部分场景了。

但是 seata 却做不到 RC, 只能做到RU。

Seata 的事务是一个全局事务,它包含了若干个分支本地事务,在全局事务执行过程中(全局事务还没执行完),某个本地事务提交了,如果 Seata 没有采取任务措施,则会导致已提交的本地事务被读取,造成脏读,如果数据在全局事务提交前已提交的本地事务被修改,则会造成脏写。

由此可以看出,传统意义的脏读是读到了未提交的数据,Seata 脏读是读到了全局事务下未提交的数据,全局事务可能包含多个本地事务,某个本地事务提交了不代表全局事务提交了。

从这个意义上说, seata 的隔离级别是 读未提交。 而实际上,这当中又有绝大多数的应用场景,实际上工作在读未提交的隔离级别下同样没有问题。

如何实现 读已提交?

默认情况下,Seata 的全局事务是工作在读未提交隔离级别的,保证绝大多数场景的高效性。

但是,在极端场景下,应用如果需要达到全局的读已提交,Seata 也提供了全局锁机制实现全局事务读已提交 RC。

Seata 全局锁实现 RC

AT 模式下,会使用 Seata 内部数据源代理 DataSourceProxy,全局锁的实现就是隐藏在这个代理中。

接下来看看, seata at分别在执行、提交的过程都做了什么。

阿里面试:Seata如何实现RC?保证事务的隔离性?_第3张图片

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

  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如何实现RC?保证事务的隔离性?_第4张图片

业务一开启seata 分布式事务,其中包含分支事务A(修改 A)和分支事务 B(修改 B),

业务二开启本地事务,仅仅修改 A,

其中业务一执行分支事务 A 先获取本地锁,

业务二则等待业务一执行完分支事务 A 之后,获得本地锁修改 A 并提交,

业务一在执行分支事务时发生异常了,由于分支事务 A 的数据被业务二修改,导致业务一的全局事务无法回滚,需要人工干预。

这里发生了两种错误的现象:

  • 业务2发生了脏读
  • 业务1 发生了脏写

如何防止脏读 、脏写? 帮助 Seata+ 独立事务 场景实现 RC。

方案有两种:

  • 独立事务 升级为分布式事务
  • 业务二查询 A 时加 @GlobalLock 注解 + select for update语句

如何防止脏读/脏写

Seata AT 模式的脏读是指在全局事务未提交前,被其它业务读到已提交的分支事务的数据,本质上是Seata默认的全局事务是读未提交。

那么怎么避免脏读、脏写现象呢?

方案一:独立事务执行时加 @GlobalTransactional注解

阿里面试:Seata如何实现RC?保证事务的隔离性?_第5张图片

业务二在执行全局事务过程中,分支事务 A 提交前注册分支事务获取全局锁时,发现业务业务一全局锁还没执行完,因此业务二提交不了,抛异常回滚,所以不会发生脏写。

方案二:独立事务业务二执行时加 @GlobalLock注解

全局事务 很重。

因此,在不需要全局事务、而又需要检查全局锁避免脏读脏写的场景,可以使用@GlobalLock注解实现隔离的功能,@GlobalLock是一个更加轻量的操作。

阿里面试:Seata如何实现RC?保证事务的隔离性?_第6张图片

与 @GlobalTransactional注解效果类似,只不过 业务二不需要开启全局事务,只在本地事务提交前,检查全局锁是否存在。 如果存在,就抛出异常,从而本地事务回滚。

@GlobalTransactional注解 开启全局事务是一个比较重的操作,需要向 TC 发起开启全局事务等 RPC 过程,而@GlobalLock注解只会在执行过程中查询全局锁是否存在,不会去开启全局事务,

方案三:业务二执行时加 @GlobalLock 注解 + select for update语句

如果独立事务希望最终能够 提交, 在发现全局锁的过程中, 独立事务希望等待和重试,则可以使用下面的方案:

加 @GlobalLock 注解 + select for update语句

阿里面试:Seata如何实现RC?保证事务的隔离性?_第7张图片

如果加了select for update语句,则会在 update 前检查全局锁是否存在,只有当全局锁释放之后,业务二才能开始执行 updateA 操作。

加select for update语句会在执行 SQL 前检查全局锁是否存在,只有当全局锁完成之后,才能继续执行 SQL,这样就彻底防止了脏读+脏写, 也能保护 独立事务不会 直接回滚。

所以@GlobalLock 注解 加 select for update 也有个好处,就是可以重试。

此面试题,收录于尼恩编著 《Seata 学习圣经》,PDF 全文可以找尼恩获取。

阿里面试:Seata如何实现RC?保证事务的隔离性?_第8张图片

阿里面试:Seata如何实现RC?保证事务的隔离性?_第9张图片

说在最后:有问题找老架构取经

以上的内容,如果大家能对答如流,如数家珍,基本上 面试官会被你 震惊到、吸引到。

最终,让面试官爱到 “不能自已、口水直流”。offer, 也就来了。

在面试之前,建议大家系统化的刷一波 5000页《尼恩Java面试宝典PDF》,里边有大量的大厂真题、面试难题、架构难题。很多小伙伴刷完后, 吊打面试官, 大厂横着走。

在刷题过程中,如果有啥问题,大家可以来 找 40岁老架构师尼恩交流。

另外,如果没有面试机会,可以找尼恩来改简历、做帮扶。

遇到职业难题,找老架构取经, 可以省去太多的折腾,省去太多的弯路。

尼恩指导了大量的小伙伴上岸,前段时间,刚指导一个40岁+被裁小伙伴,拿到了一个年薪100W的offer。

狠狠卷,实现 “offer自由” 很容易的, 前段时间一个武汉的跟着尼恩卷了2年的小伙伴, 在极度严寒/痛苦被裁的环境下, offer拿到手软, 实现真正的 “offer自由” 。

尼恩技术圣经系列PDF

  • 《NIO圣经:一次穿透NIO、Selector、Epoll底层原理》
  • 《Docker圣经:大白话说Docker底层原理,6W字实现Docker自由》
  • 《K8S学习圣经:大白话说K8S底层原理,14W字实现K8S自由》
  • 《SpringCloud Alibaba 学习圣经,10万字实现SpringCloud 自由》
  • 《大数据HBase学习圣经:一本书实现HBase学习自由》
  • 《大数据Flink学习圣经:一本书实现大数据Flink自由》
  • 《响应式圣经:10W字,实现Spring响应式编程自由》
  • 《Go学习圣经:Go语言实现高并发CRUD业务开发》

……完整版尼恩技术圣经PDF集群,请找尼恩领取

《尼恩 架构笔记》《尼恩高并发三部曲》《尼恩Java面试宝典》PDF,请到下面公号【技术自由圈】取↓↓↓

你可能感兴趣的:(面试,技术圣经,面试,职场和发展,架构,微服务,中间件,java)