侧边栏壁纸
博主头像
colo

欲买桂花同载酒

  • 累计撰写 1823 篇文章
  • 累计收到 0 条评论

设计高并发场景下的Redis分布式锁,解决锁续期与锁释放问题

2025-12-12 / 0 评论 / 3 阅读

题目

设计高并发场景下的Redis分布式锁,解决锁续期与锁释放问题

信息

  • 类型:问答
  • 难度:⭐⭐⭐

考点

分布式锁原理,Redis原子操作,锁续期(Watchdog),锁释放的安全性,高并发场景设计

快速回答

实现安全可靠的Redis分布式锁需要解决三个核心问题:

  1. 原子加锁:使用SET命令的NX和PX选项确保原子性
  2. 锁续期:通过后台线程(Watchdog)定期延长锁过期时间
  3. 安全释放:使用Lua脚本验证锁归属后再释放

典型实现方案:Redisson的RLock,包含锁续期机制和释放保护。

解析

1. 核心问题与解决思路

分布式锁三大挑战:

  • 原子性获取锁:避免多个客户端同时获得锁
  • 锁续期(Watchdog):防止业务执行超时导致锁自动释放
  • 安全释放:确保只有锁持有者能释放锁

2. 基础实现方案

原子加锁(Redis命令):

SET lock_key $unique_id NX PX 30000
  • NX:仅当key不存在时设置
  • PX 30000:设置30秒过期时间
  • $unique_id:客户端唯一标识(如UUID)

安全释放锁(Lua脚本):

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

3. 锁续期(Watchdog)设计

必要性:业务执行时间可能超过锁的初始过期时间

实现方案:

  1. 获取锁成功后启动守护线程
  2. 每10秒(建议为过期时间的1/3)检查锁是否仍持有
  3. 通过Lua脚本延长锁过期时间:
    if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("pexpire", KEYS[1], ARGV[2])
    else
        return 0
    end
  4. 业务完成时停止续期并释放锁

4. 完整流程示例(Java伪代码)

// 加锁
String lockKey = "order_lock";
String clientId = UUID.randomUUID().toString();
boolean locked = redis.set(lockKey, clientId, "NX", "PX", 30000);

if (locked) {
    // 启动Watchdog线程
    Thread watchdog = new Thread(() -> {
        while (!Thread.interrupted()) {
            // 每10秒续期一次
            redis.eval(续期Lua脚本, lockKey, clientId, 30000);
            sleep(10000);
        }
    });
    watchdog.start();

    try {
        // 执行业务逻辑
        processBusiness();
    } finally {
        // 停止续期并释放锁
        watchdog.interrupt();
        redis.eval(释放锁Lua脚本, lockKey, clientId);
    }
}

5. 最佳实践与注意事项

  • 唯一客户端标识:必须使用全局唯一ID(如UUID+线程ID)
  • 过期时间设置:初始时间需大于业务平均处理时间,建议设置超时阈值
  • 网络分区处理:Redis集群故障时可能产生脑裂,需配合Redlock等算法
  • 避免GC停顿:JVM Full GC可能导致Watchdog线程暂停,需监控GC状态
  • 推荐方案:优先使用成熟库(如Redisson的RLock)

6. 常见错误

  • 错误1:使用SETNX+EXPIRE非原子操作(可能死锁)
  • 错误2:未验证锁归属直接删除(可能释放他人锁)
  • 错误3:未处理锁续期导致业务中途锁失效
  • 错误4:未设置超时时间(服务崩溃时永久死锁)

7. 扩展知识

  • Redlock算法:多Redis实例的分布式锁协议,解决单点故障问题
  • 锁等待队列:通过Redis的List或ZSET实现获取锁排队
  • 锁重入:同一线程多次加锁需维护计数器(Redisson已实现)
  • 异步复制风险:主从切换可能导致锁丢失,需开启WAIT命令