seata at模式下的数据一致性

对于分布式系统,事务一致性的处理是比较困难的,幸好借助seata能够快速地解决分布式事务的问题。由于seata at模式对系统的侵入性小,操作上也比较简单,所以项目中大部分情况都是使用该模式。

那 seata at模式对事务一致性是如何处理?是否该方式就能完美解决分布式事务的所有问题?下面是我的一些理解和思考。

seata at模式是通过两阶段提交的机制:

一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。

二阶段:

  • 提交异步化,非常快速地完成。
  • 回滚通过一阶段的回滚日志进行反向补偿。

写隔离

  • 一阶段本地事务提交前,需要确保先拿到 全局锁
  • 拿不到 全局锁 ,不能提交本地事务。
  • 全局锁 的尝试被限制在一定范围内,超出范围将放弃,并回滚本地事务,释放本地锁。

以一个示例来说明:

两个全局事务 tx1 和 tx2,分别对 a 表的 m 字段进行更新操作,m 的初始值 1000。

tx1 先开始,开启本地事务,拿到本地锁,更新操作 m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 tx2 后开始,开启本地事务,拿到本地锁,更新操作 m = 900 - 100 = 800。本地事务提交前,尝试拿该记录的 全局锁 ,tx1 全局提交前,该记录的全局锁被 tx1 持有,tx2 需要重试等待 全局锁

tx1 二阶段全局提交,释放 全局锁 。tx2 拿到 全局锁 提交本地事务。

如果 tx1 的二阶段全局回滚,则 tx1 需要重新获取该数据的本地锁,进行反向补偿的更新操作,实现分支的回滚。

此时,如果 tx2 仍在等待该数据的 全局锁,同时持有本地锁,则 tx1 的分支回滚会失败。分支的回滚会一直重试,直到 tx2 的 全局锁 等锁超时,放弃 全局锁 并回滚本地事务释放本地锁,tx1 的分支回滚最终成功。

因为整个过程 全局锁 在 tx1 结束前一直是被 tx1 持有的,所以不会发生 脏写 的问题。

以上是两个全局事务的情况,seata at模式是可以做到写隔离的。但如果是一个全局事务tx1和一个本地事务tx3呢?也是tx1先开始开启本地事务,更新操作m = 1000 - 100 = 900。本地事务提交前,先拿到该记录的 全局锁 ,本地提交释放本地锁。 此时tx3更新操作m = 900 - 100 = 800,并提交事务。tx1二阶段全局提交或者全局回滚,就会发生脏写。

读隔离

在数据库本地事务隔离级别 读已提交(Read Committed) 或以上的基础上,Seata(AT 模式)的默认全局隔离级别是 读未提交(Read Uncommitted)

如果应用在特定场景下,必需要求全局的 读已提交 ,目前 Seata 的方式是通过 SELECT FOR UPDATE 语句的代理。

SELECT FOR UPDATE 语句的执行会申请 全局锁 ,如果 全局锁 被其他事务持有,则释放本地锁(回滚 SELECT FOR UPDATE 语句的本地执行)并重试。这个过程中,查询是被 block 住的,直到 全局锁 拿到,即读取的相关数据是 已提交 的,才返回。

出于总体性能上的考虑,Seata 目前的方案并没有对所有 SELECT 语句都进行代理,仅针对 FOR UPDATE 的 SELECT 语句。

根据以上的说明,通常情况下Seata(AT 模式)的全局隔离级别是读未提交(Read Uncommitted),是会有几率发生脏读的。沿用上面的示例,全局事务tx1在一阶段本地提交释放本地锁后,二阶段提交或回滚前,进行本地查询,如果tx1二阶段回滚,本地查询的就是脏数据。

高并发

seate at模式通过全局锁和本地锁来实现事务一致性,会对性能造成影响。具体在高并发下表现如何呢,借用网上的一组数据:

在单机环境下使用 jmeter 进行压测,

Seata AT模式:在100并发下成功数是 23、29,成功率不到30%,在1000并发下成功数是276、260,成功率同样是不到30%

Seata TCC模式:在100并发下成功数 98、80,500并发下成功数是304,在1000并发下成功数是481

如何防止脏写?

1、业务二执行时加 @GlobalTransactional注解:

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

2、业务二执行时加 @GlobalLock注解:

与 @GlobalTransactional注解效果类似,只不过不需要开启全局事务,只在本地事务提交前,检查全局锁是否存在。

2、业务二执行时加 @GlobalLock 注解 + select for update语句:

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

如果单单是 transactional,那么就有可能会出现脏写,根本原因是没有 Globallock 注解时,不会检查全局锁,这可能会导致另外一个全局事务回滚时,发现某个分支事务被脏写了。所以加 select for update 也有个好处,就是可以重试。

如何防止脏读

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

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

业务二查询 A 时加 @GlobalLock 注解 + select for update语句:

加select for update语句会在执行 SQL 前检查全局锁是否存在,只有当全局锁完成之后,才能继续执行 SQL,这样就防止了脏读。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值