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

 更新时间:2025年08月12日 09:32:21   作者:csdn_tom_168  
本文详解用Redis实现分布式锁,包含自动续期与安全释放,推荐使用Redisson,内置可重入、Watchdog及高可用支持,对比原生和自研方案,强调其在生产环境的高效与安全性,感兴趣的朋友跟随小编一起看看吧

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

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

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

一、分布式锁的核心要求

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

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

最简单的分布式锁实现:

SET 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 ScheduledExecutorService 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分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Redis不同数据类型使用场景代码实例

    Redis不同数据类型使用场景代码实例

    这篇文章主要介绍了Redis不同数据类型使用场景代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-12-12
  • Redis持久化机制之RDB与AOF的使用

    Redis持久化机制之RDB与AOF的使用

    这篇文章主要介绍了Redis持久化机制之RDB与AOF的使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-05-05
  • Redis 实现队列原理的实例详解

    Redis 实现队列原理的实例详解

    这篇文章主要介绍了Redis 实现队列原理的实例详解的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下
    2017-09-09
  • Redis整合Lua脚本的实现操作

    Redis整合Lua脚本的实现操作

    Redis对lua脚本的支持是从Redis2.6.0版本开始引入的,它可以让用户在Redis服务器内置的Lua解释器中执行指定的lua脚本,本文就来介绍一下Redis整合Lua脚本的实现,感兴趣的可以了解一下
    2024-03-03
  • Redis中跳表的实现原理分析

    Redis中跳表的实现原理分析

    Redis中的跳表是一种高效的多层链表结构,通过随机概率算法决定节点的层数,从而实现快速的插入、删除和查询操作,跳表的平均时间复杂度为O(logn),最差情况为O(n),每个节点包含值和指向更高层节点的指针,以及回退指针以提高操作效率
    2025-02-02
  • Redis配置外网可访问(redis远程连接不上)的方法

    Redis配置外网可访问(redis远程连接不上)的方法

    默认情况下,当我们在部署了redis服务之后,redis本身默认只允许本地访问。Redis服务端只允许它所在服务器上的客户端访问,如果Redis服务端和Redis客户端不在同一个机器上,就要进行配置。
    2022-12-12
  • Redis与缓存解读

    Redis与缓存解读

    文章介绍了Redis作为缓存层的优势和缺点,并分析了六种缓存更新策略,包括超时剔除、先删缓存再更新数据库、旁路缓存、先更新数据库再删缓存、先更新数据库再更新缓存、读写穿透和异步缓存写入模式,还讨论了缓存常见问题
    2025-01-01
  • Redis超详细分析分布式锁

    Redis超详细分析分布式锁

    在单体应用中,如果我们对共享数据不进行加锁操作,会出现数据一致性问题,我们的解决办法通常是加锁。下面我们一起聊聊使用redis来实现分布式锁
    2022-07-07
  • Redis分布式锁的实现方式

    Redis分布式锁的实现方式

    本文主要介绍了Redis分布式锁的实现方式,分布式锁是 满足分布式系统或集群模式下多进程可见并且互斥的锁。感兴趣的同学可以参考阅读
    2023-04-04
  • redis 亿级数据读取的实现

    redis 亿级数据读取的实现

    本文主要介绍了redis 亿级数据读取的实现,亿级数据规模下实现高效的数据读取成为了许多企业和开发者面临的重大挑战,下面就来介绍一下,感兴趣的可以了解一下
    2024-08-08

最新评论