PHP 使用 Redis 队列制作秒杀抢购的初步实现与思考

发布于 2月15日  约 3 分钟

先说一下我是如何一步步被逼使用Redis去解决秒杀的问题的 !

最开始没有考虑高并发库存超卖这些问题,简单的实现一套订单系统,结果是悲剧的,前台还傻X似的查商品库存显示,活动开始后都变成负数了 … 老脸一横,找解决方案

最早的时候使用一些限流手段,比如输入验证码啊,授权抢购啊,以期降低并发数,但总体体验太差,遂放弃

之后使用文件锁之类的解决方案,发现根本不好使,当文件被写入锁住的时候,实际上并发已经发生了 …

不得已采用 Redis , 开始的时候,将商品库存存入 List ,然后下单从 List 中 Pop 库存出来,然后入库减库存 !! 这套方案可以确保库存不超卖,但是有几点遗憾

1、用户抢购成功,但是不支付;这个时候就需要将订单定期回收,这就造成抢购一开始很多人抢不到,过一会捡漏倒是很轻松 !
2、抢购成功即生成单号入库,因为还有一些列操作(生成单号、生成二维码、发送下单通知 …),并发的时候会造成系统卡住 !就是活动开始那几秒,整个项目是卡死的 …
3、以上两点综合起来,就是抢购成功,页面无响应,只能一直等待,用户不知道情况,页面也一直没响应,结果就关闭了,然后订单创建好页面响应成功了订单又被回收了 … 死循环

—————— 很糟糕的体验

后来我又继续找解决方案,发现 RabbitMq 队列,大为惊喜,我只需要将 Pop成功的用户队列起来,然后定时消费入库,就好了 !!
说归说,实现起来各种想不通,总结如下,期待有经验的大神回复 !
Win 环境,成功安装并启动 ERLANG、RabbitMq,成功安装 AMQP - PHP扩展

1、根据官方文档,当我获得库存Pop资格后,将用户及用户抢购的商品信息以队列的形式提交到 Rmq,这个并发的过程,投递失败及其他状况不知道怎么捕捉
2、消费者端不知道如何部署,假如是定时任务,轮询某个URL,然后该URL负责消费的话,会产生太多太多消费者,服务器会拖死,网上有说做个 RD 锁,即当存在消费的时候,直接Return,这个机制不知道是否合适
3、当消费成功,应答成功后,如何通知前端 ?消费成功后将订单号和用户ID,写入 Redis Hash散列 ?前端轮询 ?

------------------- 搞不颠,希望有大神能说说这个机制 ,完整详细一点的

最终选择通过 Redis 队列实现并发订单,大致实现思路如下

1、抢购时 Pop 库存队列,成功则写入用户即商品信息到一个 Hash 散列,(同时:将用户ID PUSH 到一个 List),前端进入排队页,失败直接进入失败页 (确保不超卖)
2、定时任务消费队列,通过 LLEN 判断用户LIST,然后根据用户ID消费 Hash 键,生成订单入库,(同时写入订单信息到一个 Hash散列,同样以用户ID为键)
这两部能实现库存不超卖、响应很快
3、前端抢购成功排队页轮询,根据自己ID,hGet 散列,当拿到数据,进入支付页,生成预支付参数 !
4、定时任务轮询数据库订单,一定时间未支付的订单,关闭 (不再回收库存)
5、支付异步通知,减库存
6、每日定时轮询商品表,如果有库存,重新生成库存 List ,前端可再次抢购

以上为当前实现的逻辑,个中肯定有很多不理想的地方,我自己能考虑到的有
1、Redis 性能问题,它一旦崩溃了怎么办,整个数据链就断了
2、轮询异常怎么办,进入排队页迟迟获取不到订单,假使给个轮询时间阈值,这个值没有合理时间啊 …

其他还有很多地方不足,期待大神指出 !

阅读 201发布于 2月15日

推荐阅读
目录