Redis分布式锁从原生手写到Redisson高阶落地,附线上死锁复盘优化方案
🔥 博主专注后端高并发架构实战、中间件底层拆解、线上疑难故障复盘,定期更新可落地生产级技术方案,干货无废话,建议一键三连收藏,面试刷题、项目改造随时复用,有分布式架构、Redis调优问题可评论区留言,精准答疑!
💡 前置说明:本文适配JDK8+、Redis 6.2+、SpringBoot 3.x全生态,全程无冗余理论,只讲可直接上线的代码+线上真实踩坑案例,新手能看懂,老手能拔高适配面试场景。
一、开篇:为什么高并发业务必须弃用本地锁,改用Redis分布式锁?
日常单体应用中,我们用 synchronized、ReentrantLock 就能完美解决接口并发争抢、重复提交、库存超扣问题,但当下后端项目基本都是微服务集群部署、多实例负载均衡架构,本地锁仅能管控单JVM内部线程资源。
多实例并发场景下,不同服务实例的本地锁相互隔离,锁失效必然引发三大线上高频事故:
-
核心业务超卖:秒杀履约、积分兑换、优惠券核销、虚拟库存扣减场景高频爆发;
-
重复幂等脏数据:回调接口重复回调、定时任务多实例重复执行、订单重复创建;
-
数据一致性错乱:分布式多节点同时修改同一条核心业务数据,引发对账失败、资金异常。
✅ 核心结论:跨实例、跨服务、跨机房的并发互斥场景,唯一低成本通用方案就是Redis分布式锁,不依赖数据库悲观锁、不依赖Zookeeper复杂部署,性能碾压传统分布式锁方案。
二、新手必踩3大致命坑:原生Redis锁手写必翻车现场
很多同学网上抄两段SET NX EX代码就直接上线,线下测试没问题,一到大流量生产环境必出故障,核心根源就是没吃透分布式锁底层互斥逻辑。下面拆解3个高频致命坑,附带故障根因+紧急规避方案。
❌ 坑点1:加锁、过期时间分两条命令执行,突发宕机直接永久死锁
错误写法:先执行SETNX抢锁成功,再单独EXPIRE设置锁过期时间。
高危场景:线程刚抢锁成功,还没来得及执行过期时间命令,服务瞬间宕机、服务器断电、网络闪断。最终锁Key永久常驻Redis,其他业务线程永远抢不到锁,直接全线业务阻塞瘫痪。
❌ 坑点2:锁超时自动释放,业务没跑完,并发穿透引发超卖
业务执行耗时不可控,比如数据库慢查询、网络延迟、第三方接口超时,锁提前到期自动释放。此时其他线程抢占锁并行执行业务,核心库存、资金数据直接错乱,复盘很难定位根因。
❌ 坑点3:无唯一标识,直接DEL删锁,误删别人正在持有的有效锁
所有线程共用同一个固定Key,谁执行完直接强制删除Key。高并发时序错位场景:线程A锁快超时、业务还在执行,线程B成功抢锁,此时线程A执行完毕删除锁,直接把线程B的有效锁删掉,互斥彻底失效。
三、生产可用:原生Redis分布式锁标准落地代码(SpringBoot+Lua脚本)
核心优化思路:原子加锁+唯一防误删+Lua脚本兜底,全程单链路原子执行,杜绝网络、节点异常引发锁失效问题,无需依赖第三方组件,开箱即用。
1、核心依赖引入(pom.xml)
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> </dependency>
2、原子加锁+防误删完整工具类(可直接复制到项目)
@Component public class RedisDistributeLockUtil { @Resource private StringRedisTemplate stringRedisTemplate; // 锁过期时间 30s private static final long LOCK_EXPIRE_TIME = 30; // 线程自旋重试间隔 50ms private static final long RETRY_INTERVAL = 50; /** * 尝试抢占分布式锁 * @param lockKey 业务唯一锁Key * @param lockValue 全局唯一线程标识(UUID+线程ID) * @return true=抢锁成功 false=抢锁失败 */ public boolean tryLock(String lockKey, String lockValue) { // Lua脚本:原子判断+加锁+设置过期时间,杜绝宕机死锁 String luaScript = "if redis.call('setnx',KEYS[1],ARGV[1]) == 1 then redis.call('expire',KEYS[1],ARGV[2]) return 1 else return 0 end"; DefaultRedisScript<Long> script = DefaultRedisScript.create(luaScript, Long.class); Long result = stringRedisTemplate.execute(script, Collections.singletonList(lockKey), lockValue, String.valueOf(LOCK_EXPIRE_TIME)); return Objects.equals(result, 1L); } /** * 安全释放锁:匹配Value再删除,防止误删他人锁 */ public void releaseLock(String lockKey, String lockValue) { String luaScript = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end"; DefaultRedisScript<Long> script = DefaultRedisScript.create(luaScript, Long.class); stringRedisTemplate.execute(script, Collections.singletonList(lockKey), lockValue); } }
3、业务层标准调用示范(秒杀/库存扣减直接套用)
@Service public class SeckillOrderServiceImpl implements SeckillOrderService { @Resource private RedisDistributeLockUtil redisDistributeLockUtil; @Resource private StockMapper stockMapper; @Override @Transactional(rollbackFor = Exception.class) public String doSeckill(Long goodsId) { String lockKey = "seckill:stock:lock:" + goodsId; // 全局唯一标识,杜绝跨线程误删锁 String lockValue = UUID.randomUUID().toString(true) + Thread.currentThread().getId(); boolean lockSuccess = false; try { // 自旋重试3次,适配瞬时高并发抢锁场景 for (int i = 0; i < 3; i++) { if (redisDistributeLockUtil.tryLock(lockKey, lockValue)) { lockSuccess = true; break; } Thread.sleep(RETRY_INTERVAL); } if (!lockSuccess) { return "当前抢购人数过多,请稍后重试"; } // 核心业务:查询库存+校验+扣减库存+创建订单 int stock = stockMapper.getStockByGoodsId(goodsId); if (stock <= 0) { return "商品库存不足,抢购失败"; } stockMapper.reduceStock(goodsId); return "秒杀下单成功"; } catch (Exception e) { throw new RuntimeException("秒杀业务异常", e); } finally { // 无论成功失败,最终安全释放锁 if (lockSuccess) { redisDistributeLockUtil.releaseLock(lockKey, lockValue); } } } }
四、进阶补强:解决锁超时隐患,新手手写做不到的自动续期
上面原生代码能解决90%基础并发问题,但解决不了业务耗时不可控、锁提前过期核心痛点。手动写定时任务续期,代码臃肿、精度差、容易内存泄漏,生产高并发场景绝不推荐。
✅ 企业级标准答案:直接使用Redisson自带看门机(WatchDog)自动续期机制,零额外开发、底层成熟稳定,大厂生产通用方案。
五、高阶生产方案:Redisson分布式锁快速接入+极简落地
1、引入Redisson核心依赖(适配SpringBoot3.x)
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson-spring-boot-starter</artifactId> <version>3.27.0</version> </dependency>
2、一行注解极简使用,自动续期、自动释放、自动重试
@Service public class StockBusinessServiceImpl { @Resource private RedissonClient redissonClient; public void deductGoodsStock(Long skuId) { String lockKey = "stock:lock:sku:" + skuId; RLock lock = redissonClient.getLock(lockKey); try { // 无参lock:默认看门狗续期,业务不中断,锁永久有效直到主动释放 lock.lock(); // 执行核心库存扣减、幂等校验业务逻辑 } finally { // 安全释放锁,Redisson自带线程校验,杜绝误删 if (lock.isLocked() && lock.isHeldByCurrentThread()) { lock.unlock(); } } } }
3、Redisson核心碾压原生锁的3大优势(面试高频考点)
-
看门狗自动续期:默认后台异步定时延长锁过期时间,业务多久执行,锁就多久有效,彻底解决超时释放隐患;
-
可重入公平锁互斥:支持同一线程多次加锁,适配嵌套事务、多层业务调用场景,无需手动兼容;
-
集群/主从/哨兵全适配:原生支持Redis集群架构,自带锁容错机制,节点宕机不丢锁,适配线上多节点部署。
六、线上真实故障复盘:分布式锁失效引发批量超卖事故
🔍 故障背景
电商整点秒杀活动,瞬时并发1.2万QPS,前期压测正常,上线后突发200+笔超卖订单,对账资金差额异常,紧急回滚止损。
🔍 根因定位
开发人员手写Redis锁,加锁和设置过期时间分两条命令执行,流量峰值阶段,某服务实例加锁成功后瞬间GC卡顿,未执行过期时间命令,锁永久常驻Redis,后续线程全部抢锁失败,业务降级逻辑未兜底,最终引发流量击穿数据库。
✅ 整改方案
全量替换为Lua脚本原子加锁,核心秒杀链路直接切Redisson,新增锁等待超时熔断兜底,全链路压测回归,后续半年零故障。
七、文末总结+生产落地 Checklist
1、低并发、非核心边缘业务:Lua原生分布式锁足够使用,轻量化无依赖;
2、高并发、资金、库存、订单核心链路:强制统一使用Redisson,拒绝手写自研;
3、永远遵守三原则:原子加锁、唯一Value防误删、finally强制释放;
4、线上必备兜底:锁等待超时熔断、异常日志全埋点,方便快速复盘故障。
💡 码字不易,全是一线生产实战干货!觉得有用点赞+收藏+关注,下期分享《Redis缓存穿透/击穿/雪崩全套解决方案,附大厂应急预案》,评论区扣1直接发完整架构思维导图!