全局锁、本地锁、读隔离、写隔离的概念
元始天尊回去之后发愤图强,终于吃透了一些概念,为了保险,还找夏侯傲天借了一个道具,信心满满来找傅老大讨教。
傅青阳一身西装笔挺地坐在宽大的办公桌后,头都不抬地问到:都弄清楚了?
元始:是的老大,全局锁就是多个事务修改同一行数据的时候,只能串行化,一个事务完成后下一个事务才能获取到锁;本地锁就很简单了,只是锁住了当前数据库的数据行而已;读隔离其实就是隔离级别中的读取问题,比如你能读取到其他未提交的全局事务的数据,就是读未提交(默认的),只能读取其他已经提交过的分布式事务的数据,就是读已提交;写隔离在实现了全局锁之后也就实现了写隔离,因为修改同一行数据都只能串行化了。
傅青阳:嗯,看来下了一番苦工。
元始一脸得意:整个Seata的源码我都了如指掌。
傅青阳:那好,先讲讲Seata的三种模式。
元始:好的,首先 Seata 默认提供的是AT模式事务,这个模式适合纯关系型数据库操作。
TCC 模式适合关系型数据库 + NoSQL 数据库的组合操作。
Saga 模式适合旧系统改造分布式事务实现。
XA 模式适合单个服务多个数据库实例的操作,依赖于数据库特性。
附加链接:官方文档 。
写隔离是如何实现的
傅青阳:先讲讲 AT 模式的写隔离和读隔离。
元始:比如有 2 个事务,需要同时修改同一行记录,比如事务1 是 update t_user set age=30 where id=1
,事务2 是 update t_user set age=29 where id=1
,如果事务1 先获取到 id=1
这行数据的全局锁,那么事务2 就处于重试等待中,直到事务1 提交或回滚的时候释放全局锁之后,事务2 的重试次数还未耗尽,这个时候就能重试成功拿到全局锁,然后继续执行业务。这样的写隔离可以保证不会出现脏写,画图如下:
读隔离是如何实现的
元始:Seata 默认的读隔离级别是读未提交,由于我们的微服务都是每个服务对应一个关系型数据库,关系型数据库的作用范围就是本数据库中,所以一旦本数据库提交事务之后,虽然全局事务未提交,但是其他事务是可以直接读取到修改后的值。但是如果使用 SELECT FOR UPDATE
进行查询的话,Seata 内部会先获取全局锁,然后再读取数据,这样可以保证读已提交。
举个例子,比如原始值 age=29
,事务1是在 A服务执行 update t_user set age=30 where id=1
,然后继续调用B服务,但是还没完成这个全局事务,然后事务2 在 A服务执行select * from t_user where id=1 for update
,这个时候由于事务1 还没全局提交,所以事务2 无法获取全局锁,继而完成查询,需要不断重试获取全局锁,直到获取成功或重试次数耗尽。这个就是读已提交的实现,画图如下:
SQL 的执行机制
元始:最后讲一下SQL的执行机制。
比如上面将要执行的业务 SQL update t_user set age=30 where id=1
,那么在执行前就会先解析一下 SQL,取出里面的 where
条件,然后生成 SQL select * from t_user where id=1
,将执行返回的结果作为前镜像数据,然后执行业务 SQL,完成后再次执行 select * from t_user where id=1
,将返回的结果作为后镜像数据,镜像数据都会写入 undolog 表,最后提交本地事务的时候是这 3 条 SQL 都在一个事务内提交的。全局事务提交成功的时候会异步清理 undolog 数据,回滚的话就是将 undolog 表中记录的前镜像数据写回业务表。画图如下:
TCC 模式简单介绍
傅青阳诧异地看了一眼:继续,那 TCC 模式和 Saga 模式呢?
元始:TCC 模式也是两阶段提交,但是 commit/rollback 的处理都需要自己实现,而 AT 模式是 Seata 这个框架内部通过前后镜像数据帮我们实现的。TCC 的执行,需要我们实现 prepare、commit、rollback 3 个逻辑接口。
还是举前面那个订单的例子,下订单操作需要先调用用户服务扣减金额,那么扣减金额这个业务操作需要实现 3 个接口,prepare 接口除了需要从用户余额中扣减金额外,还需要另外存储已扣减金额的字段,commit 接口可以将这个已扣减金额的字段清零,rollback 接口则需要将已扣减金额加回去到用户余额中。
同样道理,锁定库存也需要 3 个接口,prepare 接口减少实际库存和额外保存已扣减的库存数,commit 接口可以将这个已扣减库存数的字段清零,rollback 接口则需要将已扣减库存数加回去到实际库存中。画图如下:
Saga 模式简单介绍
元始:Saga 模式分为正常流程和逆向补偿流程,基于状态机模式引擎,通过事件的发布和订阅驱动,如果没有异常则直接走完正常流程,如果某个节点出现异常则走逆向补偿流程。
老大怎么样,我可是费了老大劲才搞清楚的。
傅青阳面无波澜地说:全局事务如何初始化、分支事务如何关联、全局锁如何获取呢?
内心:小样,还治不了你吗?
元始愣了一下,幸好我早有准备,瞬间掏出道具 学士之帽:再来三百回合。
画外音提示:
学习分析源码就不是从一个个包开始分析,而是应该从官方例子 Demo 入手,层层递进,先剖析主干流程,再看看细节问题,保证让你既能从全局把握主干逻辑,又能彻底搞懂框架的内部实现,学习人家的优秀设计思想。