Redis分布式锁解决超卖和重复提交的核心是串行化关键操作;原生SETNX易因锁过期、非原子删锁、主从延迟导致问题,Redisson通过自动续期、Lua原子校验、可重入等机制提供开箱即用方案。

Java 中用 Redis 分布式锁解决超卖和重复提交,核心是让并发请求串行化执行关键操作。直接用 SETNX 容易出错,生产环境强烈推荐 Redisson —— 它自动处理续期、防误删、可重入等难题,真正开箱即用。
为什么原生 SETNX 很难用好
手写 SETNX + EXPIRE 看似简单,但藏着三个高频事故点:
- 锁过期但业务没结束:比如设了 10 秒过期,扣库存+下单耗时 12 秒,锁提前释放,其他线程趁虚而入
- 删锁不是原子操作:先 GET 判断 value 是自己的,再 DEL —— 中间可能被中断,导致删掉别人正在用的锁
- 主从延迟丢锁:Redis 主节点写入锁成功,还没同步到从节点就宕机,新主节点无锁状态,多个客户端同时拿到锁
Redisson 是怎么解决的
Redisson 把分布式锁变成了“带看门狗的智能锁”:
- 自动续期:加锁后启动后台心跳,只要业务还在跑,就不断延长锁过期时间(默认 30 秒续一次)
- 唯一标识校验:每个锁绑定线程 ID + UUID,释放时用 Lua 脚本原子判断并删除,杜绝误删
- 支持多种锁类型:可重入锁(同一线程多次 lock/unlock)、公平锁(按申请顺序排队)、读写锁(读多写少场景)
- 集群高可用:适配 Redis 单机、哨兵、Cluster 模式,自动感知节点故障
Redisson 实战代码(秒杀库存扣减)
以 SpringBoot 项目为例,三步落地:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
立即学习“Java免费学习笔记(深入)”;
-
引入依赖:
<dependency><br> <groupId>org.redisson</groupId><br> <artifactId>redisson-spring-boot-starter</artifactId><br> <version>3.23.5</version><br></dependency>
-
配置 Redis 连接(application.yml):
spring:<br> redis:<br> host: 127.0.0.1<br> port: 6379<br> password:
-
业务代码加锁:
RLock lock = redissonClient.getLock("lock:goods:" + goodsId);<br>if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {<br> try {<br> // 查询库存、扣减、生成订单<br> int stock = stockMapper.selectStock(goodsId);<br> if (stock > 0) {<br> stockMapper.updateStock(goodsId);<br> orderService.createOrder(goodsId);<br> }<br> } finally {<br> lock.unlock(); // 自动识别是否自己持有的锁<br> }<br>} else {<br> throw new RuntimeException("商品抢购中,请稍后再试");<br>}
重复提交的轻量级方案(注解 + Redis)
对非秒杀类场景(如表单提交、优惠券领取),可用更轻量方式:
- 定义注解
@PreventDuplicateSubmit(expireSeconds = 60) - AOP 拦截器中生成唯一 key(如
"submit:" + userId + ":" + md5(requestParams)) - 用
stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 60, TimeUnit.SECONDS)尝试加锁 - 加锁失败直接返回“请勿重复提交”,成功则放行
这种方式不依赖 Redisson,适合快速接入,但需注意 key 设计要足够唯一,避免不同用户参数哈希碰撞。

















