在分布式架构盛行的今天,多服务实例并发访问共享资源的场景愈发普遍,单机环境下的synchronized、ReentrantLock等锁机制已完全失效------它们仅能控制单个JVM进程内的线程同步,无法跨进程、跨服务器协调资源访问。Redis分布式锁作为解决该问题的核心方案,而Redisson则是基于Redis实现的Java驻内存数据网格,其封装的分布式锁不仅解决了原生Redis锁的诸多缺陷,还提供了可重入、自动续期、公平锁等强大特性,是生产环境中分布式锁的首选实现方式。
一、原生Redis锁的痛点与Redisson的优势
1. 原生Redis锁的4大核心痛点
原生Redis锁通常通过SETNX命令实现加锁,配合EXPIRE命令设置过期时间,但这种方式存在诸多隐患,无法满足生产级需求:
-
非原子性:SETNX和EXPIRE是两条独立命令,若加锁后、设置过期时间前发生服务器宕机或网络中断,锁将无法过期,导致死锁,永久占用资源;
-
不可重入:同一线程无法再次获取自己已持有的锁,若业务逻辑存在锁嵌套,会导致自我死锁;
-
锁误删:线程A执行超时,锁自动释放后,线程B成功获取锁;此时线程A执行完毕,会误删除线程B持有的锁,导致临界区暴露,引发数据不一致;
-
非公平锁:所有等待锁的客户端同时争抢,可能出现某些客户端长期无法获取锁的饥饿问题,且无有效的重试机制。
尽管后续可通过SET NX PX命令(单条命令原子完成加锁+设置过期)解决非原子性问题,但仍无法解决可重入、误删除、公平性等核心痛点。而Redisson则通过Lua脚本、哈希结构、看门狗机制等,系统性解决了所有问题。
2. Redisson分布式锁的核心优势
Redisson并非单纯的Redis客户端,而是基于Redis封装的分布式工具集,其提供的RLock(分布式可重入锁)是核心组件,相比原生Redis锁,具备以下不可替代的优势:
-
原子性保障:通过Lua脚本将"检查锁状态、加锁、设置过期、重入计数"等操作合并为一次原子执行,杜绝中间故障窗口;
-
支持可重入:采用Redis哈希结构记录锁持有者与重入计数,同一线程可多次获取锁,计数累加,释放时计数递减直至0才真正释放锁;
-
自动续期(看门狗机制):针对长耗时业务,自动启动后台看门狗线程,定期延长锁的过期时间,避免业务未执行完锁已过期;
-
安全释放:解锁时会校验锁的持有者,仅允许持有锁的线程释放,避免误删其他线程的锁;
-
丰富特性支持:支持公平锁、非公平锁、读写锁、联锁、红锁等多种锁类型,适配不同业务场景;
-
高性能:基于Netty实现非阻塞I/O,支持异步加解锁,且通过脚本缓存(EVALSHA)减少传输与编译开销,提升执行效率;
-
框架无缝集成:与Spring Boot、Spring Cloud等主流框架完美兼容,配置简单,开发成本低。
二、Redisson分布式锁核心原理
Redisson分布式锁的核心是RLock接口及其实现类RedissonLock,底层依赖Redis的原子操作、Lua脚本、发布订阅机制和看门狗线程,核心流程分为"加锁、续期、解锁"三大环节。
1. 核心设计基础
Redisson锁的底层设计依赖两个核心要素,确保锁的安全性与可靠性:
-
锁的存储结构:采用Redis哈希(Hash)结构存储锁信息,Key为锁的全局唯一标识(如"lock:order:1001"),Field为客户端唯一标识(UUID+线程ID),Value为锁的重入计数;
-
客户端唯一标识:由"UUID+线程ID"组成,用于区分不同服务实例、不同线程的锁持有者,避免锁误删和不可重入问题;
-
Lua脚本:Redisson的加锁、解锁逻辑均通过Lua脚本实现,利用Redis的单线程特性,保证多个操作的原子性,避免并发竞态问题。
2. 加锁流程(核心重点)
Redisson加锁的核心入口是RLock的lock()或tryLock()方法,完整流程结合了原子加锁、自旋重试、看门狗启动三大步骤,流程图如下:

核心细节拆解:
(1)加锁核心Lua脚本
加锁逻辑通过Lua脚本原子执行,避免多命令间的故障窗口,核心脚本逻辑如下:
lua
-- KEYS[1]:锁的全局唯一Key(如lock:order:1001)
-- ARGV[1]:锁的过期时间(默认30000ms,即30秒)
-- ARGV[2]:客户端唯一标识(UUID:threadId)
-- 1. 判断锁是否存在
if (redis.call('exists', KEYS[1]) == 0) then
-- 2. 锁不存在,创建Hash结构,设置持有者和重入计数1
redis.call('hset', KEYS[1], ARGV[2], 1)
-- 3. 设置锁的过期时间
redis.call('pexpire', KEYS[1], ARGV[1])
return nil -- 加锁成功,返回nil
end
-- 4. 锁存在,判断持有者是否为当前客户端
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
-- 5. 是当前客户端,重入计数+1,刷新过期时间
redis.call('hincrby', KEYS[1], ARGV[2], 1)
redis.call('pexpire', KEYS[1], ARGV[1])
return nil -- 重入加锁成功,返回nil
end
-- 6. 锁存在且持有者非当前客户端,返回锁剩余TTL(毫秒)
return redis.call('pttl', KEYS[1])
脚本返回值说明:nil表示加锁/重入成功;非nil表示加锁失败,返回锁的剩余存活时间,用于指导客户端自旋重试的间隔。
(2)自旋重试机制
当加锁失败(锁被其他客户端持有)时,Redisson不会立即返回失败,而是进入自旋等待状态:
-
客户端根据Lua脚本返回的锁剩余TTL,进行"精准睡眠",避免无意义的忙等,减少Redis压力;
-
睡眠结束后,重新执行加锁脚本,直至加锁成功或超过等待时间(tryLock()方法可设置等待时间);
-
同时,客户端会订阅该锁对应的Redis发布订阅频道,当锁被释放时,会收到通知,立即唤醒自旋线程,重新尝试加锁,提升效率。
(3)看门狗自动续期机制(WatchDog)
看门狗是Redisson解决"业务执行时间超过锁过期时间"的核心机制,仅在未显式设置锁过期时间(leaseTime)时启用:
-
默认配置:看门狗线程每隔10秒(lockWatchdogTimeout/3)检查一次锁状态,若锁仍被当前客户端持有,则将锁的过期时间刷新为30秒(默认lockWatchdogTimeout值);
-
启动时机:加锁成功后,若未设置leaseTime,Redisson会自动启动看门狗线程;
-
终止时机:客户端释放锁(unlock())或客户端进程崩溃,看门狗线程自动终止,锁会在过期时间后自动释放,避免死锁。
注意:若显式设置了leaseTime(如lock(10, TimeUnit.SECONDS)),则看门狗机制不会启用,锁会在leaseTime到期后自动释放,需确保业务执行时间不超过leaseTime。
3. 解锁流程
Redisson解锁的核心是"校验持有者+重入计数递减+释放锁",同样通过Lua脚本原子执行,避免锁误删,完整流程如下:
-
客户端调用unlock()方法,触发解锁Lua脚本;
-
脚本校验当前锁的持有者是否为当前客户端(通过ARGV[2]匹配Hash结构的Field);
-
若持有者不匹配,返回nil(解锁失败,避免误删);
-
若持有者匹配,将重入计数递减1;
-
若递减后计数>0,说明存在锁重入,刷新锁的过期时间,返回1(解锁成功,未彻底释放锁);
-
若递减后计数=0,说明无锁重入,删除锁的Hash结构,同时通过Redis发布订阅机制,发布锁释放消息,唤醒等待锁的客户端,返回2(彻底释放锁);
-
解锁成功后,终止看门狗线程。
解锁核心Lua脚本(简化版):
lua
-- KEYS[1]:锁的Key,KEYS[2]:锁对应的发布订阅频道
-- ARGV[1]:解锁消息,ARGV[2]:客户端唯一标识,ARGV[3]:锁的过期时间
-- 1. 校验锁持有者是否为当前客户端
if (redis.call('hexists', KEYS[1], ARGV[2]) == 0) then
return nil -- 持有者不匹配,解锁失败
end
-- 2. 重入计数递减1
local counter = redis.call('hincrby', KEYS[1], ARGV[2], -1)
-- 3. 计数>0,刷新过期时间
if (counter > 0) then
redis.call('pexpire', KEYS[1], ARGV[3])
return 1 -- 解锁成功,未彻底释放
else
-- 4. 计数=0,删除锁,发布释放消息
redis.call('del', KEYS[1])
redis.call('publish', KEYS[2], ARGV[1])
return 2 -- 彻底释放锁
end
三、Spring Boot 整合 Redisson 实战
1. 环境准备
(1)引入依赖
在pom.xml中引入Redisson Spring Boot Starter依赖(版本建议与Spring Boot版本匹配,此处以Spring Boot 2.7.x为例):
xml
-- 引入Redisson Spring Boot Starter
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.5</version>
</dependency>
-- 若需自定义配置,可引入核心依赖(可选)
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.23.5</version>
</dependency>
(2)配置Redisson
在application.yml中配置Redis连接信息与Redisson核心参数,支持单机、哨兵、集群三种模式,以下是最常用的单机和集群配置示例:
① 单机模式
yaml
spring:
redis:
host: 127.0.0.1
port: 6379
password: 123456 # 无密码可省略
database: 0
# Redisson配置
redisson:
config: |
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: 123456
database: 0
# 连接池配置
connectionPoolSize: 16 # 连接池最大容量
connectionMinimumIdleSize: 8 # 连接池最小空闲连接数
# 看门狗超时时间(毫秒),默认30000ms
lockWatchdogTimeout: 30000
② 集群模式(生产环境)
yaml
spring:
redis:
password: 123456
redisson:
config: |
clusterServersConfig:
# 集群节点地址,多个节点用逗号分隔
nodeAddresses:
- "redis://192.168.1.101:6379"
- "redis://192.168.1.102:6379"
- "redis://192.168.1.103:6379"
password: 123456
connectionPoolSize: 32
connectionMinimumIdleSize: 16
lockWatchdogTimeout: 30000
# 集群扫描间隔(毫秒)
scanInterval: 2000
2. 核心代码实现
Redisson的核心使用方式是通过RedissonClient获取RLock实例,调用lock()/tryLock()加锁,unlock()解锁,结合try-finally确保锁必释放。
(1)分布式锁工具类
封装工具类,统一处理加锁、解锁逻辑,避免重复编码,同时增加异常处理,确保锁释放的安全性:
java
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class RedissonLockUtil {
@Resource
private RedissonClient redissonClient;
/**
* 加锁(无等待时间,默认30秒过期,自动续期)
* @param lockKey 锁的唯一标识
* @return RLock实例
*/
public RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
/**
* 加锁(自定义过期时间,不自动续期)
* @param lockKey 锁的唯一标识
* @param leaseTime 过期时间
* @param unit 时间单位
* @return RLock实例
*/
public RLock lock(String lockKey, long leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, unit);
return lock;
}
/**
* 尝试加锁(有等待时间,自定义过期时间)
* @param lockKey 锁的唯一标识
* @param waitTime 最大等待时间(超时则加锁失败)
* @param leaseTime 过期时间
* @param unit 时间单位
* @return true:加锁成功,false:加锁失败
* @throws InterruptedException 中断异常
*/
public boolean tryLock(String lockKey, long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException {
RLock lock = redissonClient.getLock(lockKey);
return lock.tryLock(waitTime, leaseTime, unit);
}
/**
* 解锁(安全解锁,避免误删)
* @param lockKey 锁的唯一标识
*/
public void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
// 仅释放当前线程持有的锁
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
/**
* 解锁(通过RLock实例解锁)
* @param lock RLock实例
*/
public void unlock(RLock lock) {
if (lock != null && lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
(2)业务场景落地
以电商秒杀场景为例,利用Redisson分布式锁防止商品超卖,核心逻辑是"加锁→扣减库存→解锁",结合try-finally确保锁必释放:
java
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Service
public class SeckillService {
@Resource
private RedissonLockUtil redissonLockUtil;
@Resource
private ProductMapper productMapper; // 商品库存DAO
/**
* 秒杀扣减库存
* @param productId 商品ID
* @param userId 用户ID
* @return 秒杀结果
*/
public String seckill(Long productId, Long userId) {
// 1. 定义锁的唯一标识(按业务维度命名,确保全局唯一)
String lockKey = "lock:seckill:product:" + productId;
// 2. 尝试加锁:最大等待1秒,成功后30秒过期(根据业务调整)
try {
boolean lockSuccess = redissonLockUtil.tryLock(lockKey, 1, 30, TimeUnit.SECONDS);
if (!lockSuccess) {
// 加锁失败,返回秒杀失败
return "秒杀过于火爆,请重试!";
}
// 3. 加锁成功,执行业务逻辑(扣减库存)
Product product = productMapper.selectById(productId);
if (product == null || product.getStock() <= 0) {
return "商品已售罄!";
}
// 扣减库存(更新数据库)
product.setStock(product.getStock() - 1);
productMapper.updateById(product);
// 后续可添加订单创建等逻辑
return "秒杀成功!";
} catch (InterruptedException e) {
// 处理中断异常
Thread.currentThread().interrupt();
return "秒杀失败,请重试!";
} finally {
// 4. 无论业务成功与否,都要释放锁
redissonLockUtil.unlock(lockKey);
}
}
}
3. 关键注意点
-
锁Key命名规范:需保证全局唯一,建议采用"业务类型:模块:唯一标识"格式(如lock:seckill:product:1001),避免不同业务锁冲突;
-
解锁逻辑:必须在finally块中释放锁,且释放前需校验"锁是否被当前线程持有",避免误删其他线程的锁;
-
参数设置:根据业务耗时合理设置waitTime和leaseTime,waitTime不宜过长(避免线程阻塞过久),leaseTime需大于业务最大执行时间(未启用看门狗时);
-
异常处理:加锁、解锁过程中可能出现网络异常,需捕获异常并处理,避免锁泄露。
四、Redisson分布式锁最佳实践
结合生产环境常见问题,总结Redisson分布式锁的最佳实践,涵盖锁选型、参数优化、集群适配、异常处理等核心场景,避免踩坑。
1. 锁类型选型(按需选择)
Redisson提供多种锁类型,不同场景选择合适的锁,避免过度使用可重入锁,提升性能:
-
可重入锁(RLock):默认锁类型,适用于大多数场景,支持重入、自动续期,如库存扣减、订单创建;
-
公平锁(RFairLock):按客户端请求顺序获取锁,避免饥饿问题,适用于对公平性要求高的场景(如任务调度),但性能略低于非公平锁;
-
读写锁(RReadWriteLock):读锁共享、写锁独占,适用于"读多写少"场景(如商品详情查询、库存查询),提升并发读性能;
-
联锁(RMultiLock):同时获取多个锁,全部获取成功才算加锁成功,适用于需要同时锁定多个资源的场景(如转账时锁定转出账户和转入账户);
-
红锁(RedissonRedLock):基于RedLock算法,在多个独立Redis节点上加锁,需超过半数节点加锁成功才算生效,适用于Redis集群脑裂风险高的场景,但性能开销较大,需谨慎使用。
2. 核心参数优化(关键配置)
合理配置Redisson参数,平衡性能与安全性,以下是生产环境常用优化建议:
-
lockWatchdogTimeout:默认30000ms,建议根据业务最大耗时调整,如业务最长执行时间为10秒,可设置为30000ms(预留足够缓冲);
-
connectionPoolSize/connectionMinimumIdleSize:连接池大小根据并发量调整,单机环境建议16-32,集群环境建议32-64,避免连接池耗尽;
-
tryLock参数:waitTime建议设置为1-3秒(避免线程长期阻塞),leaseTime若未启用看门狗,需设置为业务最大执行时间的1.5-2倍,预留缓冲;
-
脚本缓存:启用Lua脚本缓存(默认启用),通过EVALSHA命令执行脚本,减少脚本传输和编译开销,提升执行效率。
3. 常见问题与解决方案
(1)死锁问题
问题现象:客户端崩溃、业务异常未执行unlock(),导致锁无法释放,其他线程永久等待。
解决方案:
-
必须在finally块中释放锁,且释放前校验锁持有者;
-
启用看门狗机制(未显式设置leaseTime),确保客户端崩溃后,锁能在过期时间后自动释放;
-
定期监控Redis锁键,通过脚本清理长期未释放的异常锁(如超过1小时的锁)。
(2)锁提前失效问题
问题现象:业务执行时间超过leaseTime,且未启用看门狗,导致锁提前释放,多个线程进入临界区,引发数据不一致。
解决方案:
-
未显式设置leaseTime,依赖看门狗自动续期;
-
显式设置leaseTime时,通过压测统计业务最大耗时,设置为最大耗时的1.5-2倍;
-
长耗时业务可手动续期(通过lock.renewExpiration()方法),避免锁提前失效。
(3)Redis集群脑裂问题
问题现象:网络分区导致Redis集群出现多个主节点,不同客户端在不同主节点上加锁,出现"双锁",导致临界区暴露。
解决方案:
-
优化Redis集群配置,减少脑裂概率(如设置min-replicas-to-write=1);
-
高一致性场景使用红锁(RedissonRedLock),在多个独立Redis节点上加锁,需超过半数节点加锁成功才算生效;
-
结合业务幂等性设计,即使出现双锁,也能避免数据不一致(如订单创建时校验订单是否已存在)。
(4)锁竞争激烈导致性能瓶颈
问题现象:高并发场景下,大量线程阻塞等待锁,导致系统吞吐量下降,响应变慢。
解决方案:
-
减小锁粒度:按业务ID拆分锁(如lock:seckill:product:1001、lock:seckill:product:1002),避免全局锁;
-
使用读写锁:读多写少场景,用RReadWriteLock替代RLock,提升并发读性能;
-
异步加锁:使用tryLockAsync()方法,非阻塞获取锁,避免线程阻塞;
-
限流兜底:高并发场景下,先通过接口限流(如Sentinel),减少锁竞争压力。
(5)锁重入次数过多问题
问题现象:同一线程多次重入锁,Redis中锁的Hash结构计数溢出(理论极值为Long.MAX_VALUE),导致锁无法正常释放。
解决方案:
-
重构代码,减少锁重入层次,避免深层嵌套锁;
-
监控锁的重入次数,定期检查异常计数;
-
避免在循环中重复获取锁,优化业务逻辑。
五、总结
Redisson分布式锁的核心价值的是"基于Redis,封装出安全、高效、易用的分布式锁方案",其通过Lua脚本保障原子性、哈希结构支持可重入、看门狗机制解决长耗时业务锁过期问题、发布订阅机制优化自旋重试,完美解决了原生Redis锁的诸多痛点。生产环境中,需结合业务场景(并发量、一致性要求、业务耗时),灵活调整锁的参数和类型,同时配合监控、限流、幂等性设计,确保分布式锁的稳定性和高性能,真正解决分布式环境下的资源互斥问题。