V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Renco
V2EX  ›  Java

关于分布式系统中的并发问题

  •  
  •   Renco · 2020-01-16 16:11:35 +08:00 · 7151 次点击
    这是一个创建于 1798 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问下各位,在分布式系统中,我的模块中的接口属于被调用的,用户的操作不会直接涉及我的模块,一般由其他模块调用我。这种时候我要如何考虑我模块中的并发问题。

    我现在负责的会员模板有一个余额消费的功能,由订单模块,调用我会员消费,我只需要将订单模块传给我的消费金额和相关会员账号进行 扣款处理即可,项目经理让我排查一下其中出现的并发场景。但是我不太清楚分布式系统中的,并发问题是怎么产生的。

    40 条回复    2020-01-17 12:03:32 +08:00
    xmh51
        1
    xmh51  
       2020-01-16 16:15:23 +08:00
    别人同一个订单重复调用怎么办?
    fxxkgw
        2
    fxxkgw  
       2020-01-16 16:16:22 +08:00   ❤️ 1
    对于余额这种感觉说的像是幂等性 至于并发,他是不是只想说流量大的情况下,理论上并发这问题上游有负载,对业务来说压力大扩容就完事了。
    matepi
        3
    matepi  
       2020-01-16 16:17:24 +08:00 via iPhone
    要你的消费模块做一致性加锁,防止多个调用者并发调你消费的时候超余额消费呗
    加锁,tcc 之类的呗
    SpencerCJH
        4
    SpencerCJH  
       2020-01-16 16:35:03 +08:00
    分布式事务一致性、幂等性、分布式锁 了解一下 :D
    wellsc
        5
    wellsc  
       2020-01-16 16:37:01 +08:00
    流量激增,怎么平滑无痛扩容
    xetv
        6
    xetv  
       2020-01-16 16:39:58 +08:00   ❤️ 2
    同一个订单请求用测试工具并发请求 n 次看余额会不会变成负数
    bobuick
        7
    bobuick  
       2020-01-16 16:42:54 +08:00   ❤️ 1
    单就扣款这个动作看,扣同一个账号下的款,是不是要求就是不能扣成负数?
    那就 cas 乐观锁方式处理,或者 db 直接开 select for update 锁

    你们扯上 tcc 是什么鬼,lz 只是要保障自己的模块安全就可以了.

    要 tcc 之类的,就要联动你的订单模块一起从整体上来看架构了。你们这个没个架构师来把关么,不要随便用 tcc
    0NF09LJPS51k57uH
        8
    0NF09LJPS51k57uH  
       2020-01-16 16:43:01 +08:00   ❤️ 1
    4 楼正解,5 楼不知所云。

    每个订单的扣款要考虑幂等,不能重复扣款。
    模块中如果有多个消费者共享的资源,要考虑加锁或者 CAS,比如领券的场景。
    RPC 调用,账户额度和订单状态要考虑一致性。
    Renco
        9
    Renco  
    OP
       2020-01-16 16:55:42 +08:00
    对于分布式系统来说 sychronize 锁是不是没有意义
    0NF09LJPS51k57uH
        10
    0NF09LJPS51k57uH  
       2020-01-16 17:00:37 +08:00   ❤️ 1
    @Renco Redis、Zookeeper 是比较常见的分布式锁实现方案。如果你们有现成的 redis 集群,可以了解一下 Redisson,借助 Redis 完全依照 Java 规范实现包括分布式锁在内的各种分布式工具。

    借助其他实现了分布式一致性协议的组件也可以实现分布式锁。
    justfly
        11
    justfly  
       2020-01-16 17:01:52 +08:00   ❤️ 2
    但是我不太清楚分布式系统中的,并发问题是怎么产生的。

    就你这个场景,两个对同一个用户的扣款请求,可能被路由到你服务的两个完全不相关的进程上,这两个进程内各自处理自己的扣款操作,比如是检查余额和扣款俩操作,二者均检查本次扣款余额充足没问题,结果可能会扣款变成负数。这时候就需要分布式锁来强制两个用户的扣款操作顺序进行。
    Renco
        12
    Renco  
    OP
       2020-01-16 17:07:53 +08:00
    @justfly 明白了,感谢
    Renco
        13
    Renco  
    OP
       2020-01-16 17:08:06 +08:00
    @phantomzz 好的,学习下,多谢
    wangyzj
        14
    wangyzj  
       2020-01-16 17:08:13 +08:00
    tcc
    notwaste
        15
    notwaste  
       2020-01-16 19:15:35 +08:00 via Android
    好多大佬,学习了
    index90
        16
    index90  
       2020-01-16 20:10:18 +08:00   ❤️ 1
    跟分布式系统没多大关系吧
    即使你是单点的也会有并发问题啊
    例如,我用同一个账号同时下了两个单
    例如,我消费的同时对账号进行充值
    对共享数据(余额)进行并发读写,要注意的是脏读脏写问题
    FrankD
        17
    FrankD  
       2020-01-16 20:47:01 +08:00 via Android
    楼里瞎扯啥,一堆名词往上怼,人家说的是这个场景的吗
    opengps
        18
    opengps  
       2020-01-16 21:12:31 +08:00
    关键词:分布式锁。
    多了不敢说,毕竟我经验不足
    skypyb
        19
    skypyb  
       2020-01-16 21:51:14 +08:00
    来,给你两篇分布式锁的文章,一个 Redis 的,一个 Zookeeper 的
    http://zhangtielei.com/posts/blog-redlock-reasoning.html
    http://skypyb.com/2019/08/jishu/943/
    CoderGeek
        20
    CoderGeek  
       2020-01-16 21:56:48 +08:00
    一般情况:
    1.redis、zk、数据库行锁
    2.是否幂等
    3.条件如 version 版本号、余额是否可为负等
    CoderGeek
        21
    CoderGeek  
       2020-01-16 21:58:20 +08:00
    看你描述已经流转到你内部系统了 不要跑偏了
    CoderGeek
        22
    CoderGeek  
       2020-01-16 22:03:49 +08:00
    对了哦我说这些是单个用户才需要考虑的哦,多个用户间不互转不存在并发问题
    wccc
        23
    wccc  
       2020-01-16 23:40:49 +08:00 via iPhone
    加版本号即可,保持接口的幂等
    wysnylc
        24
    wysnylc  
       2020-01-17 03:30:39 +08:00 via Android   ❤️ 1
    分布式锁性能差,做多队列单消费者
    保证一个用户任务在一个队列中
    上面要加锁的一律不看,并发的终极方案就是队列
    队列分组就 hash 取余,后期可升级一致性哈希环
    btnokami
        25
    btnokami  
       2020-01-17 07:04:27 +08:00
    所以你的模块只是作为 Proxy 来处理 request 发给 database,还是 databse 那一层呢。
    感觉把并发问题推给 database layer 然后用不同的 isolation level 来处理会比较简单
    btnokami
        26
    btnokami  
       2020-01-17 07:06:51 +08:00
    分布式计算就两个终极问题,exactly once delivery 和 consensus。。。往上套就好了
    lewis89
        27
    lewis89  
       2020-01-17 07:30:12 +08:00
    @btnokami #26 非常赞同..
    lewis89
        28
    lewis89  
       2020-01-17 07:32:34 +08:00
    @wysnylc #24 很显然 楼主的场景跟楼主公司的业务量并没有那么大的规模,如果有的话 肯定不会轮到写代码的来排查这个问题了,在架构设计上就要避免了..
    lewis89
        29
    lewis89  
       2020-01-17 07:39:11 +08:00
    @wysnylc #24 我看它的情况,会员系统应该本身应该就维护了 扣款 跟 会员权限配置 的事务操作,这本身就是一个本地事务,如果别人调你的话,你只要对外保证幂等性即可,一般分布式调用链会有 SpanId 调用 用来维护调用链的(像 SpringCloud-Seluth ),最好对 SpanId 做一个幂等性,有些中间件会在超时的时候做一些重试操作,很有可能单个订单会调用两次扣款操作
    lewis89
        30
    lewis89  
       2020-01-17 07:41:49 +08:00
    或者可以针对订单 ID 做幂等性校验 把订单 ID 本地落库跟会员扣款放到同一个本地事务 commit 进数据库,做好隔离级别校验跟订单 ID 唯一性校验,如果有重复订单 ID 落库 直接吞掉异常 正常返回即可
    passerbytiny
        31
    passerbytiny  
       2020-01-17 09:27:27 +08:00   ❤️ 2
    前方在极短的时间内连续调用你的接口,这个时候你的接口,可能是多节点同时执行,也可能是单节点多线程同时执行,但访问的数据库只有一个:这就是并发场景。这种场景,单节点应用和分布式应用都会出现。

    这个场景你如果不考虑,那么可能出现的问题是:一,没上事务,余额表的变动历史将乱成一锅粥;二,上了事务,但在扣余额的时候没做二次验证,余额会被口称负的。

    解决方法如下,顺序是从简单到复杂,所有方法都要先有事务(单节点事务):
    一、乐观锁,只适合单节点应用。这个太常见了,而且你也不是单节点应用,就不细说了。
    二、实时动态扣费,适合订单模块(不是你的模块)并发量不是特别高的情况。你在扣费的时候不直接扣,先去查下当前余额,如果扣完不为负,再扣费。你的接口的返回结果不是 void,也不是 true/false,而是一个对象,包含这些字段:是否扣款成功、扣费前的实际余额、扣费后的实际余额。
    三、最终一致性,适合订单模块超高并发量的情况。直接扣费,返回结果总是成功,如果扣成负的了,发事件通知,让其他模块去做补偿(或者啥也不干就允许余额为负,或者再通知订单模块去取消订单)。

    最后说一句,看见分布式事务或者分布式锁的回复,请直接忽略,这是老早就被淘汰的技术。
    passerbytiny
        32
    passerbytiny  
       2020-01-17 09:38:01 +08:00
    还有各种回复“幂等”的,你们在想什么。首先,这是接口调用不是事件订阅,没有重发和无序性,无需考虑幂等;其次,扣余额这种动作你做幂等? 10 天前扣 1 元跟今天扣 1 元的结果一摸一样?
    ke1e
        33
    ke1e  
       2020-01-17 09:51:37 +08:00 via Android
    最简单的就是模拟并发情况,看会出现什么问题,然后再去解决
    xuanbg
        34
    xuanbg  
       2020-01-17 10:08:30 +08:00
    楼主你只需要考虑你的模块有多个服务实例的情况下,每个实例都在同一个时间扣同一个用户的钱的时候,如何保证扣款正确。
    bukeshuo
        35
    bukeshuo  
       2020-01-17 10:39:41 +08:00
    数据库乐观锁 或者 直接 分布式锁 锁账号
    Raymon111111
        36
    Raymon111111  
       2020-01-17 10:42:41 +08:00
    。。。

    楼里很多人是不是根本没有做过业务,只看过书,一堆什么分布式锁都出来了

    这是要梳理场景,不是解决方案。何况场景没有,解决方案是怎么意淫出来的

    并发问题主要考虑两点,这个接口短时间相同请求发起两次请求,这个接口被不同请求同时调用
    leafre
        37
    leafre  
       2020-01-17 11:15:17 +08:00 via iPhone
    @passerbytiny 好吧 我们最近上线的项目就是使用被淘汰的分布式锁
    peyppicp
        38
    peyppicp  
       2020-01-17 11:29:47 +08:00   ❤️ 1
    修改为订单模式,每笔余额扣减都有一个固定单号,需要明确一个状态机,保证状态流转正确

    做业务时,开启事务,select for update 锁住对应的单号,用户的余额,然后在本事务中做业务,完事了 commit

    这种同一单号的并发请求过来,一定会被挂起,执行代码中增加判断订单状态的逻辑就好了,如果本单状态为成功则幂等返回成功。
    peyppicp
        39
    peyppicp  
       2020-01-17 11:31:26 +08:00
    这个感觉压根没必要上到分布式系统的层面,也没有必要考虑分布式锁,用 db 抗住就完事了。

    你这是有热点会员吗,同一个会员做大量余额扣减?如果是这样完全可以考虑用子会员的方法实现,也不差的
    wc951
        40
    wc951  
       2020-01-17 12:03:32 +08:00 via Android
    扣款表里把订单号加唯一索引
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1061 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 18:56 · PVG 02:56 · LAX 10:56 · JFK 13:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.