平时开发中使用 Redis 分布式锁,有哪些需要注意的问题?
在微服务和高并发后端系统开发中,分布式锁已是保障数据一致性的重要手段,而 Redis 以其高性能和易用性成为实现分布式锁的主流方案。但你是否真的了解:Redis 分布式锁容易踩哪些坑?怎样才能用得更安全、更健壮?
本文将结合实际开发经验,详细列举在使用 Redis 分布式锁过程中的注意事项,帮助你避免常见的“坑”。
1. 保证加锁和过期设置的原子性
很多开发者会用如下伪代码实现 Redis 锁:
SETNX lock_key my_value
EXPIRE lock_key 30
这种方式会有严重并发问题:如果在 SETNX
和 EXPIRE
之间进程挂了,锁就可能变成永久锁,导致死锁!
正确做法:
利用 Redis 2.6.12+ 提供的 SET key value NX EX seconds
/PX milliseconds
原子命令,一步加锁+设置过期时间。
SET resource_name my_random_value NX PX 30000
这样就保证了加锁和过期的原子性。
2. 锁必须设置过期时间
绝不能只用 SETNX 不设过期时间!
一旦拿到锁的进程挂了,锁永远不会自动释放,下一个进程就再也拿不到这把锁。
锁的过期时间要比你的业务处理时间略长,并给一定的冗余空间。
3. 解锁操作必须安全,不能误删他人锁
常见的做法是业务完成后直接 DEL lock_key
释放锁。如果你的锁 value 不是唯一的(比如都写死字符串),会出现典型的误删问题。
举例:
- A线程加锁成功,锁 value=abc
- 锁到期自动失效
- B线程加锁,此时重新设置 value=xyz
- A线程业务执行完后,误解锁,直接
DEL
锁 key,把B的锁删了!
更安全的做法:
加锁时,为每次锁都分配唯一value(如uuid),解锁时校验当前value仅由自己持有,使用 Lua 脚本原子性实现:
if redis.call('get', KEYS[1]) == ARGV[1] thenreturn redis.call('del', KEYS[1])
elsereturn 0
end
这样只有锁持有者才能真正释放这把锁,避免误删。
4. 业务超时&锁续期支持
锁的自动过期机制虽然能防止死锁,但若业务执行时间不可控,容易出现锁未用完就到期被别的线程/服务抢走,导致并发安全隐患。
解决办法:
- 加锁进程应定时(如 watch dog 机制)主动刷新锁的过期时间
- 刷新前仍要校验自己的 value,严防误操作
- 推荐用成熟库(比如 Redisson),已帮你实现自动续期
5. 主从复制带来的一致性问题
Redis 高可用常用主从或哨兵架构,但 Redis 复制是异步的。极端情况下,主库刚写完锁,还没同步到从库就挂掉了,故障转移后锁信息丢失,可能导致多个进程同时拿到同一把锁!
应对措施:
- 锁操作只允许写主库节点,绝不能从从库读写锁!
- 重要场景请优先采用 RedLock 算法,实现基于多 Redis 实例的强一致分布式锁
6. 锁Key设计需充分考虑粒度和唯一性
同一个业务或资源一定要有唯一的锁key,不同业务分开加锁,避免无谓的全局大锁导致系统性能下降。
- 通常 key 可用如
lock:业务名:资源标识
- value 用UUID、机器名/线程ID等保证唯一性