资讯动态

秒杀库存三种缓存实现方案

发布时间:2026/10/17 2:47:21 来源:尧图企业网站定制
1、概述秒杀场景核心诉求防超卖、高并发吞吐、故障可控。行业内基于 Redis 实现库存预扣主流有三种方案HINCRBY 客户端判断 客户端回补原生 Redis 命令方案Lua 脚本原子扣减方案Redis 分布式锁Redisson 等库存扣减方案本文从原理、代码示例、原子性、性能、故障模型、运维成本、适用场景等维度横向对比用于团队技术评审与方案选型。前置共识秒杀不是单一库存逻辑必须配套前端限流、网关限流、本地缓存拦截、熔断降级、MQ 异步下单、定时对账等组合防护库存扣减只是整个链路内层环节。2、Lua 脚本原子扣减推荐2.1 原理将库存读取判断 库存扣减封装在一段 Lua 脚本交由 Redis 单线程整体执行。Redis 保证脚本执行期间不会插入其他命令整体原子。逻辑先校验库存库存充足才执行扣减库存不足直接返回不修改库存无需客户端回补。Lua 脚本-- KEYS[1]商品hash key -- ARGV[1]库存字段名 -- ARGV[2]购买数量 local stock tonumber(redis.call(HGET, KEYS[1], ARGV[1])) local buyNum tonumber(ARGV[2]) if stock buyNum then redis.call(HINCRBY, KEYS[1], ARGV[1], -buyNum) return 1 -- 扣减成功 else return 0 -- 库存不足 endJava 端仅调用脚本根据返回值判断抢购结果客户端不再做库存回补操作。2.2 核心特点无论成功 / 失败固定 1 次网络往返库存不足不会产生负库存中间状态。3、HINCRBY 客户端回补3.1 原理利用HINCRBY单条命令原子增减 Hash 字段。先执行库存扣减客户端拿到扣减后的库存值做判断扣减后库存≥0抢购成功进入下单流程扣减后库存0代表库存不足客户端再次执行 HINCRBY 把库存加回抛出库存不足异常。关键点HINCRBY 单条命令原子判断库存、回补库存两段逻辑在 Java 客户端执行不属于 Redis 原子域。伪代码// 扣减库存buyCount为本次购买数量 long stockCount redisTemplate.opsForHash().increment(goodsInfoKey, OrderConstant.Create.GOODS_INFO_DTO_STOCK_COUNT, -OrderConstant.Create.BUY_COUNT); if (stockCount 0) { // 库存不足回补 redisTemplate.opsForHash().increment(goodsInfoKey, OrderConstant.Create.GOODS_INFO_DTO_STOCK_COUNT, OrderConstant.Create.BUY_COUNT); throw new BizException(ReturnCode.GOODS_INFO_NOT_ENOUGH); } // 扣减成功投递MQ异步落库处理订单与支付3.2 核心特点成功请求1 次 Redis 网络往返失败请求2 次 Redis 网络往返扣减 回补。4、Redis 分布式锁Redisson扣库存4.1 原理请求先抢占商品库存分布式锁拿到锁之后在临界区内查询 Redis 库存、判断、扣减完成后释放锁。同一时间只有一个请求进入库存判断扣减逻辑并发串行化。伪代码RLock lock redisson.getLock(stock_lock: goodsId); boolean tryLock lock.tryLock(1, 10, TimeUnit.SECONDS); if (!tryLock) { throw new BizException(抢购繁忙); } try { Integer stock (Integer) redisTemplate.opsForHash().get(goodsInfoKey, OrderConstant.Create.GOODS_INFO_DTO_STOCK_COUNT); if (stock buyCount) { redisTemplate.opsForHash().increment(goodsInfoKey, OrderConstant.Create.GOODS_INFO_DTO_STOCK_COUNT, -buyCount); // 下单MQ } else { throw new BizException(ReturnCode.GOODS_INFO_NOT_ENOUGH); } } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } }4.2 核心特点并发被锁串行化天然降低吞吐量锁需要处理锁超时、锁续期、死锁等问题。5、多维度横向对比表对比维度HINCRBY 客户端回补Lua 脚本原子扣减分布式锁扣库存原子范围仅 HINCRBY 单条命令原子库存判断、回补在客户端非原子判断 扣减全部在 Redis 内部原子执行锁临界区内的业务代码串行锁本身有超时风险负库存问题存在短暂负库存窗口若扣减后、回补前服务 / 网络异常 → 永久负库存不存在负库存库存不足不会执行扣减不会产生负库存锁串行执行性能成功请求 1 次 RT失败请求 2 次 RT大量失败请求会放大 Redis QPS稳定 1 次 RT成功失败开销一致性能最差并发串行吞吐量被锁限制开发上手纯 Java 代码IDE 断点调试日志友好团队只需要掌握 Redis 原生命令需要学习 Lua无法断点调试Redis 集群要求脚本内所有 key 在同一个 slot依赖 Redisson 组件锁超时、续期、可重入概念需要理解代码量多扩展性业务规则限购、黑白名单写 Java灵活易改新增校验逻辑无法加入原子域简单校验同 key 内限购可以写 Lua 保持原子复杂逻辑不能写入脚本复杂业务逻辑写 Java但是所有逻辑串行性能持续下降故障模型风险点JVM 崩溃、长时间 GC STW、网络中断发生在扣减后回补前。需要负库存监控 定时对账兜底风险点脚本过长阻塞 Redis集群 slot 约束脚本版本变更风险。对账主要兜底 Redis 数据丢失灾难风险点锁过期、锁误释放、主从切换丢锁锁粒度不好控制极易成为性能瓶颈Redis 集群无特殊限制原生命令key 分布自由脚本内所有 KEY 必须落在同一个 slot多 key 场景受限无特殊 key 限制但锁热点 key 压力巨大变更与测试Java 代码变更单元测试、灰度发布成熟脚本修改需要单独测试Redis 缓存脚本线上变更风险更高锁逻辑变更测试复杂需要模拟锁超时、主从切换场景适用流量配合前置限流、熔断可支撑大流量秒杀不适合没有前置防护下极小库存 海量无效请求场景热点小库存秒杀更友好失败请求不会放大 QPS不适合秒杀洪峰场景适合低并发、强一致性业务5.1 Lua 脚本原子扣减5.1.1 优点库存判断与扣减原子执行从根源消除客户端回补失败带来的负库存脏数据无论抢购成功失败固定单次 Redis 往返QPS 更平稳对账任务降级为灾备兜底不再用来修复业务逻辑缺陷。5.1.2 缺点Lua 脚本调试困难排错依赖 Redis 慢日志Redis 集群模式下脚本操作的多个 key 必须位于同一个 slot多 key 业务受限脚本不能执行 IO、复杂计算过长脚本会阻塞 Redis 单线程脚本版本管理需要额外测试保障。5.1.3 适合场景热点小库存秒杀、无效请求占比极高不想依赖对账修复业务逻辑脏数据团队具备 Lua 与 Redis 集群运维能力。5.2 HINCRBY 客户端回补5.2.1 优点全部使用 Redis 原生命令团队学习成本低Java 代码可调试、可打日志线上问题排查直观成功请求性能优秀没有 Lua 脚本集群 slot、脚本阻塞等额外运维坑。5.2.2 缺点存在理论故障窗口扣减完成后客户端还没执行回补发生进程 crash、网络断开会造成 Redis 永久负库存失败请求会产生双倍 Redis 命令大量无效抢购请求会放大 Redis 压力需要配套监控库存负数告警 定时对账任务作为兜底修复机制。5.2.3 适合场景已有完善的前置限流、熔断、对账体系打到库存层的请求密度可控业务可接受极低概率脏库存并自动修复。5.3 分布式锁扣库存5.3.1 优点库存判断、扣减逻辑都写在 Java 代码可读性强串行执行天然不会出现超卖、负库存。5.3.2 缺点并发请求被锁串行化吞吐量很低秒杀瞬时洪峰场景极易成为瓶颈分布式锁本身有很多边界问题锁超时、主从切换丢锁、锁误释放热点商品会造成锁 key 竞争大量请求排队等待锁请求堆积超时增多。5.3.3 适合场景低并发的库存扣减业务不推荐作为秒杀核心库存方案。秒杀高并发场景下分布式锁会严重压低吞吐几乎不用于线上秒杀。6、通用最佳实践前置多层限流前端按钮置灰、CDN、网关限流、应用本地 Caffeine 缓存拦截无效请求减少穿透到 Redis 库存层的流量熔断降级Redis 超时、RT 飙升时直接降级返回抢购失败防止连锁雪崩异步下单库存预扣成功后投递 MQ消费者异步落库创建订单DB 不直接承接秒杀流量库存回补支付超时、用户取消订单需要独立的库存回补逻辑和 HINCRBY 失败回补不是一回事定时对账Redis 库存和数据库订单库存定期校对监控库存异常作为最终兜底监控监控 Redis 热点 key 耗时、命令 QPS、库存数值异常告警。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑