当应用从单节点扩展到多节点部署后,原本依赖JVM内部 synchronized 或 ReentrantLock 就能解决的并发问题会立刻暴露出来。比如两个服务节点同时读取同一条库存记录,各自扣减后再写回,最终库存只扣了一次,这就是典型的数据不一致。要解决这类问题,就必须引入一个所有节点都能看到的全局锁,而Redisson正是实现这种全局锁最流行的Java方案之一。它基于Redis构建,提供了可重入锁、公平锁、读写锁、联锁等多种锁形态,本文重点分析最常用的可重入锁。

一、为什么本地锁在多节点环境下会失效
先理解问题的根源。Java内存模型中,synchronized 和 ReentrantLock 的作用范围是当前JVM进程。假设你的服务部署了三个节点,每个节点内部各自有一把锁,节点A拿到自己的锁,节点B也拿到自己的锁,两者互不感知,临界区代码实际上是被并发执行的。
很多人会想到用数据库的行锁或者乐观锁来兜底,这种方案可行但性能受限于数据库的承载能力,高并发场景下容易把数据库打垮。而Redis基于内存操作,单次加锁、解锁通常在毫秒级以内完成,配合合理的过期时间,性能和可靠性都能满足绝大多数业务需求。Redisson则是在Redis原生命令之上封装了一层完善的API,屏蔽了底层细节,同时解决了原生实现中的几个大坑。
二、Redisson分布式锁的核心原理
Redisson加锁的核心是Redis的Hash结构加上Lua脚本的组合。锁在Redis中以一个Hash存储,key是锁名称,field是客户端唯一标识加线程ID,value记录重入次数。加锁和设置过期时间必须原子完成,因此整个操作被封装在一段Lua脚本中由Redis单线程执行,避免了先set再expire两步操作之间进程崩溃导致的死锁。
下面是一个基础的加锁解锁示例:
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("order:lock:1001");
try {
// 不传leaseTime,触发看门狗自动续期
lock.lock();
try {
// 临界区:扣减库存、更新订单状态等业务逻辑
doBusinessLogic();
} finally {
lock.unlock();
}
} finally {
redisson.shutdown();
}解锁时同样使用Lua脚本完成两件事:检查锁的持有者是否是当前线程,是则把重入次数减一;减到零时删除key并发布解锁消息,唤醒其他等待的客户端。整个过程原子执行,不会误删别人的锁。
三、看门狗机制与可重入特性详解
看门狗是Redisson最容易被误解的部分。当调用lock方法时不传leaseTime参数,锁的初始过期时间被设置为30秒,同时Redisson会启动一个定时任务,每隔三分之一过期时间也就是10秒检查一次,如果业务线程还持有锁,就把过期时间重新刷回30秒。这样即使业务执行了40秒,锁也不会提前失效。
需要注意两点:第一,一旦你在lock时显式传入了leaseTime,看门狗就不会生效,到期后锁自动释放,此时必须自行评估业务最长执行时间;第二,看门狗的续期依赖客户端进程存活,如果进程被强杀,续期任务也随之消失,锁会在30秒后自动过期释放,这正是过期时间存在的意义,防止死锁。
可重入特性则依赖Hash结构的value计数。同一线程第二次加锁时,Lua脚本发现field已存在,只把计数加一而不重复创建key,解锁时计数减一,减到零才真正删除锁。这和JDK中ReentrantLock的语义保持一致,写嵌套调用代码时不用担心自己把自己锁死。
四、常见踩坑点与最佳实践
实际项目中最常见的错误是加锁后解锁操作没有放在finally块中,业务抛异常导致锁一直持有到过期,期间其他节点全部阻塞。解锁前还应调用isHeldByCurrentThread判断当前线程是否持有锁,避免释放他人锁时抛出IllegalMonitorStateException异常。
第二个坑是锁粒度设计不当。有人为了省事给整个服务只配一把全局锁,结果所有并发请求串行化,吞吐量急剧下降。正确做法是按业务维度细分锁名称,例如按订单ID加锁,不同订单之间互不影响。此外,生产环境建议使用Redis哨兵或Cluster模式并配置好Master下行时的处理策略,理解Redisson默认不保证强一致,RedLock算法在社区存在争议,多数场景下单实例加锁配合合理的过期时间已经足够。
最后一个建议是结合tryLock做快速失败。高并发秒杀场景下,与其让大量请求排队等待,不如设置一个较短的等待时间,拿不到锁直接返回稍后重试,保护系统不被线程堆积拖垮。
RLock lock = redisson.getLock("stock:lock:" + skuId);
boolean acquired = false;
try {
// 最多等待2秒,拿到锁后看门狗自动续期
acquired = lock.tryLock(2, TimeUnit.SECONDS);
if (!acquired) {
return Result.busy();
}
deductStock(skuId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (acquired && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}掌握Redisson分布式锁,关键在于理解Lua脚本保证原子性、看门狗自动续期、Hash结构实现可重入这三个核心机制。把原理吃透之后,再结合业务场景选择合适的锁粒度和等待策略,就能在多节点环境下稳定地保证数据一致性。
Redisson分布式锁数据一致性Redis修改时间:2026-09-08 12:50:42