> 技术文档 > 平时开发中使用 Redis 分布式锁,有哪些需要注意的问题?

平时开发中使用 Redis 分布式锁,有哪些需要注意的问题?

在微服务和高并发后端系统开发中,分布式锁已是保障数据一致性的重要手段,而 Redis 以其高性能和易用性成为实现分布式锁的主流方案。但你是否真的了解:Redis 分布式锁容易踩哪些坑?怎样才能用得更安全、更健壮?

本文将结合实际开发经验,详细列举在使用 Redis 分布式锁过程中的注意事项,帮助你避免常见的“坑”。


1. 保证加锁和过期设置的原子性

很多开发者会用如下伪代码实现 Redis 锁:

SETNX lock_key my_valueEXPIRE lock_key 30

这种方式会有严重并发问题:如果在 SETNXEXPIRE 之间进程挂了,锁就可能变成永久锁,导致死锁!

正确做法:
利用 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] then return redis.call(\'del\', KEYS[1])else return 0end

这样只有锁持有者才能真正释放这把锁,避免误删。


4. 业务超时&锁续期支持

锁的自动过期机制虽然能防止死锁,但若业务执行时间不可控,容易出现锁未用完就到期被别的线程/服务抢走,导致并发安全隐患。

解决办法:

  • 加锁进程应定时(如 watch dog 机制)主动刷新锁的过期时间
  • 刷新前仍要校验自己的 value,严防误操作
  • 推荐用成熟库(比如 Redisson),已帮你实现自动续期

5. 主从复制带来的一致性问题

Redis 高可用常用主从或哨兵架构,但 Redis 复制是异步的。极端情况下,主库刚写完锁,还没同步到从库就挂掉了,故障转移后锁信息丢失,可能导致多个进程同时拿到同一把锁!

应对措施:

  • 锁操作只允许写主库节点,绝不能从从库读写锁!
  • 重要场景请优先采用 RedLock 算法,实现基于多 Redis 实例的强一致分布式锁

6. 锁Key设计需充分考虑粒度和唯一性

同一个业务或资源一定要有唯一的锁key,不同业务分开加锁,避免无谓的全局大锁导致系统性能下降。

  • 通常 key 可用如 lock:业务名:资源标识
  • value 用UUID、机器名/线程ID等保证唯一性