V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
Richard14
V2EX  ›  问与答

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

  •  
  •   Richard14 · 2022-04-14 12:10:59 +08:00 · 1739 次点击
    这是一个创建于 733 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    19 条回复    2022-04-15 01:12:10 +08:00
    liprais
        1
    liprais  
       2022-04-14 12:17:54 +08:00 via iPhone
    状态 b 呢?
    XiaoxiaoPu
        2
    XiaoxiaoPu  
       2022-04-14 12:41:37 +08:00
    Richard14
        3
    Richard14  
    OP
       2022-04-14 13:32:42 +08:00
    @liprais
    @XiaoxiaoPu 感谢两位回复,但两位没有回答我的问题啊。比如我预设一个场景,某人账号里有 100 万资产,然后多线程操作重复若干万次随机增加或减少若干值,我感觉对于任意时刻的任意状态下,aba 不会影响这个场景,对于当时间点的计算结果都是准确的,所以 aba 有啥问题
    XiaoxiaoPu
        4
    XiaoxiaoPu  
       2022-04-14 13:38:34 +08:00
    @Richard14
    计数只是 CAS 应用的一个场景啊,这个场景没有 aba 问题不代表其他场景没有问题。
    Richard14
        5
    Richard14  
    OP
       2022-04-14 13:41:58 +08:00
    @XiaoxiaoPu 所以我在问什么场景会出问题
    hua123s
        6
    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
        7
    XiaoxiaoPu  
       2022-04-14 13:43:44 +08:00
    @Richard14
    我上面贴的 wiki 的连接给了示例的。。。
    AlkTTT
        8
    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
        9
    yogogo  
       2022-04-14 13:48:22 +08:00
    @AlkTTT 像这样操作钱包余额更新,你们是数据库 SQL 执行加减,还是在代码层面执行加减之后再更新到数据库?
    vate32
        10
    vate32  
       2022-04-14 13:49:38 +08:00
    因为 CAS 只关注预期值和当前值的比较,数值实际发生了什么变更,他并不关注。
    就比如说,某人账号里有 100 万资产,在存储和提取两个过程中,数额都是一致的,但实际过程中存在有非法挪用并偷偷归还的情况,这明显是违法的。这种场景单纯通过 CAS 读取记录是无法感知到的。
    AlkTTT
        11
    AlkTTT  
       2022-04-14 13:51:21 +08:00
    @yogogo 第二种,我们是用 reids 锁资源的,不能用 sql 操作
    LeeReamond
        12
    LeeReamond  
       2022-04-14 14:01:21 +08:00
    @hua123s 理解了一下,可以这么描述,应该理解为小明同时在两台取款机,账号原有 100 块,小明同时发出 2 次取款 100 的请求,理论上应该有一次失败,但如果中途有人汇款 100 那么两次都会成功。

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

    不过这种场景怎么解决呢,直接用悲观锁吗。
    AlkTTT
        17
    AlkTTT  
       2022-04-14 15:12:21 +08:00
    @yogogo lock For Update 只能锁单表,业务上是多表,所以在代码里发锁进行操作
    hua123s
        18
    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
        19
    afewok  
       2022-04-15 01:12:10 +08:00
    感觉不一样了,一个洗过澡的女神 /男神出去放浪形骸了一圈回来又洗了个澡,你还心动嘛?
    漏洞的话,我想到首次操作不及时,可能这个操作就被放弃,不期望能执行成功,但最后还是执行了。就像 ABA 总用 2 个 ATM 机,第一个 ATM 在你卡都拔了,人都跑到第二台 ATM 机了,突然扣钱成功,拿着钱算不算 离柜概不负责?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1020 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 22:10 · PVG 06:10 · LAX 15:10 · JFK 18:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.