秒杀系统要拦住无效查库请求,核心就一句话:能进缓存的绝不打库,进不了缓存的也别直接打库。缓存数据库作为唯一入口,把流量挡在最外层,查库请求数量能压到原来的几十分之一,系统抗住的并发量完全不同。
秒杀场景下,为什么查库请求会打爆数据库
秒杀开启的那一瞬间,流量不是慢慢爬坡,而是直接冲顶,1000件商品,来了100万人,其中99.99万人的请求都是无效的,但问题是,在缓存方案没做好的系统里,这些无效请求会挨个穿透到数据库,数据库的连接池、行锁、磁盘IO全部被拖死,结果真正抢到资格的那部分用户,也提交不了订单。
库的压力和流量成正比,这在秒杀里是致命的。 缓存数据库要做的,是让请求在进入业务层之前,就被识别为无效并直接返回,这也是为什么业界普遍采用"缓存先行、数据库兜底"的架构,据行业共识,绝大多数秒杀系统的瓶颈不在应用服务器,而在数据库的读压力,单纯靠扩容机器解决不了根本问题。
秒杀系统怎么防超卖,先从缓存层的职责说起
缓存数据库不是简单存一份商品库存数,它要承担三个任务:挡住无效请求、计算剩余库存、过滤已购用户,这三个任务对应三类不同的缓存结构。
- 商品库存缓存:用Redis的String或Hash保存剩余库存,每次请求进来先DECR,返回值小于0直接返回"已售罄"。
- 用户去重缓存:用Set或布隆过滤器记录已经下单成功的用户ID,防止同一用户重复刷接口。
- 热点标记缓存:当库存降到0或者活动尚未开始时,用一个短TTL的标记位直接拒绝请求,不查任何数据。
这套分层设计的核心思路,是把"查询"改造成"判断",原来的代码逻辑是请求来了查一下数据库看有没有货,现在变成请求来了先在Redis里对几个Key做原子操作,数据库只在缓存层确认放行后才被访问。

缓存和数据库一致性怎么保证,库存扣减别走弯路
很多人担心Redis里的库存数和MySQL不一致,于是每次请求都先查数据库,再回种缓存,这种写法在秒杀里是自掘坟墓。
正确做法是下单链路先写Redis,异步同步MySQL,用户请求到达后,Redis执行DECR操作获得返回值,若扣减成功直接进入下单流程,然后把扣减记录写入MQ或日志表,由消息消费者异步更新数据库,最终一致性靠MQ的可靠投递和重试机制保证。
这个方案里有一个容易踩的坑:MQ消息积压导致数据库库存和缓存库存长期不一致,业内专家指出,解决方案是给每一次扣减操作带上全局唯一的流水号,消费端做幂等处理,即使重复投递也不会重复扣减。
如果Redis挂了怎么办?这就引出备选方案。Redis哨兵或Cluster模式保证高可用,同时本地缓存作为最后一道保险丝,本地缓存存一个全局开关,Redis全部不可用的时候直接短路,防止请求穿透到MySQL。
Redis缓存穿透和击穿有什么区别,秒杀里怎么区分对待
缓存穿透是查一个不存在的Key,Redis里没有,MySQL里也没有,请求每次都打到数据库,秒杀场景里大量伪造的SKU或者活动未上架的商品ID,就是穿透流量。
解决办法是布隆过滤器前置,把所有合法的商品ID初始化到布隆过滤器里,请求进来先判断ID是否存在,不存在直接返回售罄。
缓存击穿是某个热点Key在过期的一瞬间,大量请求同时打到数据库,秒杀商品只有一个SKU,这个Key的过期时间点就是灾难时刻。
解决办法:
- 热点Key不设置过期时间,用逻辑过期,比如Redis里存一个JSON字符串,里面同时带上库存数据和过期时间戳,每次请求比较时间戳,需要更新时异步刷新。
- 用互斥锁在缓存重建期间只放行一个请求去查库,其他请求短暂等待或返回旧值。

业内常用的JVM级别锁配合Redis分布式锁,能有效控制击穿窗口期的并发数量。
高并发秒杀架构方案对比,缓存数据库部署在哪里
先对比两种常见方案的优劣,方便你根据预算和场景选型。
| 方案 | 查询性能 | 防穿透能力 | 改造成本 | 适用场景 |
|---|---|---|---|---|
| Nginx+Lua直连Redis | 高,单机可达10万+QPS | 需配合Lua脚本实现复杂判断 | 中,需写Lua扩展 | 大厂标准做法,适合流量极高的活动 |
| 应用层Redis+分布式锁 | 中等,受限于应用线程池 | 逻辑灵活,易扩展 | 低,基于现有框架改造 | 中小项目,快速上线 |
从技术选型来看,如果你的业务是淘宝双十一量级,就要走OpenResty+Lua方案;普通电商平台的促销活动,在应用层做Redis拦截已经绰绰有余。
另外一个更现实的问题是成本。缓存数据库的购买费用和带宽是秒杀活动的最大支出,据公开信息,主流云厂商Redis实例按规格计费,8GB内存版月费用在数百到上千元不等,秒杀活动一般建议使用按量付费或临时升配,活动结束后降配节省成本。
秒杀系统缓存架构实操步骤,一步步搭起来
以下是以Java技术栈为例的标准落地流程。
第一步:定义缓存Key设计规范
- 库存Key:
seckill:stock:{skuId},值为剩余库存 - 用户去重Key:
seckill:users:{skuId},用Set存储已购用户ID - 活动开关Key:
seckill:open:{activityId},控制秒杀是否开始
第二步:初始化数据到Redis
商品上架时一次性加载库存到Redis,确保不会超卖,这里需要先检查MySQL当前库存,再写入缓存,写完后对账一次,防止缓存值比库里的值大。

第三步:请求链路改造
前端请求打到API网关,先经过筛选,过滤掉无Token、非白名单用户,然后进入业务层,业务层先走Redis判断,再走数据库下单,所有数据库操作封装在独立的服务里面,不跟缓存操作混在一起,方便后期调优。
秒杀活动结束后,缓存数据怎么清理
活动结束时不是直接把Key删掉就完事了,要分三步走:
- 先关闭活动开关Key,让新的请求直接被拒,这时候缓存还在;
- 等数据库的异步消费追上进度,确认Redis和MySQL的库存一致;
- 最后删除库存Key和用户去重Key,释放内存空间。
这套流程的关键点是活动开关挡在库存Key之前,即使清理过程中有新请求进来,也不会打到数据库。
Q&A:秒杀场景缓存拦截无效查库请求的常见疑问
问:秒杀场景下如果Redis也扛不住怎么办?
Redis单实例写能力在10万级QPS以上,通常瓶颈不在Redis处理能力,而在于网络带宽,如果单节点不够,做分片集群即可,把不同的SKU分散到多个分片上,为了防止热点Key倾斜,可以在Key后面加随机后缀分散读请求。
问:秒杀系统的缓存数据要不要做持久化?
秒杀活动的缓存数据不需要Redis持久化,因为缓存丢失后可以从MySQL重建,活动结束后数据也没有保留价值,关闭AOF和RDB能显著提升Redis的写性能。
问:用本地缓存替代Redis做秒杀拦截行不行?
单机部署可以,但分布式环境下不行,多个应用节点的本地缓存各自维护库存,必然出现超卖,如果只有一台机器且不想引入新组件,本地缓存配合数据库唯一索引防超卖是可行方案,服务一旦需要扩容,就必须换成Redis集中管理库存。