不止一叶代码屋 个人技术博客 · 代码与生活的记录

后端开发

Redis 分布式锁:从能用写到不出事

最朴素的写法

SET lock_key 1 NX EX 10

NX 表示不存在才设置,EX 是过期时间。这一行是原子的,没问题。

问题出在后面。

坑一:锁被别人删了

// 线程 A 加锁,10 秒后过期
// 线程 B 拿到锁
// 线程 A 执行完,删锁 —— 删掉的是 B 的锁

解法:锁的值存一个随机唯一 ID,删之前比对:

if redis.call('get', KEYS[1]) == ARGV[1] then
  return redis.call('del', KEYS[1])
else
  return 0
end

必须用 Lua 保证「比对 + 删除」的原子性,分两步还是会出问题。

坑二:任务没做完锁就过期

业务跑了 15 秒,锁只有 10 秒。第二个线程就进来了。

两种解法:

  1. 把过期时间设得足够长(简单,但不够优雅)
  2. 看门狗续期(复杂,但可靠)

看门狗的思路:加锁成功后起一个定时任务,每隔 过期时间/3 就把锁续期一次,直到业务完成主动释放。

坑三:主从切换丢锁

Redis 主节点加锁成功,还没来得及同步到从节点就挂了。从节点被提升为主,锁就没了。

这个问题单靠 Redis 解决不了,需要 Redlock 算法或者干脆换 ZooKeeper / etcd。

我的选择

对于博客这种规模的项目,我的判断是:

  • 单机部署:根本不需要分布式锁,用语言内置的锁就够了
  • 主从部署:接受极小概率的重复执行,做好幂等
  • 强一致要求:别用 Redis,上 etcd

小结

分布式锁是用简单方案解决复杂问题的典型。真正重要的是想清楚:锁失效的最坏后果是什么。如果是扣钱,那就别用 Redis 锁;如果是重复发一封邮件,那接受它。

订阅 RSS

← 返回首页