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

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

  •  
  •   Renco · 37 天前 · 3855 次点击
    这是一个创建于 37 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

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

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

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

    要 tcc 之类的,就要联动你的订单模块一起从整体上来看架构了。你们这个没个架构师来把关么,不要随便用 tcc
    phantomzz
        8
    phantomzz   37 天前   ❤️ 1
    4 楼正解,5 楼不知所云。

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

    借助其他实现了分布式一致性协议的组件也可以实现分布式锁。
    justfly
        11
    justfly   37 天前   ❤️ 1
    但是我不太清楚分布式系统中的,并发问题是怎么产生的。

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

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

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

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

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

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

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

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

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

    你这是有热点会员吗,同一个会员做大量余额扣减?如果是这样完全可以考虑用子会员的方法实现,也不差的
    wc951
        40
    wc951   36 天前 via Android
    扣款表里把订单号加唯一索引
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   1635 人在线   最高记录 5168   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 00:35 · PVG 08:35 · LAX 16:35 · JFK 19:35
    ♥ Do have faith in what you're doing.