MENU

Redis 实现分布式锁

July 23, 2025 • 已被 40 位童鞋围观过 • 代码分享

添加唯一任务防止重复

        foreach ($documents as $k => $v){
            $domains[] = $v["domain"];
            $lockKey = "domain_lock:".$v["domain"];
            if (RedisUtil::setnx($lockKey, 1)) {
                // 设置锁过期时间,防止死锁
                RedisUtil::expire($lockKey, 3600); // 1小时过期
                // 将域名添加到队列
                RedisUtil::lpush("domain_queue", $v["domain"]);
            }
        }

更安全的写法

            foreach ($documents as $k => $v){
                $domains[] = $v["domain"];
                $lockKey = "domain_lock:".$v["domain"];
                if (RedisUtil::set($lockKey, 1, ['NX', 'EX' => 3600])) {
                    // 将域名添加到队列
                    RedisUtil::lPush("domain_queue", $v["domain"]);
                }
            }

这句代码的意思

$redis->setnx($lockKey, 1);

是 Redis 原生命令 SETNX 的 PHP 调用,意思是:

只有当指定的 key $lockKey 不存在时,才将其设置为 1。如果该 key 已存在,则什么都不做。

📌 用法解释:

  • setnx = set if not exists(如果不存在就设置)
  • 返回值是:

    • 1:表示设置成功(key 原来不存在)
    • 0:表示设置失败(key 已经存在)

💡 典型用途:实现分布式锁

$lockKey = 'lock:my_job';

if ($redis->setnx($lockKey, 1)) {
    // 获取锁成功
    // 设置过期时间,避免死锁
    $redis->expire($lockKey, 10); // 锁有效期10秒

    try {
        // 执行业务逻辑
    } finally {
        // 释放锁
        $redis->del($lockKey);
    }
} else {
    // 获取锁失败,可能其他进程正在执行
}

⚠️ 注意:

SETNX 本身 不支持设置过期时间 ,所以需要 单独调用 expire() 来避免锁永远不释放。

为了解决这个问题,Redis 后来引入了更强大的原子命令:

$redis->set($lockKey, 1, ['NX', 'EX' => 10]); // 同时实现 SETNX 和 10 秒过期
$redis->set($lockKey, 1, 'NX', 'EX', 10);

✅ 总结:

命令含义适用场景
setnx如果 key 不存在就设置分布式锁、单例执行
hSetNx如果哈希字段不存在就设置哈希结构的数据存储
set ... NX EX原子操作 + 过期时间设置推荐方式,安全高效