Redis

关注公众号 jb51net

关闭
首页 > 数据库 > Redis > Redis分布式锁

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

作者: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

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

三、安全释放锁: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 提供了开箱即用的分布式锁,完美支持:

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 原理

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

七、原生 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   # 重入次数

加锁时:

释放时:

九、最佳实践与注意事项

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

十、常见问题(FAQ)

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

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

解决方案

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

您可能感兴趣的文章:
阅读全文