Redis 分布式锁是利用 Redis 的原子操作特性,实现对共享资源的互斥访问,常见的实现方式主要基于 Redis 的 SET 命令(带 NX 和 PX 参数)或 Lua 脚本,保证加锁和解锁的原子性和安全性。下面我详细讲讲具体的实现步骤和常用方案:


🎯一、分布式锁的核心目标

  1. 互斥性:同一时刻只有一个客户端持有锁,防止资源冲突。

  2. 防死锁:锁应设置超时时间,防止因客户端崩溃而锁永远无法释放。

  3. 解锁安全:只有加锁的客户端能解锁,防止误解锁。


🧠二、最简单的分布式锁实现(基于 SET NX)

加锁示例

SET lock_key unique_value NX PX 30000
  • lock_key:锁的键名

  • unique_value:客户端唯一标识(一般是 UUID)

  • NX:只在 key 不存在时设置,保证原子性加锁

  • PX 30000:设置锁过期时间,防止死锁,这里是 30 秒

返回值:

  • 如果返回 OK,说明成功加锁。

  • 如果返回 null,说明锁已被占用。

解锁示例(安全解锁)

因为锁可能会超时自动释放,或者被其他客户端加锁,所以解锁时要先确认自己是锁的持有者。

用 Lua 脚本保证判断和删除原子性:

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

Java 伪代码:

String script = "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                "return redis.call('del', KEYS[1]) else return 0 end";
redis.eval(script, Collections.singletonList("lock_key"), Collections.singletonList("unique_value"));


✅三、分布式锁完整流程示例

  1. 客户端生成唯一标识(UUID)。

  2. 客户端调用 SET lock_key UUID NX PX 30000 尝试加锁。

  3. 如果成功,业务逻辑执行。

  4. 业务逻辑完成后,执行 Lua 脚本解锁,只删除自己持有的锁。

  5. 如果加锁失败,客户端可选择重试或放弃。


🎯四、分布式锁实现中常见问题与解决

问题说明解决方案
1. 锁过期时间太短,业务没完成锁就释放业务超时导致锁被误删动态续期(watchdog机制),Redisson有实现
2. 客户端崩溃导致死锁锁永远不释放设置合理锁过期时间
3. 解锁误删别人锁多客户端同时操作使用唯一标识判断是否解锁
4. 单Redis节点故障锁失效或失去互斥性使用 Redlock 算法,多个 Redis 节点投票


✅五、Redlock 算法(高级分布式锁方案)

Redlock 是 Redis 官方推荐的分布式锁算法,适用于多节点 Redis 集群环境。

  • 在多个独立 Redis 实例上分别尝试加锁(比如 5 个实例)。

  • 加锁成功达到大多数(3 个)即视为加锁成功。

  • 加锁失败则释放已获得的锁。

  • 解锁时释放所有实例上的锁。

Redlock 能更好保证高可用和容错,但实现复杂,一般建议使用成熟客户端实现,比如 Redisson。


🎯六、Java 常用分布式锁客户端

  • Redisson:基于 Redis 实现的分布式工具集,内置分布式锁支持,支持可重入锁、公平锁、读写锁、信号量等,推荐使用。

  • Spring Data Redis:可以手动实现基于 SET NX PX 的锁逻辑。

  • 其他开源库:如 lettuce、jedis 也可以自定义实现。


✅七、示例代码(Java + Jedis)

String lockKey = "lock:resource";
String uniqueId = UUID.randomUUID().toString();
int expireTime = 30000; // 30秒

// 加锁
String result = jedis.set(lockKey, uniqueId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
    try {
        // 业务处理
    } finally {
        // 释放锁,使用 Lua 脚本保证安全解锁
        String luaScript =
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "return redis.call('del', KEYS[1]) else return 0 end";
        jedis.eval(luaScript, Collections.singletonList(lockKey), Collections.singletonList(uniqueId));
    }
} else {
    // 未获取到锁,重试或失败处理
}

更多推荐