V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
xpyusrs
V2EX  ›  Go 编程语言

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

  •  
  •   xpyusrs · 2021-12-13 22:55:50 +08:00 · 4641 次点击
    这是一个创建于 1075 天前的主题,其中的信息可能已经有所发展或是发生改变。
    因为要做流水, 所以得先查询余额再做加减
    语言 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 ,不要过早优化,够用就行,关键要稳
    Chad0000
        10
    Chad0000  
       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   ·   实用小工具   ·   2868 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 06:58 · PVG 14:58 · LAX 22:58 · JFK 01:58
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.