1. 分布式锁失效的典型场景剖析
最近在排查一个线上事故时发现,明明系统已经加了Redis分布式锁,但在促销活动的高并发场景下,库存数据仍然出现了超卖。这个现象让我意识到,很多开发者(包括曾经的我)对分布式锁存在严重的认知误区。今天我们就来深挖这个90%开发者都会踩的坑。
分布式锁失效通常发生在以下三种典型场景:
- 锁过期时间设置不合理,导致业务未执行完锁已释放
- 锁被其他线程误删,出现非持有者的删除操作
- 锁未实现可重入特性,造成同一线程多次获取锁失败
1.1 锁过期时间的陷阱
Redis分布式锁通常使用SETNX命令实现,但90%的初级开发者会忽略一个关键点:锁必须设置过期时间。更可怕的是,即便设置了过期时间,仍然可能出问题。比如:
java复制// 错误示范1:未设置过期时间
Boolean result = redisTemplate.opsForValue().setIfAbsent("lock_key", "1");
// 错误示范2:设置过期时间但与加锁非原子操作
Boolean result = redisTemplate.opsForValue().setIfAbsent("lock_key", "1");
redisTemplate.expire("lock_key", 30, TimeUnit.SECONDS);
正确的做法应该是使用原子性操作:
java复制// 正确写法:原子性加锁并设置过期时间
Boolean result = redisTemplate.opsForValue().setIfAbsent(
"lock_key",
"1",
30,
TimeUnit.SECONDS
);
关键经验:锁的过期时间应该大于业务执行的最长时间。建议通过压测确定合理的过期时间,通常设置为平均业务耗时的3倍。
1.2 锁误删的隐蔽风险
另一个常见问题是锁被非持有者删除。假设线程A获取锁后执行时间超过锁过期时间,此时锁自动释放,线程B获取到锁。接着线程A执行完毕尝试删除锁,就会误删线程B的锁。
解决方案是为每个锁设置唯一标识:
java复制String clientId = UUID.randomUUID().toString
