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

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

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

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

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

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


1. 网络问题导致 MQ 根本没收到。这就需要 MQ 和生产者之间有确认机制。
2. MQ 自己把消息搞丢了(比如宕机等)。这就需要打开 MQ 上的一些持久化机制。
3. 被消费者搞丢了。比如消费者取到消息后,还没完成流程,出错了。这就需要有个确认机制,在消费者消费完此条消息后,给 MQ 发送确认,然后 MQ 再删除消息。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/781730

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX