CAS 乐观锁如果发生 ABA,可能导致什么问题?

2022-04-14 12:10:59 +08:00
 Richard14

最近在学习 java 和 redis ,学到 cas 的部分认识到了 aba 问题。想问一下在生产环境下 aba 具体可能导致什么漏洞呢?我粗略想来感觉 aba 完全不是问题啊,既然“锁”的需求是保证逻辑正常执行(比如计算某变量增加或减少若干,在多线程下不会出错),那么不管怎样 aba ,总归都不会出错不是么

1895 次点击
所在节点    问与答
19 条回复
liprais
2022-04-14 12:17:54 +08:00
状态 b 呢?
XiaoxiaoPu
2022-04-14 12:41:37 +08:00
Richard14
2022-04-14 13:32:42 +08:00
@liprais
@XiaoxiaoPu 感谢两位回复,但两位没有回答我的问题啊。比如我预设一个场景,某人账号里有 100 万资产,然后多线程操作重复若干万次随机增加或减少若干值,我感觉对于任意时刻的任意状态下,aba 不会影响这个场景,对于当时间点的计算结果都是准确的,所以 aba 有啥问题
XiaoxiaoPu
2022-04-14 13:38:34 +08:00
@Richard14
计数只是 CAS 应用的一个场景啊,这个场景没有 aba 问题不代表其他场景没有问题。
Richard14
2022-04-14 13:41:58 +08:00
@XiaoxiaoPu 所以我在问什么场景会出问题
hua123s
2022-04-14 13:42:41 +08:00
小明在提款机,提取了 50 元,因为提款机问题,有两个线程,同时把余额从 100 变为 50

线程 1 (提款机):获取当前值 100 ,期望更新为 50 ,
线程 2 (提款机):获取当前值 100 ,期望更新为 50 ,
线程 1 成功执行,线程 2 某种原因 block 了,这时,某人给小明汇款 50

线程 3 (默认):获取当前值 50 ,期望更新为 100 ,
这时候线程 3 成功执行,余额变为 100 , 线程 2 从 Block 中恢复,获取到的也是 100 ,compare 之后,继续更新余额为 50 !!! 此时可以看到,实际余额应该为 100 ( 100-50+50 ),但是实际上变为了 50 ( 100-50+50-50 )这就是 ABA 问题带来的成功提交。
XiaoxiaoPu
2022-04-14 13:43:44 +08:00
@Richard14
我上面贴的 wiki 的连接给了示例的。。。
AlkTTT
2022-04-14 13:43:51 +08:00
现在我要发起两个操作,1.支付 20w ; 2.转入 20w
由于各种问题,同时有两个线程执行这两个操作
线程 1: 查到资产为 100w ( A )
线程 2: 查到资产为 100w ( A )
支付 20w ,转入 20w ( B )
总金额 100w ( A )
问:线程 1 要不要继续操作呢?
yogogo
2022-04-14 13:48:22 +08:00
@AlkTTT 像这样操作钱包余额更新,你们是数据库 SQL 执行加减,还是在代码层面执行加减之后再更新到数据库?
vate32
2022-04-14 13:49:38 +08:00
因为 CAS 只关注预期值和当前值的比较,数值实际发生了什么变更,他并不关注。
就比如说,某人账号里有 100 万资产,在存储和提取两个过程中,数额都是一致的,但实际过程中存在有非法挪用并偷偷归还的情况,这明显是违法的。这种场景单纯通过 CAS 读取记录是无法感知到的。
AlkTTT
2022-04-14 13:51:21 +08:00
@yogogo 第二种,我们是用 reids 锁资源的,不能用 sql 操作
LeeReamond
2022-04-14 14:01:21 +08:00
@hua123s 理解了一下,可以这么描述,应该理解为小明同时在两台取款机,账号原有 100 块,小明同时发出 2 次取款 100 的请求,理论上应该有一次失败,但如果中途有人汇款 100 那么两次都会成功。

感觉在你描述的这个场景中 cas 不构成问题,毕竟它理论上确实能取出 200 块,但只能说对于一些特殊要求,比如小明实际上只想取款一次,但由于机器错误后台自动发出了两次请求,那么程序是不按期望工作的。(但是实际上小明也没亏。。似乎也没啥问题。。)
yogogo
2022-04-14 14:01:35 +08:00
@AlkTTT 不用 lock For Update 的吗?我目前是 lock For Update 加事务操作,代码加减后再更新余额
Jooooooooo
2022-04-14 14:17:28 +08:00
一般是钱这种可替换(fungible)的东西 aba 没啥问题, 扣钱又不管扣的哪部分的 100 块.
hua123s
2022-04-14 14:43:27 +08:00
@LeeReamond 我也觉得例子不好懂。
首要前提:认为乐观锁没有 ABA 问题。
线程 1:update table set balance = 50 where balance = 100;
线程 2:update table set balance = 50 where balance = 100;
当前背景下,1 ,2 是幂等的,执行多少次都无所谓啦。
但是实际上会有 ABA 问题,
线程 1 是 where balance = 100 and version = 1;
线程 2 需要执行是 where balance = 100 and version = 1; 结果执行的是 where balance = 100 and version = 2;
Richard14
2022-04-14 15:00:20 +08:00
@hua123s 乐观锁有 ABA 问题是很好理解的,只是好奇生产环境里什么场景需要注意规避 aba 问题,你在上文说的,比如小明想取款一次,但前端错误发出两次请求,后台使用乐观锁在特定条件下(刚好有 IO 发生使两次请求都成功执行),那么后台默认是执行了两次,但两次返回请求前端按正常工作逻辑只能处理第一次,导致小明亏钱。

不过这种场景怎么解决呢,直接用悲观锁吗。
AlkTTT
2022-04-14 15:12:21 +08:00
@yogogo lock For Update 只能锁单表,业务上是多表,所以在代码里发锁进行操作
hua123s
2022-04-14 15:26:06 +08:00
@Richard14 加版本号重试。
举个栗子:
select id, version from table;

if(xxx){
update table set xxx, version = version + 1 where id = 1 and version = 1; // version = verison + 1
}

如果 affect rows 不对,即其实 version 被其他线程改了,affect rows 就是 0
就好像要自旋,然后再走一遍逻辑,直到成功。
反正就那么个意思吧,我只是一个小前端具体也不是很清楚
afewok
2022-04-15 01:12:10 +08:00
感觉不一样了,一个洗过澡的女神 /男神出去放浪形骸了一圈回来又洗了个澡,你还心动嘛?
漏洞的话,我想到首次操作不及时,可能这个操作就被放弃,不期望能执行成功,但最后还是执行了。就像 ABA 总用 2 个 ATM 机,第一个 ATM 在你卡都拔了,人都跑到第二台 ATM 机了,突然扣钱成功,拿着钱算不算 离柜概不负责?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/846914

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX