一个更高效的 Redis 锁

Discourse 广泛使用 Redis 锁作为同步机制。Discourse 的 Redis 锁实现(如下所示)可以优化,以减少所需的 Redis 命令数量,从而降低往返时间和 Redis 执行这些命令的成本。

我们如何改进它

lockunlock 都使用了 Redis 事务lock 过程可以简化为以下代码:

WATCH key
GET key # 判断键是否已过期
MULTI
SET key
EXPIRE key, expire_time + 1

使用 Redis 事务的原因(我推测)似乎是:在真正设置键之前,先检查该键是否已过期。

但我觉得我们可以直接使用 Redis 提供的 SETEX 命令,该命令可以设置一个带过期时间的键。事实上,Redis SETEX 文档 也将其作为用 SETEX 替代 SETEXPIRE 的示例。

以下是我们应进行替换的理由:

  1. 将值设置为过期时间并在设置键之前进行检查是不必要的。TTL 机制本身已足以确保键正确过期。
  2. 即使我们决定将过期时间作为值,也不需要任何事务。因为在 GET(上述代码第 2 行)与 MULTIEXEC 之间提供原子性并无实际意义。这是因为如果锁未获取成功,系统会进行重试。

历史备注

我查阅了 Git 提交历史以获取更多信息。看起来在 Redis 锁被 引入 时,我们并未使用 Redis 提供的 TTL 功能。TTL 特性是在 很久之后 才加入的。

因此,我认为我们可以更进一步,完全移除 Redis 事务。

参见 Develop with Redis | Docs

我们或许可以放弃整个事务,改用带有 px 和 nx 选项的 set 命令。