这是一个非常经典且容易混淆的问题,涉及到 Redis 分布式锁的原子性操作。我们来深入分析:

SETNX lock value EX 10

和

SET lock value NX EX 10

这两条命令看似相似,但第一条是错误用法,第二条才是正确的分布式锁实现方式。


一、逐条解析

❌ 1. SETNX lock value EX 10

  • SETNX 是一个独立命令:SET if Not eXists
  • 它 不支持 EX 参数!
  • 这条命令在 Redis 中是 语法错误,会返回:
    (error) ERR syntax error

📌 原因:SETNX 只接受两个参数:key 和 value。

你试图加上 EX 10(过期时间),但 SETNX 不认识这个参数,所以报错。


✅ 2. SET lock value NX EX 10

  • 这是 Redis 2.6.12+ 版本引入的复合命令。
  • SET 命令支持多个修饰符组合使用:
    • NX:Only set the key if it does not exist.
    • EX:Set the specified expire time, in seconds.
等价于:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
执行逻辑:
  • 如果 lock 不存在,则设置 lock = value,并设置过期时间为 10 秒。
  • 如果 lock 已存在,则什么都不做,返回 nil。

✅ 这是一个原子操作,完美实现“加锁 + 设置过期时间”一步到位。


二、为什么不能分开执行?

有些人可能会想:

# 步骤1:先设值
SETNX lock value
# 步骤2:再设过期时间
EXPIRE lock 10

❌ 问题出在哪里?

如果在 SETNX 成功后,EXPIRE 执行前,服务宕机了,会发生什么?

Thread A:
  SETNX lock value → 成功(返回 1)
  ↓ 系统崩溃!
  EXPIRE lock 10 → 未执行!

结果:

  • 锁被成功获取,但没有设置过期时间!
  • 这个锁会永远存在,导致其他线程永远无法获取锁 → 死锁!

三、正确姿势:使用 SET ... NX EX 原子操作

✅ 正确命令:

SET lock my_random_value NX EX 10
  • 原子性:加锁和设置过期时间是一个操作,不会中断。
  • 防死锁:即使客户端崩溃,10 秒后锁自动释放。
  • 可重入性:通过 my_random_value(如 UUID)标识锁持有者,避免误删。

四、完整分布式锁流程(含解锁)

🔒 加锁

SET lock_uuid NX EX 10

🔓 解锁(必须用 Lua 脚本保证原子性)

-- 如果当前锁的 value 等于我的 uuid,才删除
if redis.call("get", KEYS[1]) == ARGV[1] then
    return redis.call("del", KEYS[1])
else
    return 0
end

Java 示例(使用 Jedis):

String lockKey = "lock:order";
String uuid = UUID.randomUUID().toString();

// 加锁
String result = jedis.set(lockKey, uuid, "NX", "EX", 10);
if ("OK".equals(result)) {
    try {
        // 执行业务逻辑
    } finally {
        // 解锁(Lua 脚本)
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(uuid));
    }
}

五、对比总结

命令是否合法原子性是否推荐说明
SETNX lock value EX 10❌ 否❌绝对不要用语法错误,Redis 不识别
SETNX lock value + EXPIRE lock 10✅ 是❌不推荐非原子,可能死锁
SET lock value NX EX 10✅ 是✅强烈推荐原子操作,安全可靠

六、高级替代方案

虽然 SET ... NX EX 是基础,但在生产环境建议使用更成熟的方案:

方案优点
Redisson提供 RLock,支持看门狗(自动续期)、可重入、公平锁等
Redlock 算法多实例部署,提高可用性(但有争议)
ZooKeeper强一致性,临时节点自动释放锁

✅ 总结

SETNX lock value EX 10 是错误的! SET lock value NX EX 10 才是正确的!

  • SETNX 不支持 EX 参数,强行使用会报语法错误。
  • 必须使用 SET 命令的复合形式,才能实现“加锁 + 过期”原子操作。
  • 分布式锁的核心是:原子性、防死锁、可重入、高可用。

掌握这一点,你就能在面试中清晰解释“如何正确实现 Redis 分布式锁”,避免踩坑!

更多推荐