有网友提了这几个需求,这里,我整理了特别篇发出来
《7天学会Redis》特别篇: 如何保证缓存与数据库的数据一致性?
《7天学会Redis》特别篇: Redis分布式锁
Redis分布式锁
1. Redis分布式锁的基础实现
1.1 使用SET命令实现
Redis分布式锁最基本且关键的实现方式是使用SET命令的NX(Not eXists)和PX(毫秒级过期时间)参数。这种实现方式简单直接,能够满足基本的分布式锁需求。
实现原理:
-
NX参数:确保只在键不存在时设置值,实现了互斥性
-
PX参数:设置键的过期时间(毫秒),防止死锁
-
唯一值:每个客户端生成唯一标识,防止误删他人锁
基本命令格式:
bash
SET lock_key unique_value NX PX 30000
工作流程图:

基础实现代码示例:
java
public class SimpleRedisLock {
private Jedis jedis;
private String lockKey;
private String clientId;
public SimpleRedisLock(Jedis jedis, String lockKey) {
this.jedis = jedis;
this.lockKey = lockKey;
this.clientId = UUID.randomUUID().toString();
}
/**
* 尝试获取锁
* @param expireTime 锁过期时间(毫秒)
* @return 是否获取成功
*/
public boolean tryLock(long expireTime) {
String result = jedis.set(lockKey, clientId, "NX", "PX", expireTime);
return "OK".equals(result);
}
}
1.2 解锁的原子性问题与Lua脚本
解锁操作不是简单的DEL命令,需要确保只有锁的持有者才能释放锁。非原子性的解锁操作可能导致严重问题。
非原子解锁的问题场景:

解决方案:使用Lua脚本保证原子性
java
public boolean unlock() {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
" return redis.call('del', KEYS[1]) " +
"else " +
" return 0 " +
"end";
Long result = (Long) jedis.eval(luaScript,
Collections.singletonList(lockKey),
Collections.singletonList(clientId));
return result == 1;
}
原子解锁的优势:
-
操作原子性:读取、比较、删除在一个原子操作中完成
-
安全性:只有锁持有者才能释放锁
-
简单性:无需复杂的业务逻辑判断
1.3 存在的问题与改进
基础实现虽然简单,但存在一些明显问题:
主要问题:
-
锁续期困难:业务执行时间超过锁过期时间时,无法自动续期
-
非可重入:同一线程无法多次获取同一把锁
-
锁粒度单一:缺乏读写分离等高级特性
-
单点故障:单个Redis实例故障时锁不可用
改进方向:
-
实现锁续期机制:添加后台线程定期续期
-
支持可重入:记录锁持有者信息和重入次数
-
实现多种锁类型:读写锁、公平锁等
-
多实例支持:使用红锁算法提高可用性
2. Redisson分布式锁的高级实现
2.1 Redisson概述
Redisson是一个在Redis基础上实现的Java驻内存数据网格框架,提供了丰富的分布式Java对象和服务。其分布式锁实现是企业级应用中的首选方案。
Redisson的主要特性:
-
完整的分布式锁实现(可重入锁、读写锁、公平锁等)
-
内置看门狗机制,自动续期避免死锁
-
支持多种部署模式(单机、集群、哨兵等)
-
提供丰富的分布式数据结构
Redisson分布式锁架构:

2.2 RLock接口与可重入锁
Redisson的分布式锁实现了Java标准库中的Lock接口,并扩展为RLock接口,提供分布式环境下的可重入锁功能。
RLock接口主要方法:
-
lock():阻塞式获取锁 -
lockInterruptibly():可中断的锁获取 -
tryLock():尝试获取锁,立即返回结果 -
tryLock(long waitTime, long leaseTime, TimeUnit unit):带超时的锁获取 -
unlock():释放锁 -
forceUnlock():强制释放锁
可重入锁的实现原理:
Redisson使用Redis的Hash结构存储锁信息,支持同一线程多次获取同一把锁。
锁存储结构:
java
键名: my_lock
Hash结构:
字段: 客户端ID:线程ID
值: 重入次数
示例代码:
java
// 获取锁实例
RLock lock = redissonClient.getLock("myLock");
try {
// 获取锁(可重入)
lock.lock();
// 同一线程可以再次获取锁
lock.lock();
// 业务逻辑
doBusiness();
} finally {
// 需要释放两次
lock.unlock();
lock.unlock();
}
2.3 看门狗机制(续期与自动释放)
看门狗机制是Redisson分布式锁的核心特性,解决了锁过期时间设置的难题。
工作机制:
-
客户端获取锁时,如果没有指定leaseTime(租约时间),Redisson会启动看门狗线程
-
看门狗线程每10秒检查一次客户端是否还持有锁
-
如果客户端仍持有锁,则将锁的过期时间重置为30秒
-
当锁被释放或客户端断开连接时,看门狗线程停止

2.4 公平锁、读写锁、联锁、红锁等
1. 公平锁(FairLock):
公平锁确保所有等待获取锁的线程按照请求顺序获得锁,避免线程饥饿现象。
Redisson的公平锁通过Redis的List和Sorted Set数据结构实现请求排队机制,多个数据结构协同工作:
-
等待队列(List):存储等待锁的线程请求ID
-
超时集合(Sorted Set):存储每个请求的超时时间
-
锁状态(Hash):存储当前锁的持有者信息
-
发布订阅频道:用于通知等待线程锁已释放

优势:
-
避免线程饥饿,所有请求都有机会获得锁
-
按照请求顺序分配资源,更加公平
-
适用于资源分配、任务调度等场景
劣势:
-
实现复杂,性能开销较大
-
需要维护额外的队列数据结构
-
相比非公平锁,吞吐量可能降低
2. 读写锁(ReadWriteLock):
读写锁允许多个读操作同时进行,但写操作是独占的。这种锁在读多写少的场景下能显著提高系统并发性能。

读写锁的实现原理
Redisson读写锁在Redis中使用不同的键来管理读锁和写锁:
-
读锁计数器:使用Redis的String结构记录当前持有读锁的线程数
-
写锁标记:使用Redis的String结构记录写锁持有者信息
-
互斥机制:读锁和写锁之间通过检查对方的状态实现互斥
3. 联锁(MultiLock):
联锁允许将多个独立的锁组合成一个锁,要求同时获取所有锁,要么全部获取成功,要么全部失败。适用于需要原子性操作多个资源的场景。

4. 红锁(RedLock):
红锁算法是基于多个独立Redis实例的分布式锁算法,旨在解决单点故障问题,提高锁的可用性。

RedLock算法的关键条件:
-
实例独立性:每个Redis实例应该是独立部署的,避免单点故障
-
大多数原则:需要从大多数实例(N/2 + 1)获取锁成功
-
时钟同步:需要考虑各实例间的时钟差异
-
网络延迟:获取锁的总时间需要考虑网络延迟
2.5 各种锁的对比
| 锁类型 | 适用场景 | 并发性能 | 实现复杂度 | 容错能力 | 公平性 |
|---|---|---|---|---|---|
| 普通可重入锁 | 通用场景,单个资源互斥访问 | 中等 | 低 | 低 | 非公平 |
| 公平锁 | 需要按序访问的资源分配 | 较低 | 高 | 低 | 公平 |
| 读写锁 | 读多写少的共享资源访问 | 高 | 中等 | 低 | 可配置 |
| 联锁 | 需要原子操作多个资源 | 较低 | 高 | 低 | 非公平 |
| 红锁 | 高可用性要求的核心业务 | 低 | 很高 | 高 | 非公平 |
3. 看门狗机制(Watchdog)深度解析
3.1 为什么需要看门狗?
在分布式锁的使用中,锁过期时间的设置是一个难题:
-
设置过短:业务未完成,锁已过期,其他线程可能获取锁导致并发问题
-
设置过长:客户端崩溃后,锁长时间无法释放,影响系统可用性
-
难以预估:不同业务执行时间不同,难以统一设置合适的过期时间
看门狗机制通过自动续期解决了这个问题,让开发者无需关心锁过期时间。
3.2 看门狗的工作原理
看门狗机制的核心是后台定时任务,定期检查并续期锁的过期时间。
工作流程详细说明:

关键配置参数:
-
lockWatchdogTimeout:看门狗超时时间,默认30秒 -
续期间隔:
lockWatchdogTimeout / 3,默认10秒
3.3 源码分析
Redisson看门狗机制的核心代码位于RedissonLock类中:
1. 锁获取时启动看门狗:
java
private void lock(long leaseTime, TimeUnit unit, boolean interruptibly) throws InterruptedException {
long threadId = Thread.currentThread().getId();
// 尝试获取锁
Long ttl = tryAcquire(leaseTime, unit, threadId);
if (ttl == null) {
// 获取锁成功
return;
}
// 如果leaseTime <= 0,启动看门狗
if (leaseTime <= 0) {
scheduleExpirationRenewal(threadId);
}
}
2. 调度锁续期任务:
java
private void scheduleExpirationRenewal(long threadId) {
ExpirationEntry entry = new ExpirationEntry();
ExpirationEntry oldEntry = EXPIRATION_RENEWAL_MAP.putIfAbsent(
getEntryName(), entry);
if (oldEntry != null) {
oldEntry.addThreadId(threadId);
} else {
entry.addThreadId(threadId);
// 启动续期任务
renewExpiration();
}
}
3. 续期任务实现:
java
private void renewExpiration() {
ExpirationEntry ee = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ee == null) {
return;
}
// 创建定时任务
Timeout task = commandExecutor.getConnectionManager()
.newTimeout(new TimerTask() {
@Override
public void run(Timeout timeout) throws Exception {
ExpirationEntry ent = EXPIRATION_RENEWAL_MAP.get(getEntryName());
if (ent == null) {
return;
}
Long threadId = ent.getFirstThreadId();
if (threadId == null) {
return;
}
// 执行续期操作
RFuture<Boolean> future = renewExpirationAsync(threadId);
future.onComplete((res, e) -> {
if (e != null) {
log.error("Can't update lock expiration", e);
return;
}
if (res) {
// 递归调用,继续下一次续期
renewExpiration();
}
});
}
},
internalLockLeaseTime / 3, TimeUnit.MILLISECONDS); // 10秒后执行
ee.setTimeout(task);
}
4. 续期Lua脚本:
Lua
-- KEYS[1]: 锁的key
-- ARGV[1]: 新的过期时间
-- ARGV[2]: 客户端标识(线程ID)
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('pexpire', KEYS[1], ARGV[1]);
return 1;
end;
return 0;
4. 总结
Redis分布式锁是分布式系统中实现资源互斥访问的关键技术。从基础的SET命令实现到Redisson的高级特性,分布式锁的解决方案不断演进和完善。
Redisson分布式锁知识体系

核心要点总结:
-
基础实现:SET NX PX + Lua脚本保证原子性,简单但功能有限
-
Redisson优势:提供看门狗机制、可重入锁、多种锁类型等高级特性
-
看门狗机制:自动续期解决锁过期时间设置难题,提高系统可靠性
-
锁类型选择:根据业务场景选择合适的锁类型(普通锁、读写锁、公平锁、红锁等)
-
最佳实践:合理控制锁粒度、设置超时时间、完善异常处理、建立监控体系