用Redis搞秒杀那事儿,性能和实现细节聊聊吧
- 问答
- 2026-01-18 02:14:16
- 1
说到用Redis搞秒杀,这事儿说白了就是在极短的时间内,有海量用户同时来抢购极少量的商品,比如限量100个的特价手机,几十万人同时点下单,传统的做法是把库存存在数据库里,比如MySQL,每次下单都去数据库里查一下库存够不够,然后减掉1,这在人少的时候没问题,但秒杀时瞬间的请求像海啸一样,数据库根本顶不住,直接就可能挂掉,或者慢得跟蜗牛一样,用户体验极差。
那Redis为啥能行呢?核心就两点:一是它特别快,因为数据都在内存里操作;二是它提供了一些“原子操作”,原子操作这个词听起来专业,其实意思就是这个操作一旦开始,就不会被别的请求打断,直到它做完,这在秒杀里至关重要,能保证不会出现“超卖”,也就是不会出现库存只剩1个,结果两个请求同时查都看到有货,都下单成功,卖出去2个的尴尬情况。

具体怎么实现呢?思路就像打仗一样,要层层设防,不能把所有压力都丢给最后一道关卡。
第一道防线:尽量把请求挡在外面。 秒杀开始前,用户肯定会不断刷新页面,我们可以在页面层面做文章,比如把秒杀商品的详情页、按钮提前静态化,用CDN分发到离用户最近的节点,这样用户看到的页面不用每次都从服务器生成,减轻服务器压力,甚至可以在用户点击秒杀按钮前,增加一些简单的答题、滑块验证之类的互动,这虽然有点“损”,但能有效拦住一部分脚本机器人,让真正的请求稍微分散开一点。

第二道防线:读多写少,用Redis扛住查询。 秒杀时绝大部分请求其实是来问“还有没有货?”的,我们可以把商品库存数量提前预热到Redis里,用一个简单的string类型或者hash类型存起来,所有查询库存的请求都直接打到Redis上,由于Redis速度极快,能轻松应对这种海量读取,这样,数据库就只负责最后那一点点真正的写操作(减库存)。
第三道防线,也是最关键的一步:原子性地扣减库存。 当用户点击秒杀,我们服务端收到请求后,不能直接程序里先get库存,再判断,再set库存,因为这不是原子操作,会超卖,Redis给了我们现成的原子武器。

最常用的就是DECR命令,我们提前把库存数量,比如100,SET到一个键里(比如stock:sku001),当秒杀请求来时,直接执行DECR stock:sku001,这个命令的作用是把键的值减1,并且返回减完之后的值,关键是,这个“读-减-写”的过程是原子的,同一时刻只有一个请求能执行成功,我们判断返回值就行了:如果返回值大于等于0,说明扣减成功,这个用户抢到了;如果返回值小于0,说明库存已经扣成负数了,抢购失败,这时候我们再执行一个INCR把库存加回来,保持数据一致性,或者就让它为负,最后再统一处理。
还有一种更严谨的方法是使用Redis的 Lua 脚本,我们可以把一整段复杂的判断逻辑写成一个Lua脚本,然后让Redis去执行,因为Lua脚本在Redis中执行也是原子的,所以我们可以在这个脚本里完成:检查用户是否已经秒杀过(防止重复购买)、检查库存是否大于0、如果都满足则扣减库存并记录用户秒杀成功信息,这一系列操作打包成一个原子命令,万无一失。
抢到之后怎么办? 用户秒杀成功,我们只是在Redis里标记了一下库存和用户关系,这不算完,我们需要把这个成功的消息通知出去,让后续的流程(比如生成订单、扣减真实数据库库存、支付)慢慢处理,这里就用到了Redis的队列(List)或者发布订阅(Pub/Sub)功能,我们把秒杀成功的用户ID和商品ID作为一个消息,LPUSH到一个队列里,然后后台启动一些worker进程,不断地从队列另一边RPOP消息,异步地去处理创建订单等耗时操作,这样就把瞬间的峰值压力转化为了一个平稳的流式处理,数据库就不会被冲垮了,这也就是所谓的“流量削峰”。
还得注意些啥?
- 库存预热要准确:活动开始前,一定要把正确的库存数从数据库同步到Redis。
- 处理失败情况:如果用户秒杀成功,但后来因为没付款等原因订单取消了,需要有一个机制把库存再加回去,可能还需要设置一个库存回补的队列。
- Redis本身的高可用:这么重要的环节,Redis不能是单点的,得用主从、哨兵或者集群模式,保证挂了也能快速恢复。
- 数据一致性:最后还是要落地到数据库的,所以要确保Redis里的秒杀结果和数据库的最终订单状态是一致的,可能需要一些对账机制。
用Redis搞秒杀,核心思想就是前端限流、Redis扛读和原子写、消息队列异步化处理,它不是单靠Redis一个命令就能搞定所有事,而是一套组合拳,利用Redis的各种特性,构建一个能抗住瞬时洪峰的系统架构。
本文由水靖荷于2026-01-18发表在笙亿网络策划,如有疑问,请联系我们。
本文链接:https://haoid.cn/wenda/82769.html
