开发者

如何使用Redis 实现分布式锁(含自动续期与安全释放)

开发者 https://www.devze.com 2025-08-14 09:10 出处:网络 作者: csdn_tom_168
目录用 Redis 实现分布式锁(含ZRqtM自动续期与安全释放)详解一、分布式锁的核心要求二、基础实现:SET + NX + EX三、安全释放锁:Lua 脚本防误删✅ Lua 脚本(unlock.lua)Java 调用示例:四、自动续期(Watchdog
目录
  • 用 Redis 实现分布式锁(含ZRqtM自动续期与安全释放)详解
    • 一、分布式锁的核心要求
    • 二、基础实现:SET + NX + EX
    • 三、安全释放锁:Lua 脚本防误删
      • ✅ Lua 脚本(unlock.lua)
      • Java 调用示例:
    • 四、自动续期(Watchdog 机制)
      • Watchdog 工作流程:
    • 五、完整实现方案对比
      • 六、使用 Redisson 实现(推荐方案)
        • 1. 添加依赖
        • 2. 获取锁并自动续期
        • 3. Redisson 的 Watchdog 原理
      • 七、原生 Redis + Lua 实现(学习用)
        • 1. 加锁(SETNX + EX)
        • 2. 启动 Watchdog 线程
        • 3. 释放锁(Lua 脚本)
        • 4. 使用示例
      • 八、可重入锁实现思路
        • 九、最佳实践与注意事项
          • 十、常见问题(FAQ)
            • Q1:Redis 主从切换会导致锁失效吗?
            • Q2:Watchdog 占用资源吗?
            • Q3:能用 SETNX + DEL 吗?
          • 十一、总结:Redis 分布式锁实现方案对比

          用 Redis 实现分布式锁(含自动续期与安全释放)详解

          在分布式系统中,多个服务实例可能同时操作共享资源(如库存扣减、订单生成),为保证数据一致性,必须使用 分布式锁。Redis 凭借其高性能和原子操作能力,成为实现分布式锁的常用选择。

          本文将深入讲解如何用 Redis 实现一个 安全、可重入、带自动续期(Watchdog)、支持高可用 的分布式锁,并提供 Java 实现示例(含 Redisson 与原生 Lua 脚本两种方式)。

          一、分布式锁的核心要求

          要求说明
          互斥性同一时间只有一个客户端能持有锁
          可重入性同一线程可多次获取同一把锁
          锁释放安全只能由加锁的客户端释放(防误删)
          自动续期(Watchdog)防止业务执行时间超过锁过期时间
          高可用支持主从、集群、哨兵模式
          高性能加锁/释放速度快,不影响业务

          二、基础实现:SET + NX + EX

          最简单的分布式锁实现:

          Shttp://www.devze.comET lock:order:12345 "client_1" NX EX 10
          • NX:key 不存在时才设置(保证互斥)
          • EX 10:10秒后自动过期(防死锁)
          • client_1:唯一客户端标识(用于释放时校验)

          问题:无续期机制,业务执行超时会自动释放,导致并发。

          三、安全释放锁:Lua 脚本防误删

          直接 DEL 锁可能误删其他客户端的锁。应使用 Lua 脚本校验 value

          ✅ Lua 脚本(unlock.lua)

          -- KEYS[1] = lock key
          -- ARGV[1] = client_id
          if redis.call('get', KEYS[1]) == ARGV[1] then
              return redis.call('del', KEYS[1])
          else
              return 0
          end

          Java 调用示例:

          public boolean unlock(String lockKey, String clientId) {
              String script = 
                  "if redis.call('get', KEYS[1]) == ARGV[1] then " +
                  "   return redis.call('del', KEYS[1]) " +
                  " else " +
                  "   return 0 " +
                  " end";
              DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
              redisScript.setScriptText(script);
              redisScript.setResultType(Long.class);
              Long result = redisTemplate.execute(
                  redisScript,
                  Collections.singletonList(lockKey),
                  clientId
              );
              return result != null && result == 1;
          }

          四、自动续期(Watchdog 机制)

          如果业务执行时间超过锁过期时间,锁会被自动释放,导致多个客户端同时进入临界区。

          解决方案:启动一个后台线程(Watchdog),每隔一段时间检查锁是否仍被持有,若持有则延长过期时间。

          Watchdog 工作流程:

          客户端A加锁(EX 30s)
               ↓
          启动 Watchdog 线程(每10s检查一次)
               ↓
          若锁仍存在且属于本客户端 → 执行 EXPIRE lock:xxx 30
               ↓
          业务执行完成 → 取消续期 + 释放锁
          

          五、完整实现方案对比

          方案是否推荐说明
          原生 SET + Lua⚠️ 基础可用需自行实现续期、可重入
          Redisson(推荐)✅ 强烈推荐内置 Watchdog、可重入、公平锁等
          Jedis + 自研❌ 不推荐容易出错,维护成本高

          六、使用 Redisson 实现(推荐方案)

          Redisson 提供了开箱即用的分布式锁,完美支持:

          • 可重入
          • 自动续期(Watchdog)
          • 公平锁、读写锁
          • 高可用(集群/哨兵)

          1. 添加依赖

          <dependency>
              <groupId>org.redisson</groupId>
              <artifactId>redisson</artifactId>
              <version>3.24.1</version>
          </dependency>

          2. 获取锁并自动续期

          @Autowired
          private RedissonClient redissonClient;
          public void doBusiness() {
              RLock lock = redissonClient.getLock("lock:order:12345");
              try {
                  // 尝试加锁,最多等待10秒,上锁后30秒自动解锁
                  boolean res = lock.tryLock(10, 30, TimeUnit.SECONDS);
                  if (res) {
                      try {
                          // 执行业务逻辑(可能耗时较长)
                          System.out.println("Locked! Doing business...");
                          Thread.sleep(25000); // 模拟长任务
                      } finally {
                          lock.unlock(); // 自动取消续期 + 安全释放
                      }
                  } else {
                      System.out.println("Failed to acquire lock");
                  }
              } catch (InterruptedException e) {
                  Thread.currentThread().interrupt();
              }
          }

          3. Redisson 的 Watchdog 原理

          • 默认锁过期时间:30s
          • Watchdog 每 10s 检查一次
          • 若客户端仍持有锁 → 自动 EXPIRE lock:xxx 30
          • 解锁时自动取消续期

          ✅ 无需担心业务超时,只要客户端存活,锁就不会被释放。

          七、原生 Redis + Lua 实现(学习用)

          如果你不想使用 Redisson,也可自行实现 Watchdog。

          1. 加锁(SETNX + EX)

          public boolean tryLock(String lockKey, String clientId, int expireSeconds) {
              String result = redisTemplate.opsForValue()
                  .setIfAbsent(lockKey, clientId, expireSeconds, TimeUnit.SECONDS);
              return Boolean.TRUE.equals(result);
          }

          2. 启动 Watchdog 线程

          private ScheduledExecutorServihttp://www.devze.comce scheduler = Executors.newScheduledThreadPool(1);
          private volatile boolean isLocked = false;
          public void watchDog(String lockKey, String clientId, int expireSeconds) {
              isLocked = true;
              scheduler.scheduleAtFixedRate(() -> {
                  if (isLocked) {
                      // 只有当前客户端持有锁时才续期
                      String current = redisTemplate.opsForValue().get(lockKey);
                      if (clientId.equals(current)) {
                          redisTemplate.expire(lockKey, expireSeconds, TimeUnit.SECONDS);
                      }
                  }
              }, expireSeconds / 3, expireSeconds / 3, TimeUnit.SECONDS);
          }

          3. 释放锁(Lua 脚本)

          见前文 Lua 脚本实现。

          4. 使用示例

          String lockKey = "lock:order:12345";
          String clientId = "client_" + Thread.currentThread().getId();
          if (tryLock(lockKey, clientId, 30)) {
              try {
                  watchDog(lockKey, clientId, 30); // 启动续期
                  // 执行业务
              } finally {
                  isLocked = false; // 停止续期
                  unlock(lockKey, clientId); // 安全释放
              }
          }

          八、可重入锁实现思路

          可重入锁需记录:

          • 当前持有线程
          • 重入次数

          可用 Hash 结构实现:

          # 锁结构
          lock:order:12345
            field: client_1
            value: 2   # 重入次数

          加锁时:

          • 若 key 不存在 → 设置 client_1:1
          • 若 key 存在且 field == client_1 → value +1
          • 否则失败

          释放时:

          • value -1,为 0 时删除 key

          九、最佳实践与注意事项

          项目建议
          安全释放必须使用 Lua 脚本校验 client_id
          锁过期时间设置合理(如 10~30s),避免过长导致阻塞
          自动续期使用 Redisson 或自研 Watchdog
          可重入生产环境必须支持
          阻塞操作锁内避免网络调用、sleep
          监控记录加锁失败、等待时间
          异常处理确保 finally 中释放锁
          高可用使用 Redis 集群或哨兵

          十、常见问题(FAQ)

          Q1:Redis 主从切换会导致锁失效吗?

          ✅ 会!主节点加锁后未同步到从节点,主节点宕机,从节点升主,锁丢失。

          解决方案

          • 使用 Redlock 算法(多个独立 Redis 实例)
          • 使用 ZooKeeperetcd 实现更安全的分布式锁
          • 多数场景下,Redisson + 主从已足够(牺牲 CAP 中的 CP)

          Q2:Watchdog 占用资源吗?

          ✅ 占用少量 CPU 和连接,但可接受。Redisson 默认只在持有锁时启动。

          Q3:能用 SETNX + DEL 吗?

          ❌ 不安全!DEL 可能误删其他客户端的锁。

          十一、总结:Redis 分布式锁实现方案对比

          方案是否可重入自动续期安全释放推荐度
          SETNX + DEL
          SETNX + Lua⚠️⭐⭐⭐
          自研 Watchdog⚠️⭐⭐⭐⭐
          Redisson⭐⭐⭐⭐⭐

          结语

          使用 Redis 实现分布式锁,强烈推荐使用 Redisson。它封装了复杂的细节(可重入、续期、安全释放),让你像使用本地锁一样操作分布式锁。

          核心代码一句话:

          RLock lock = redisson.getLock("myLock");
          lock.tryLock(10, 30, TimeUnit.SECONDS);

          简单、安全、高效,是生产环境的最佳实践

          到此这篇关于如何使用Redis 实现分布式锁(含自动续期与安全释放)的文章就介绍到这了,更多相关Redis分布式锁内容请搜索编程客栈(www.devze.com)编程以前的文章或继续浏览下面的相关文android章希望大家以后多多支持编程客栈(www.devze.com)!

          0

          精彩评论

          暂无评论...
          验证码 换一张
          取 消

          关注公众号