Redis分布式锁详细实现演进与Redisson深度解析
一、Redis分布式锁演进历程
1. 基础版:SETNX + EXPIRE(已废弃,存在严重问题)
第1步:尝试获取锁,返回1表示成功,0表示锁已被占用
bash
SETNX lock:order 1
第2步:如果上一步成功,设置锁的过期时间为30秒
EXPIRE lock:order 30
问题分析:
非原子性:两个命令分开执行,如果在SETNX后客户端崩溃,EXPIRE不会执行,锁永远不会释放,导致死锁
无持有者标识:任何客户端都能释放锁,可能导致误删
无法区分不同客户端:不知道锁被谁持有
2. 推荐版:原子SET(当前主流基础方案)
bash
# 单命令原子操作:获取锁 + 设置过期时间 + 设置持有者标识
SET lock:order client_001 NX PX 30000
代码详解:
lock:order:锁的key,标识业务类型
client_001:锁的value,通常是客户端唯一标识(如UUID+线程ID)
NX:仅当key不存在时才设置(Not eXists)
PX 30000:设置过期时间为30000毫秒(30秒)
优势:
原子性:单个命令,要么全部成功要么全部失败
避免死锁:自动过期机制保证锁最终会释放
持有者标识:value可用于验证锁的持有者
但仍有问题:
锁续期问题:业务执行时间超过30秒时,锁会提前释放
误释放风险:需要额外逻辑保证只有持有者能释放锁
二、Redisson生产级实现深度解析
1. Redisson入门使用
java
// 初始化Redisson客户端
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
// 获取锁对象(非阻塞)
RLock lock = redisson.getLock("order_lock");
// 尝试加锁(支持等待时间)
boolean isLocked = lock.tryLock(10, 30, TimeUnit.SECONDS);
if (isLocked) {
try {
// 业务逻辑
processOrder();
} finally {
// 必须确保释放锁
lock.unlock();
}
}
2. Redisson核心特性详解
特性1:可重入锁(Reentrant Lock)
java
// 获取可重入锁
RLock lock = redisson.getLock("anyLock");
// 第一次加锁
lock.lock(10, TimeUnit.SECONDS);
// 同一个线程内可以再次加锁(重入)
lock.lock(5, TimeUnit.SECONDS);
// ... 执行一些操作
// 需要解锁两次
lock.unlock();
lock.unlock();
实现原理:
使用Redis的Hash结构存储锁信息:key: lock_name, field: client_uuid:thread_id, value: 重入次数。
每次重入时,将重入次数加1。
释放锁时,重入次数减1,直到为0才真正删除锁。
特性2:公平锁(Fair Lock)
java
// 获取公平锁
RLock fairLock = redisson.getFairLock("fairLock");
// 加锁
fairLock.lock();
try {
// 公平锁保证按照请求顺序获取锁
// 先请求的客户端先获取锁
} finally {
fairLock.unlock();
}
实现原理:
客户端请求锁时,在Redis中创建一个顺序节点(sorted set)。
按照节点顺序获取锁,先创建的节点先获取。
使用发布订阅机制通知下一个等待的客户端。
代价:性能比普通锁低,因为需要维护顺序和通知机制。
特性3:联锁(MultiLock)
java
// 创建多个锁对象
RLock lock1 = redisson.getLock("lock1");
RLock lock2 = redisson.getLock("lock2");
RLock lock3 = redisson.getLock("lock3");
// 创建联锁,将多个锁关联在一起
RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3);
// 获取联锁:必须所有锁都成功获取才算成功
multiLock.lock();
try {
// 需要同时锁定多个资源时使用
// 比如:转账需要同时锁定A账户和B账户
transfer(accountA, accountB, amount);
} finally {
multiLock.unlock();
}
使用场景:
分布式事务:需要同时锁定多个资源。
跨资源操作:如同时修改用户表和订单表。
避免死锁:按固定顺序获取多个锁(Redisson内部处理)。
特性4:红锁(RedLock)
java
// 创建多个独立的Redisson客户端(连接到不同Redis实例)
RedissonClient client1 = createClient("redis://node1:6379");
RedissonClient client2 = createClient("redis://node2:6379");
RedissonClient client3 = createClient("redis://node3:6379");
RedissonClient client4 = createClient("redis://node4:6379");
RedissonClient client5 = createClient("redis://node5:6379");
// 从每个客户端获取锁对象
RLock lock1 = client1.getLock("lock");
RLock lock2 = client2.getLock("lock");
RLock lock3 = client3.getLock("lock");
RLock lock4 = client4.getLock("lock");
RLock lock5 = client5.getLock("lock");
// 创建红锁
RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3, lock4, lock5);
// 尝试获取红锁
boolean isLocked = redLock.tryLock(1000, 30000, TimeUnit.MILLISECONDS);
if (isLocked) {
try {
// 业务逻辑
} finally {
redLock.unlock();
}
}
红锁算法原理:
1、获取当前时间(毫秒级精度)
2、依次尝试从N个独立的Redis实例获取锁
使用相同的key和value
设置较短的超时时间(远小于锁自动释放时间)
3、计算获取锁的总耗时
当前时间减去步骤1的时间
4、检查是否获取成功
成功条件:从大多数(N/2 + 1)个实例获取锁成功,且总耗时小于锁有效时间
5、如果获取失败,则在所有实例上释放锁
红锁争议:
优点:提高可用性,单个Redis实例故障不影响锁服务
缺点:性能下降,实现复杂,仍有极端情况下的锁安全问题
建议:仅在需要极高可用性且接受复杂性的场景使用
3. Redisson内部实现机制
看门狗(WatchDog)机制
java
// Redisson加锁时自动启动看门狗线程
lock.lock(); // 不指定leaseTime,启用看门狗
// 指定leaseTime,不启用看门狗
lock.lock(10, TimeUnit.SECONDS); // 10秒后自动释放,无看门狗
看门狗工作流程:
1、加锁成功后,启动一个后台线程(看门狗)
2、每隔锁过期时间/3(默认10秒)检查一次
3、如果业务还在执行,则重置锁的过期时间(默认30秒)
4、业务完成后,停止看门狗线程
Lua脚本保证原子性
java
// Redisson释放锁的Lua脚本
String script =
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
" return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
" redis.call('pexpire', KEYS[1], ARGV[2]); " +
" return 0; " +
"else " +
" redis.call('del', KEYS[1]); " +
" redis.call('publish', KEYS[2], ARGV[1]); " +
" return 1; " +
"end; " +
"return nil;";
脚本解释:
KEYS[1]:锁的key
KEYS[2]:发布订阅的channel
ARGV[1]:解锁消息
ARGV[2]:锁的过期时间
ARGV[3]:客户端标识(UUID:threadId)
功能:验证持有者 → 重入计数减1 → 判断是否完全释放 → 删除锁并通知等待者
4. 生产环境最佳实践
java
@Component
public class OrderService {
@Autowired
private RedissonClient redisson;
public void processOrder(String orderId) {
String lockKey = "order:lock:" + orderId;
RLock lock = redisson.getLock(lockKey);
// 尝试获取锁,最多等待5秒,锁持有时间10秒
try {
boolean acquired = lock.tryLock(5, 10, TimeUnit.SECONDS);
if (!acquired) {
throw new BusinessException("系统繁忙,请稍后重试");
}
// 获取锁成功,执行业务逻辑
handleOrder(orderId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("处理被中断");
} finally {
// 只有当前线程持有锁时才释放
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
// 异步处理,避免长时间占用锁
@Async
public CompletableFuture<Void> asyncProcessOrder(String orderId) {
return CompletableFuture.runAsync(() -> processOrder(orderId));
}
}
- 性能调优与监控
yaml
Redisson配置示例
redisson:
single-server-config:
address: "redis://localhost:6379"
connection-pool-size: 100 # 连接池大小
connection-minimum-idle-size: 10 # 最小空闲连接数
idle-connection-timeout: 10000 # 空闲连接超时时间
connect-timeout: 10000 # 连接超时时间
timeout: 3000 # 操作超时时间
retry-attempts: 3 # 重试次数
retry-interval: 1500 # 重试间隔
lock:
watchdog-timeout: 30000 # 看门狗超时时间
lock-watch-timeout: 10000 # 锁监控超时
6. 常见问题与解决方案

总结
Redis分布式锁从基础到生产级的演进,体现了对可靠性、性能、可用性的不断追求:
1、基础版:简单但危险,仅适用于学习和测试
2、原子SET版:满足基本需求,但需自行处理续期和释放
3、Redisson版:生产级方案,提供可重入、公平锁、联锁、红锁等高级特性,内置看门狗和原子性保证
选择建议:
1、大部分场景:使用Redisson普通锁(可重入锁)
2、需要严格顺序:公平锁
3、多资源原子操作:联锁
4、极高可用性需求:红锁(但需权衡复杂性)
黄金法则:永远在finally块中释放锁,并验证当前线程是否持有锁。