目录
- 用 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 实例)
- 使用 ZooKeeper 或 etcd 实现更安全的分布式锁
- 多数场景下,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)!
精彩评论