V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
jsdi
V2EX  ›  问与答

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

  •  1
     
  •   jsdi · 2021-06-06 19:50:03 +08:00 · 1353 次点击
    这是一个创建于 1026 天前的主题,其中的信息可能已经有所发展或是发生改变。
    最近在设计一个秒杀系统。秒杀操作的执行逻辑是:
    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   ·   我们的愿景   ·   实用小工具   ·   2759 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 42ms · UTC 00:24 · PVG 08:24 · LAX 17:24 · JFK 20:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.