各位大佬看看我这个思路有没有有没有问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
jsdi
V2EX    问与答

各位大佬看看我这个思路有没有有没有问题

  •  1
     
  •   jsdi 2021-06-06 19:50:03 +08:00 2026 次点击
    这是一个创建于 1587 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在设计一个秒杀系统。秒杀操作的执行逻辑是:
    1 、从 redis 中读取当前秒杀商品的剩余库存(因为 redis 是单线程的,这里不会产生并发问题)
    2 、如果库存大于 0,把秒杀请求存进 rabbitmq 消息队列,保证 rabbitmq 中的每个请求都会被成功消费(生成订单)。也就是如果有 100 个秒杀商品,那么消息队列中只会有 100 个秒杀请求。
    3 、如果库存小于 0,直接返回秒杀结束

    这样设计有没有问题?针对秒杀减库存下订单这个操作又没有更好的解决方案?
    16 条回复    2021-07-25 16:40:57 +08:00
    ZRS
        1
    ZRS  
       2021-06-06 20:45:10 +08:00   1
    1 、2 步是原子操作吗
    jsdi
        2
    jsdi  
    OP
       2021-06-06 21:05:23 +08:00
    第一步是原子操作
    第二步只有一条插入订单表的 SQL,应该也是原子性的吧?
    jsdi
        3
    jsdi  
    OP
       2021-06-06 21:05:41 +08:00
    @ZRS
    第一步是原子操作
    第二步只有一条插入订单表的 SQL,应该也是原子性的吧?
    ZRS
        4
    ZRS  
       2021-06-06 21:34:07 +08:00
    @jsdi 只要保证 获取库存->判断库存->更新库存 这三步是一个原子操作即可,我没实际用过 redis,查了下可以通过 lua 脚本实现。
    seakingii
        5
    seakingii  
       2021-06-06 22:23:12 +08:00   1
    感觉有问题
    假设有 100 商品可下订单
    第一步:并发 200 个请求,此 200 个请求读取到 REDIS 的剩余库存全是 100
    第二步:因为这 200 个请求的库存都大于 0,所以你会所 200 个请求全存入消息队列...
    jsdi
        6
    jsdi  
    OP
       2021-06-06 23:48:26 +08:00
    @seakingii 不可能有这种情况吧,redis 不是单线程的吗?我用上面的思路撸完代码了,测试过后没有发现超卖问题
    revlis7
        7
    revlis7  
       2021-06-07 00:11:27 +08:00
    @jsdi 这取决于你在哪步做更新库存的操作,你主题里没有明述。

    如果是在 rabbitmq 消费时更新,一旦发生阻塞,肯定会有超发的情况。
    jsdi
        8
    jsdi  
    OP
       2021-06-07 00:23:17 +08:00
    @revlis7 目前是在消费信息时更新库存。采用的是手动签收的方式,如果更新库存+生成订单这个事务没有成功,会回滚并拒绝签收此消息,rabbitmq 会重新发送该消息,从而保证消息一定会被消费。
    这么设计应该可以避免超卖以及少卖的情况吧

    还有另外一种想法是设置定时任务定期把 redis 中的库存信息同步到 MySQL,因为秒杀期间读取剩余库存也是从 redis 中读取的,MySQL 的记录并不需要实时更新。不知道这种想法有没有问题
    lewinlan
        9
    lewinlan  
       2021-06-07 07:01:48 +08:00 via Android
    1 是单线程
    1-2 之间的过程又不是
    ccde8259
        10
    ccde8259  
       2021-06-07 09:14:33 +08:00 via iPhone
    你把消息队列的延迟拉大一点
    比如消费者在生产者投递后三秒启动
    Distand
        11
    Distand  
       2021-06-07 19:46:22 +08:00
    你这个方式队列中一定会超过 100
    jsdi
        12
    jsdi  
    OP
       2021-06-07 22:35:46 +08:00
    @Distand 确实超过 100 了,测试的时候消息队列有 10000 多条消息请问这会产生什么问题?测试的时候没发现问题阿
    Distand
        13
    Distand  
       2021-06-08 10:20:36 +08:00
    @jsdi 取库存到减库存需要是原子性的,你可以用 redis decr 去原子性减库存,减完判断>=0 再写入队列,再保证下 redis 操作和 mq 操作的事务就差不多了
    jsdi
        14
    jsdi  
    OP
       2021-06-08 15:38:03 +08:00
    @Distand 不好意思理解错了
    队列长度为什么会超过 100 ?我使用的 redis 命令是 “decr key”,如果 reuturn >= 0,进入队列,< 0,不进入队列 。
    redis 中的单条命令不是保证原子性的吗?所以队列长度只能是 100 。
    经过测试确实没出现问题阿,反而是消息队列有可能丢失消息导致商品少卖了,但是没有超卖的情况
    jsdi
        15
    jsdi  
    OP
       2021-06-08 15:39:58 +08:00
    @Distand 我也是这么想的,但是消息队列可靠性怎么保证?消息队列有可能会丢失消息,怎么让生产者重发此消息
    kensin
        16
    kensin  
       2021-07-25 16:40:57 +08:00
    “消息队列可靠性怎么保证?”,可以试试事务型消息,RocketMQ 中就支持。

    其实对于秒杀系统而言,本来就是万里挑一,丢失一个也无所谓。
    如果是重要的消息,防止丢失可以从以下三个方面考虑:


    1. 网络问题导致 MQ 根本没收到。这就需要 MQ 和生产者之间有确认机制。
    2. MQ 自己把消息搞丢了(比如宕机等)。这就需要打开 MQ 上的一些持久化机制。
    3. 被消费者搞丢了。比如消费者取到消息后,还没完成流程,出错了。这就需要有个确认机制,在消费者消费完此条消息后,给 MQ 发送确认,然后 MQ 再删除消息。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5793 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 93ms UTC 06:39 PVG 14:39 LAX 23:39 JFK 02:39
    Do have faith in what you're doing.
    ubao 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