Skip to content

Redis实现分布式锁,请详细说明这个分布式锁的实现原理

使用 Redis 实现分布式锁是通过 Redis 的 单线程特性和原子操作,以键值对的形式设置锁,确保在分布式系统中多个进程或线程只有一个能获取锁。核心原理是利用 SETNX(Set if Not Exists)命令设置锁,结合过期时间防止死锁,解锁时用 Lua 脚本保证原子性。


关键事实

  1. 目标
  2. 在分布式环境中(如多服务器)实现互斥访问。
  3. Redis 特性
  4. 单线程执行命令,保证原子性。
  5. 高性能,支持分布式部署。
  6. 基本步骤
  7. 加锁:设置唯一键,值带标识。
  8. 解锁:检查标识后删除键。
  9. 超时:设置过期时间,避免死锁。

实现原理详解

1. 加锁

  • 命令SET key value NX PX milliseconds
  • NX:键不存在时设置(Set if Not Exists)。
  • PX:设置过期时间(毫秒)。
  • value:唯一标识(如客户端 ID 或随机 UUID)。
  • 原理
  • 多个客户端尝试设置同一键,只有第一个成功,后续失败。
  • 过期时间防止锁未释放导致死锁。
  • 代码(Java + Jedis):
public boolean lock(Jedis jedis, String key, String value, int expireMs) {
    String result = jedis.set(key, value, "NX", "PX", expireMs);
    return "OK".equals(result);
}
  • 示例
  • 客户端 A:SET lock:resource clientA NX PX 30000 -> 成功。
  • 客户端 B:SET lock:resource clientB NX PX 30000 -> 失败。

2. 解锁

  • 问题:直接用 DEL key 可能误删其他客户端的锁。
  • 解决:用 Lua 脚本检查 value 匹配后再删除。
  • Lua 脚本
if redis.call("GET", KEYS[1]) == ARGV[1] then
    return redis.call("DEL", KEYS[1])
else
    return 0
end
  • 原理
  • 确保只有锁的拥有者能释放。
  • GETDEL 在单次执行中完成,原子性。
  • 代码
public void unlock(Jedis jedis, String key, String value) {
    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(key), Collections.singletonList(value));
}

3. 完整示例

public class DistributedLock {
    private Jedis jedis = new Jedis("localhost", 6379);
    private static final String LOCK_KEY = "lock:resource";
    private static final int EXPIRE_MS = 30000; // 30秒

    public boolean tryLock(String clientId) {
        return lock(jedis, LOCK_KEY, clientId, EXPIRE_MS);
    }

    public void releaseLock(String clientId) {
        unlock(jedis, LOCK_KEY, clientId);
    }

    public static void main(String[] args) {
        DistributedLock lock = new DistributedLock();
        String clientId = UUID.randomUUID().toString();
        if (lock.tryLock(clientId)) {
            System.out.println("Lock acquired");
            // 执行业务
            lock.releaseLock(clientId);
            System.out.println("Lock released");
        } else {
            System.out.println("Failed to acquire lock");
        }
    }
}

实现细节与原理

1. 为什么用 SETNX?

  • Redis 单线程执行,SET NX 保证只有一个客户端设置成功,天然互斥。

2. 为什么加过期时间?

  • 防止客户端崩溃未释放锁,过期后自动解锁。
  • 示例:客户端 A 获取锁后宕机,30 秒后锁失效。

3. 为什么用 Lua 解锁?

  • 避免误删:直接 DEL 可能删除其他客户端的锁。
  • 原子性:GETDEL 分开执行可能被中断,Lua 脚本一次完成。

4. 锁续期问题

  • 问题:业务执行超 30 秒,锁过期被他人获取。
  • 解决:客户端定时检查并延长锁(PEXPIRE),如 Redisson 的看门狗机制。

延伸与面试角度

  • 优缺点
  • 优点:简单、高性能、跨节点。
  • 缺点:依赖 Redis 单点,过期时间难估。
  • 与 Zookeeper 对比
  • Redis:高性能,基于内存,弱一致性。
  • Zookeeper:强一致性,基于顺序节点,性能低。
  • 改进
  • Redisson:封装分布式锁,支持锁续期、公平锁。
  • 多节点:用 Redlock 算法,多 Redis 实例投票。
  • 实际应用
  • 秒杀系统:锁库存。
  • 分布式任务:锁任务 ID。
  • 面试点
  • 问“死锁怎么防”时,提过期时间。
  • 问“误删怎么防”时,提 Lua 脚本。

总结

Redis 分布式锁用 SETNX 加锁,设置过期时间防死锁,Lua 脚本原子解锁。原理基于 Redis 单线程和原子性,保证互斥。面试时,可写代码或提 Redlock 改进,展示深入理解。