高并发情况下如何保证金额加减的一致性 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
xpyusrs
V2EX    Go 编程语言

高并发情况下如何保证金额加减的一致性

  •  
  •   small class="gray">xpyusrs 2021-12-13 22:55:50 +08:00 5044 次点击
    这是一个创建于 1412 天前的主题,其中的信息可能已经有所发展或是发生改变。
    因为要做流水, 所以得先查询余额再做加减
    语言 golang, 数据库框架 GORM
    22 条回复    2022-01-16 07:44:02 +08:00
    thinkershare
        1
    thinkershare  
       2021-12-13 23:01:06 +08:00
    开启事务, 按照需求选择乐观或者悲观并发策略. 或者考虑 EventSourcing
    Yoock
        2
    Yoock  
       2021-12-13 23:02:00 +08:00
    并发量是多大?
    niubee1
        3
    niubee1  
       2021-12-13 23:18:31 +08:00
    那要看你是在单节点实现高并发还是多节点实现高并发,如果都交给数据库那就开事务,用数据库去解决一致性问题,但是性能不容乐观,如果要多节点实现高并发,那么分布式事务的 CAP 问题,要么用现成的分布式事务方案,要么自己实现 2 阶段提交( 2PC )或者 3 阶段提交( 3PC ),或者更近一步实现 Paxos 算法。这个问题很多现成方案,真没有必要自己再想当然的搞一套了,因为 CAP 三个问题你只能搞定其中两个,别自出机杼了
    xpyusrs
        4
    xpyusrs  
    OP
       2021-12-13 23:25:25 +08:00
    @Yoock 单机, 只是考虑到这种情况, 怕偶尔出现一次数据加减不成功, 因为我在本机模拟了二个协程, 数据库却只加了一次
    xpyusrs
        5
    xpyusrs  
    OP
       2021-12-13 23:30:52 +08:00
    @niubee1 单节点的, 简单一点最好, 实在麻烦的话我先用 go 的互斥锁用着, 发现这个也能满足需求, 就是性能差了点
    niubee1
        6
    niubee1  
       2021-12-13 23:42:10 +08:00
    @xpyusrs 单节点的你要靠谱还不如直接用数据库事务好了,你用 go 的互斥锁那是硬生生的把法拉利开成了拖拉机
    ClarkAbe
        7
    ClarkAbe  
       2021-12-13 23:51:15 +08:00 via Android   1
    我没用事务也是直接 Golang 互斥锁,因为我们业务关系所以一直都是 3k 人左右具体看学校新生多不多.....然后我没人分配了一把锁....好处是他不用二次刷卡,不用返回错误,就相当于加个高级点的行锁.....而且基本也就等个 100-200ms 就好了,内存之前单独写了个 main 测试过好像整体占用就 17M 左右...坏处是没有骚操作,在其他人面前没得吹......但是稳就行....另可慢几百毫秒也不可错一分
    dayeye2006199
        8
    dayeye2006199  
       2021-12-14 02:12:22 +08:00
    最简单就是交给数据库去处理事务

    数据库事务设计出来就是处理这样的业务场景的,为啥大家第一反应想不到用它呢?
    ericgui
        9
    ericgui  
       2021-12-14 02:12:31 +08:00
    @ClarkAbe 嗯不错,不要 over engineering ,不要过早优化,够用就行,关键要稳
    ktqFDx9m2Bvfq3y4
        10
    ktqFDx9m2Bvfq3y4  
       2021-12-14 04:24:16 +08:00 via iPhone   6
    加上版本号,读出来后带上金额一起更新。如果 miss 就是有新版本,再次读。那还可以把这个版本号写入日志。

    update account set money += 5, version = 2 where id=1 and version = 1
    xuanbg
        11
    xuanbg  
       2021-12-14 08:20:43 +08:00
    如果在代码逻辑中先读出余额再加减,那么数据库事务的隔离级别就很关键,而且可能需要分布式锁。如果直接在 sql 更新语句中加减,那就不需要事务。
    securityCoding
        12
    securityCoding  
       2021-12-14 09:03:02 +08:00 via Android
    有 redis 集群吗? 用 lua 来做,数据库异步落地
    dooonabe
        13
    dooonabe  
       2021-12-14 09:28:31 +08:00
    update table set price = price - #{x} where price >= #{x}
    chenzheyu
        14
    chenzheyu  
       2021-12-14 09:50:09 +08:00
    对待钱的问题,宁肯慢也要保证不错
    timethinker
        15
    timethinker  
       2021-12-14 10:07:29 +08:00
    悲观锁:

    在一个事务内,查询余额使用 SELECT .. FOR UPDATE 获取锁,这样其他事务既无法读取,也无法写入,但是要注意死锁的情况,顺序编排要一致。

    乐观锁:

    在一个事务内,更新余额使用 UPDATE account SET ..., version = @version + 1 WHERE id = @id AND version = @version ,这样更新失败的话返回的影响行数为 0 ,可以凭此判断是否成功。
    justRua
        16
    justRua  
       2021-12-14 10:18:48 +08:00
    版本号实现乐观锁,自旋一定次数(比如 10 次),超过十次未更新成功就是失败
    BigMountain
        17
    BigMountain  
       2021-12-14 10:23:05 +08:00
    涉及金额的强一致不应该用 Redis 来保证的
    Joker123456789
        18
    Joker123456789  
       2021-12-14 10:39:37 +08:00
    并发问题就是加锁,没别的
    back0893
        19
    back0893  
       2021-12-14 13:00:51 +08:00
    加锁..最简单
    pengtdyd
        20
    pengtdyd  
       2021-12-14 13:23:16 +08:00
    分布式锁
    MoYi123
        21
    MoYi123  
       2021-12-14 18:01:00 +08:00
    update money_table set mOney= money + 100 where id = 1 returning money - 100;
    用 PostgreSQL 的 returning,可以一句 sql 完成查询和修改

    Mysql 也有类似功能,不过比较麻烦.
    akriafly01
        22
    akriafly01  
       2022-01-16 07:44:02 +08:00
    乐观锁 自旋重试
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     857 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 22:43 PVG 06:43 LAX 15:43 JFK 18:43
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86