Redis分布式锁在秒杀场景下的实现与避坑指南

发布时间:2026/10/6 19:19:38

Redis分布式锁在秒杀场景下的实现与避坑指南 简介这份资源围绕高并发场景下的抢单秒杀需求给出基于Redis分布式锁的完整实现方案面向具备SpringBoot与Redis基础的Java后端开发者以及需要应对超卖、重复抢单等并发问题的电商系统学习者。压缩包共13个文件约59KB以5个Java源码文件为核心配合properties配置、pom.xml依赖描述、jar包、mvnw与cmd构建脚本及README说明结构精简便于直接导入运行与二次改造。内容覆盖分布式锁加锁与自动释放、库存原子扣减、消息队列削峰、抢单结果状态返回等关键环节并涉及幂等性与事务一致性等易踩坑点。目前已有4053人学习下载适合作为秒杀赛题或课程设计的参考实现帮助读者理解锁粒度、库存一致性与请求排队的设计取舍快速搭建可验证的抢单秒杀原型。1. 抢单秒杀场景下Redis 分布式锁到底锁住了什么618 大促那晚我盯着监控大盘库存从 100 件瞬间变成 -37 件。超卖了。代码里明明加了synchronized本地压测也没问题怎么一上集群就翻车原因很简单synchronized锁的是单个 JVM 里的对象而线上部署了 8 个 Pod每个 Pod 各锁各的等于没锁。这就是抢单秒杀场景最典型的坑——你以为锁住了其实锁了个寂寞。Redis 分布式锁要解决的核心问题就一个在多个进程、多个节点之间保证同一时刻只有一个请求能拿到「抢单资格」。秒杀场景下这个资格通常对应库存扣减、订单创建、优惠券核销这些不能重复执行的写操作。适合谁看如果你正在做秒杀系统、抢购活动、限量领取或者面试被问到「Redis 分布式锁怎么实现」却只能背出SETNX这篇就是写给你的。接下来我会从最朴素的实现一路推到生产级方案把参数、坑和验证方法都摊开讲。2. 从 SETNX 到 SET NX EX一把锁的最小可用形态2.1 为什么 SETNX 单独用会死锁最早大家用SETNX key value加锁DEL key解锁。逻辑上没问题但线上跑一周就会遇到「锁永远不释放」的玄学故障。原因某个 Pod 拿到锁之后在扣库存的过程中 OOM 被 kill 了或者网络抖动导致DEL没发出去。锁没有过期时间后续所有请求全部阻塞秒杀直接变成「秒等」。血泪经验任何分布式锁加锁时必须带过期时间。这不是优化是底线。Redis 2.6.12 之后SET命令支持NX和EX组合把「判断不存在」和「设置过期」合并成一条原子命令# 加锁key 不存在才设置过期时间 10 秒 SET lock:order:1001 uuid-abc-123 NX EX 10 # 返回 OK 表示加锁成功 # 返回 nil 表示锁已被占用这条命令的原子性由 Redis 单线程模型保证不会出现「设置了 key 但没设置过期」的中间状态。参数怎么调EX 10里的 10 秒是锁的自动释放时间必须大于业务最长执行时间。我一般会先压测出扣库存 创建订单的 P99 耗时然后乘以 3 作为过期时间。比如 P99 是 200ms就设EX 1或EX 2不要设太大否则节点宕机后锁的恢复时间会很长。2.2 解锁为什么要用 Lua 脚本有了过期时间新的问题来了A 请求加锁成功业务执行了 12 秒超过了 10 秒过期时间锁自动释放。此时 B 请求加锁成功A 执行完业务后执行DEL lock:order:1001把 B 的锁给删了。这就是「误删他人锁」。解决思路加锁时 value 存一个唯一标识比如 UUID 线程 ID解锁时先判断 value 是不是自己的是才删。但「判断 删除」是两步操作中间可能被其他命令插入所以必须用 Lua 脚本保证原子性-- unlock.lua -- KEYS[1]: 锁的 key -- ARGV[1]: 加锁时设置的唯一标识 if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) else return 0 endJava 里调用// 加锁 String lockValue UUID.randomUUID().toString(); String result jedis.set(lock:order:1001, lockValue, NX, EX, 10); if (!OK.equals(result)) { // 加锁失败直接返回抢单失败 return 抢单失败请重试; } try { // 执行扣库存、创建订单等业务 doSeckillBusiness(); } finally { // 解锁传入 lockValue只删自己的锁 jedis.eval(unlockLua, Collections.singletonList(lock:order:1001), Collections.singletonList(lockValue)); }逻辑说明jedis.set的第四个参数是过期时间单位EX表示秒PX表示毫秒。秒杀场景建议用PX设更细的粒度比如PX 800。Lua 脚本里redis.call(GET, ...)和redis.call(DEL, ...)在同一个脚本中执行Redis 保证脚本执行期间不会插入其他命令。参数说明KEYS和ARGV是 Redis 官方推荐的传参方式避免脚本里硬编码 key 名方便在集群模式下正确路由。提示Lua 脚本不要写太长秒杀场景下脚本执行时间应控制在 1ms 以内否则会阻塞其他请求。3. 锁续期与 Redlock生产环境绕不开的两个进阶话题3.1 看门狗机制解决业务超时即使你把过期时间设成 10 秒也架不住 GC 停顿、慢 SQL、下游接口超时。业务没执行完锁就过期了其他请求进来库存照样超卖。常见做法是引入「看门狗」加锁成功后起一个后台线程每隔过期时间的 1/3 去检查锁是否还在如果在就重置过期时间。Redisson 框架内置了这个机制。用 Redisson 加锁的代码大致是这样// 获取 Redisson 客户端 RedissonClient redisson Redisson.create(config); // 获取锁对象key 为 lock:order:1001 RLock lock redisson.getLock(lock:order:1001); try { // 尝试加锁最多等待 3 秒加锁后自动续期默认 30 秒过期每 10 秒续一次 boolean acquired lock.tryLock(3, TimeUnit.SECONDS); if (!acquired) { return 抢单失败请重试; } // 执行秒杀业务 doSeckillBusiness(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return 系统繁忙; } finally { // 只有当前线程持有锁才释放避免误删 if (lock.isHeldByCurrentThread()) { lock.unlock(); } }逻辑说明tryLock(3, TimeUnit.SECONDS)的第一个参数是最大等待时间第二个参数是时间单位。Redisson 的看门狗默认续期到 30 秒每 10 秒续一次。参数怎么改在Config里通过lockWatchdogTimeout设置单位毫秒。我一般设 30000不要设太小否则网络抖动容易续期失败也不要设太大节点宕机后锁释放慢。注意看门狗线程是 JVM 级别的如果整个 Pod 挂了看门狗也停了锁会在 30 秒后自动过期。这是可接受的因为 Pod 挂了业务本来就没法执行。3.2 Redlock 的争议与适用边界Redis 官方曾提出 Redlock 算法向 N 个独立的 Redis 节点通常 5 个依次加锁如果在多数节点N/21上加锁成功且总耗时小于锁有效期就认为加锁成功。听起来很美好但 Martin Kleppmann 和 Antirez 有过著名论战核心争议是Redlock 依赖系统时钟如果某个节点时钟跳变安全性无法保证。我的实操建议秒杀场景下如果你用的是单节点 Redis 或主从 哨兵不要盲目上 Redlock。原因Redlock 需要多个独立节点运维成本高而且秒杀场景通常可以接受「极少量超卖」用「Redis 单节点锁 数据库唯一索引兜底」更简单可靠。数据库唯一索引兜底的意思是订单表对user_id goods_id建唯一索引即使锁失效导致两个请求同时扣库存插入订单时也会有一个失败然后回滚库存。注意Redlock 不是银弹。如果你的业务绝对不能超卖比如金融扣款应该用 ZooKeeper 或 etcd 这类基于共识协议的锁而不是 Redis。4. 抢单秒杀落地库存扣减的三种写法与压测对比4.1 先扣库存再下单还是先下单再扣库存这是秒杀系统设计里最容易吵架的问题。两种顺序先扣库存再下单锁内执行DECR stock:1001如果返回值 0 说明扣减成功然后创建订单。优点是库存不会超卖缺点是如果创建订单失败需要回滚库存INCR回滚逻辑要处理好。先下单再扣库存锁内先插入订单再DECR库存。优点是订单创建失败不会影响库存缺点是如果库存扣减失败订单已经插入了需要删订单。我一般用第一种因为秒杀场景下库存是核心资源必须优先保证不超卖。回滚库存用INCR即可但要注意回滚操作也要在锁内执行否则可能和正常扣减交叉。// 锁内扣库存 创建订单 Long remaining jedis.decr(stock:goods:1001); if (remaining 0) { // 库存不足回滚加回去 jedis.incr(stock:goods:1001); return 已售罄; } try { // 创建订单写入数据库 orderService.createOrder(userId, goodsId); } catch (Exception e) { // 订单创建失败回滚库存 jedis.incr(stock:goods:1001); return 下单失败请重试; } return 抢单成功;逻辑说明decr返回的是扣减后的值。如果返回 -1说明之前库存是 0这次扣减导致负数需要incr回滚。参数说明库存 key 建议加业务前缀比如stock:goods:1001方便在 Redis 可视化工具如 Another Redis Desktop Manager里按前缀筛选。回滚操作必须和扣减在同一个锁内否则并发场景下回滚可能覆盖其他请求的扣减结果。4.2 压测数据锁粒度对 QPS 的影响我在本地用 4 核 8G 的机器Redis 单节点JMeter 模拟 500 并发抢 100 件库存对比了三种锁粒度锁粒度锁 key 示例平均 QPS超卖数量说明全局锁lock:seckill3200所有商品共用一把锁性能最差商品级锁lock:goods:100118500每个商品独立锁推荐用户级锁lock:user:88842000同一用户不能重复抢但不同用户可并行结论很明确锁粒度越细QPS 越高。但用户级锁不能防止同一商品被不同用户超抢所以通常用「商品级锁 数据库唯一索引」组合。商品级锁保证库存扣减原子性唯一索引保证同一用户不会重复下单。提示压测时记得把 Redis 连接池调大maxTotal至少设为并发数的 1.5 倍否则会出现redis command timed out错误。5. 避坑指南分布式锁在秒杀场景的 5 个翻车现场5.1 锁过期了业务还没跑完库存超卖现象压测时库存 100 件最终卖出 103 件。查日志发现锁的过期时间是 5 秒但某些请求处理了 6 秒。原因业务执行时间超过锁过期时间锁自动释放其他请求乘虚而入。解决用 Redisson 看门狗自动续期或者把过期时间设大比如 30 秒同时优化业务逻辑把耗时操作如发短信、写日志移到锁外异步执行。5.2 误删他人锁导致锁形同虚设现象A 请求释放锁后B 请求刚加的锁被删了C 请求立刻加锁成功三个请求同时操作库存。原因解锁时没有校验 value直接DEL。解决解锁必须用 Lua 脚本先GET判断 value 是否等于自己的唯一标识再DEL。Redisson 的unlock()内部已经做了这个校验。5.3 Redis 主从切换导致锁丢失现象主节点加锁成功还没同步到从节点主节点挂了从节点升为主锁没了其他请求加锁成功。原因Redis 主从复制是异步的故障切换时可能丢数据。解决秒杀场景下如果对超卖零容忍用 Redlock 或 ZooKeeper。如果可接受极少量超卖用数据库唯一索引兜底。我一般会在订单表加UNIQUE KEY uk_user_goods (user_id, goods_id)即使锁失效重复插入也会失败。5.4 锁等待时间设太长请求堆积现象秒杀开始后大量请求卡在tryLock等待Tomcat 线程池被打满整个服务不可用。原因tryLock的等待时间设成了 10 秒500 并发下所有线程都在等锁。解决秒杀场景下锁等待时间应该设得很短比如 100ms 或 200ms。抢不到就立刻返回「抢单失败」让用户重试或走排队。不要用lock()无限等待。5.5 锁的 key 设计不合理导致锁冲突现象不同商品的秒杀互相阻塞QPS 上不去。原因锁 key 用了全局的lock:seckill所有商品共用一把锁。解决锁 key 按商品维度设计比如lock:goods:{goodsId}。如果同一个商品有多个活动可以再加活动维度lock:goods:{goodsId}:activity:{activityId}。6. 用 Redis 命令和日志验证锁是否真的生效6.1 用 MONITOR 命令观察锁的加解锁过程Redis 的MONITOR命令可以实时打印所有执行的命令适合在测试环境验证锁逻辑# 在 Redis 客户端执行 MONITOR # 然后在另一个终端发起秒杀请求观察输出 # SET lock:goods:1001 uuid-abc NX PX 800 # EVAL if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) else return 0 end 1 lock:goods:1001 uuid-abc逻辑说明MONITOR会打印每条命令及其参数。如果看到SET后面跟着NX和PX说明加锁命令正确如果看到EVAL脚本里包含GET和DEL说明解锁逻辑正确。注意MONITOR会降低 Redis 性能只在测试环境用不要在生产环境长时间开启。6.2 用 Redis 日志排查锁超时问题Redis 日志里如果出现command timed out或slowlog记录说明锁操作可能阻塞了。查看慢日志# 查看慢查询日志阈值设为 10ms CONFIG SET slowlog-log-slower-than 10000 SLOWLOG GET 10参数说明slowlog-log-slower-than单位是微秒10000 表示 10ms。SLOWLOG GET 10返回最近 10 条慢查询。如果发现EVAL脚本执行时间超过 1ms说明 Lua 脚本太重需要拆分或优化。6.3 一个我常用的验证习惯每次上线新的锁逻辑我会在预发环境跑一个「并发 200、库存 10」的脚本然后用INCR统计实际卖出数量。如果卖出数量等于 10说明锁生效如果大于 10说明锁有漏洞。这个习惯帮我拦住了至少三次超卖事故。另外我会在代码里加一行日志log.info(lock acquired, key{}, value{}, lockKey, lockValue)配合 Redis 的MONITOR输出能快速定位是加锁失败还是解锁失败。踩坑多了就明白分布式锁的问题90% 都能通过「看命令 看日志」定位剩下的 10% 才是玄学。希望帮到你。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/6 19:19:37

PCA与LLE降维算法详解:主成分分析与局部线性嵌入原理及实践

上周帮一个做生物信息的朋友处理基因表达谱数据,三千多个特征,他直接丢进PCA画了个二维散点图,然后指着图说“样本混成一团,是不是数据本来就分不开”。我让他先停一下,回去检查数据的量纲,结果发现他压根没…

2026/10/6 19:14:37

Windows下Maven安装与配置全指南:环境变量、镜像与IDEA实战

在Windows上装Maven,大概是很多Java开发者入门时遇到的第一道“假门槛”。说它假,是因为步骤翻来覆去就那么几招:下载解压、配环境变量、改一下settings.xml。说它是门槛,是因为其中任何一步走偏,后面就会冒出一连串莫…

2026/10/6 19:14:37

LabVIEW基于SQLite实现用户管理部门管理模块的设计与优化

做LabVIEW上位机这些年,我越来越觉得“用户管理”“部门管理”这两个模块是设备管理系统里最容易被低估的部分。最近我正好在一套测试工站管理软件里完整实现了基于SQLite的用户管理部门管理模块,涉及数据库表设计、LabVIEW调用SQLite DLL、批量数据操作…

2026/10/6 20:24:41

2024年Win11+Ubuntu 22.04.1 LTS双系统安装与避坑指南

简介:这份文档面向已装Windows 11、希望再装Ubuntu 22.04.1 LTS组成双系统的用户,覆盖从环境检查到安装完成的全流程。内容包含查看BIOS与UEFI模式、下载ISO镜像、用Rufus制作启动U盘、压缩磁盘分区、BIOS启动项设置,以及安装时选择“与Windo…

2026/10/6 20:24:41

Windows 11 装 Ubuntu 22.04.1 LTS 双系统:UEFI 引导避坑指南

简介:这份文档面向已装好 Windows 11、想再装 Ubuntu 22.04.1 LTS 组建双系统的用户,尤其适合初次接触 UEFI 引导与磁盘分区的新手。内容围绕双系统安装全流程展开,涵盖基础环境检查、Rufus 启动盘制作、与 Windows Boot Manager 共存的安装方…

2026/10/6 20:24:41

轻量AI中台实战:用私有化大模型实现智能录入与对账

去年年底结账,财务主管把一摞打印出来的银行流水拍在桌上,说这个月有三十二笔款项对不上业务订单,业务那边又说,光是补齐客户名称和合同编号就加班了两晚。我当时听完只有一个想法:这不是人不够勤快的问题,…

2026/10/6 20:24:41

国产RFSOC+FPGA双芯宽带高速信号处理板设计实战

这阵子集中做了一块国产RFSOCFPGA双芯架构的宽带高速信号处理板,从方案评估、器件选型到回板调试、跑通数据链路,整套流程走下来,踩了不少坑,也积累了一些值得记录的经验。做这类板卡的人应该都有同感:单看RFSOC的集成…

2026/10/6 20:24:41

大模型评测平台落地方案:集成Coze-Loop与AllData的自动化评估体系

跑了一年多大模型的落地项目,我越来越认同一个判断:真正卡住AI应用落地的,往往不是模型本身的智商,而是“怎么看它到底行不行”这套评测机制。我们团队在AllData平台上集成了开源项目Coze-Loop,搭了一套大模型评测平台…

2026/10/6 20:19:40

System-1 判断模型接入 agent loop:MCP 工具链下的循环控制实践

1. 为什么我会想到把 System-1 判断模型塞进 agent loop 先说清楚这篇要聊的东西是什么。System-1 判断模型,指的是那种"看一眼就给结论"的快速决策模型——它不追求长链条推理,而是在极短时间内对当前状态做一个高置信度的判断,输…

2026/10/5 6:32:56

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/6 4:01:51

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/6 17:46:51

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/6 0:03:23

MR25H40CDF+STM32F031C6工业级高可靠数据存储方案

1. 项目概述:为什么在工业现场非得用 MR25H40CDF 配 STM32F031C6 做数据存储?在工厂产线的 PLC 控制柜里、在风电变流器的散热片背面、在矿井监测终端的金属外壳下,你经常能看到一块指甲盖大小的黑色芯片——它既不是 Flash,也不是…

2026/10/6 0:03:23

MRAM+STM32工业断电数据保全实战指南

1. 项目概述:为什么在工业现场非得用 MR25H40CDF 配 STM32F031C6 做数据存储?在工厂产线的PLC柜里、在野外无人值守的环境监测终端里、在高速运转的包装机控制板上,你经常能看到一块指甲盖大小的黑色芯片,旁边贴着“MR25H40CDF”丝…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑