深入理解 Redisson 分布式锁

引言

在分布式系统中,多个服务实例往往需要同时访问同一共享资源,例如:

  • 多节点同时修改同一条库存记录
  • 并发执行同一订单处理流程
  • 多实例定时任务同时触发
  • 并发写入同一个数据对象

如果没有分布式锁机制做互斥控制,系统容易出现数据竞争、写冲突、脏写、重复执行等问题。

Redis 因为高性能、单线程执行模型、丰富的原子命令和 Lua 脚本支持,成为当前最主流的分布式锁实现底座。

而 Redisson 作为 Redis 官方推荐的 Java 客户端之一,在锁语义、可重入性、自动续期、原子性保障等方面具有较高的工程完备度。

本文将从工作原理到工程实践全面介绍 Redisson 分布式可重入锁(RLock)。


一、为什么选择 Redisson 实现分布式锁?

如果直接手写 Redis 分布式锁(例如使用 SETNX + EXPIRE),会面临大量工程级问题:

问题 原因 典型风险
SETNX 与 EXPIRE 非原子 两条命令 获取锁后未设置过期时间 → 死锁
锁删除不安全 业务线程崩溃 / 误删 删除了别的线程的锁
锁超时后自动释放 EX 过期时间不足 业务未执行完 → 锁提前过期导致数据错误
不支持可重入 SETNX 无法表达线程所有者 同一线程再次加锁会造成自阻塞
缺乏自动续期机制 自己实现困难 业务执行长任务容易导致锁失效

Redisson 提供的 RLock 特性完整解决了上述问题:

  • 可重入语义
  • 自动续期(看门狗机制)
  • 原子加锁 / 解锁(Lua 脚本)
  • 多种锁模式(公平锁、联锁、红锁等)
  • 分布式环境稳定可靠

因此 Redisson 是 Java 体系中工程落地最完善的 Redis 分布式锁方案。


二、Redisson 分布式锁底层原理

1. 锁在 Redis 中的结构

Redisson 使用 Redis 的 Hash 结构保存锁元数据:

复制代码
Key: myLock
Type: Hash
Field: <uuid:threadId>
Value: <reentrant count>
Expire: 30 seconds (默认锁自动过期时间)

示例:

复制代码
HSET myLock "e8c0d-...-12:23" 1
EXPIRE myLock 30

这带来几个重要特性:

  • 支持可重入:同一线程再次加锁只需增加次数,不会阻塞
  • 线程级别锁所有权:clientId+threadId 作为唯一标识
  • Redis TTL 控制过期:防止死锁
  • Hash 支持多个 field,可扩展到联锁等高级场景

2. 加锁流程详解

加锁过程使用 Lua 脚本确保原子性:

核心流程(简化逻辑)

  1. 锁不存在 → 设置 Hash(field=当前线程)并设置 TTL
  2. 锁存在但属于当前线程 → 可重入次数+1
  3. 锁存在且不属于当前线程 → 阻塞等待或立即返回失败

所有分支均由 Lua 脚本完成,确保操作原子、不被打断。


三、Spring Boot + Redisson 配置示例

java 复制代码
@Configuration
public class RedissonConfig {

    @Bean
    public RedissonClient redissonClient() {
        Config config = new Config();
        config.useSingleServer()
                .setAddress("redis://127.0.0.1:6379")
                .setDatabase(0);

        return Redisson.create(config);
    }
}

四、加锁与释放锁使用方法

Redisson 提供两类常用加锁方式:

  • lock.lock():阻塞式加锁,自动启动看门狗续期
  • lock.tryLock(...):可设定等待时间与租期,更灵活

1. 使用 lock() ------ 自动续期(Watchdog)

java 复制代码
@Service
public class LockExampleService {

    @Autowired
    private RedissonClient redissonClient;

    public void testLock() {

        RLock lock = redissonClient.getLock("order:lock");

        try {
            lock.lock();  // 不指定时间 → 自动续期机制生效
            System.out.println("获取到锁,执行操作...");

            Thread.sleep(60000);  // 业务耗时,看门狗续期保障锁有效

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

特点:

  • 默认锁过期时间:30 秒
  • 自动续期(看门狗)每 10 秒刷新过期时间

2. 使用 tryLock()

java 复制代码
public void tryLockExample() {

    RLock lock = redissonClient.getLock("product:lock");

    try {
        if (lock.tryLock(5, TimeUnit.SECONDS)) {
            System.out.println("成功获取锁");
            Thread.sleep(30000);
        } else {
            System.out.println("未获取到锁");
        }

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (lock.isHeldByCurrentThread()) {
            lock.unlock();
        }
    }
}

特点:

  • 5 秒内阻塞等待
  • 拿到锁后:启用看门狗自动续期

五、看门狗(Watchdog)机制完整解析

1. 看门狗的工作机制

默认:

  • 检查周期:10 秒
  • 每次续期:将锁 TTL 重置为 30 秒

工作流程:

  1. 定时任务触发
  2. 判断锁是否仍被当前线程持有
  3. 若是 → 调用 PEXPIRE myLock 30000
  4. 若线程结束或锁已释放 → 停止续期

2. 为什么需要续期?

如果业务耗时超过锁 TTL(30s),传统分布式锁会提前失效,导致并发问题。

续期确保业务执行再长,也不会中途释放锁。

3. 如何判断"锁属于当前线程"?

通过检查:

复制代码
HEXISTS myLock <clientId:threadId>

只要当前线程还是 lock 的 owner,就继续续期。


六、Redis 中锁的实时具象表现

加锁后执行:

复制代码
HGETALL order:lock
TTL order:lock

可能看到:

复制代码
1) "7ab1e9...:23"
2) "1"

(integer) 30

随着看门狗执行,TTL 会保持波动但始终在 30 秒附近。


七、锁释放原理(Lua 脚本)

Redisson 使用 Lua 保证解锁的原子性。

  1. 检查锁是否属于当前线程
  2. 若重入次数 > 1 → 次数 -1
  3. 若重入次数为 1 → 删除整个 key
  4. 若锁不属于当前线程 → 不执行任何操作(避免误删)

这个机制保证:"只有锁持有者才能释放锁"。


八、典型问题与常见误区(FAQ)

1. 调用 lock.lock(10, TimeUnit.SECONDS) 会启用看门狗吗?

不会。

指定租期时不启用看门狗,锁达到指定时间后自动释放。


2. 看门狗续期时间可以调整吗?

可以:

java 复制代码
config.setLockWatchdogTimeout(45000); // 默认30s

3. Redisson 如何避免死锁?

  • 线程异常退出 → 看门狗停止续期
  • 默认 30s TTL 自动过期
  • 锁结构中包含线程标识,避免误删

4. 可重入锁意味着什么?

同一个线程可以多次获取该锁,不会进入自阻塞:

复制代码
lock.lock();
lock.lock(); // 重入,计数+1

5. 可否用于分布式事务?

可以用于分布式资源互斥,但不能代替事务。


十、完整业务案例:库存扣减(典型高并发场景)

java 复制代码
@Service
public class StockService {

    @Autowired
    private RedissonClient redissonClient;

    public void deductStock() {

        RLock lock = redissonClient.getLock("stock:lock");

        try {
            lock.lock();  // 触发看门狗续期
            System.out.println("开始扣减库存...");

            // 模拟扣减操作
            Thread.sleep(30000);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }
}

适用于:

  • 秒杀减库存
  • 订单去重
  • 并发任务幂等控制
  • 分布式调度系统

总结:Redisson 是 Java 开发中最成熟的 Redis 分布式锁实现

功能点 Redisson 是否支持 说明
可重入分布式锁 Hash 结构记录重入次数
自动续期(看门狗) lock() 自动启用
原子加锁与解锁 Lua 脚本保障原子性
死锁自动恢复 TTL 到期自动释放
公平锁、联锁、读写锁 丰富的分布式同步结构
高性能、可伸缩 基于 Redis 单线程执行模型

凭借其完善的锁语义、自动续期机制与 Lua 原子操作设计,Redisson 是 Java 生态中分布式锁的首选方案,适用于中大型业务中的强一致性场景。

相关推荐
摇滚侠2 小时前
Redis 零基础到进阶,类型概述,命令查询,key 操作命令,大小写和帮助命令,string,list,hash,set,笔记11-19
redis·笔记·哈希算法
嘻哈baby2 小时前
Redis突然变慢,排查发现是BigKey惹的祸
数据库·redis·缓存
Kiddyup2 小时前
记一次Redis连接泄露问题排查
redis·排查问题
小满、3 小时前
Redis:高级数据结构与进阶特性(Bitmaps、HyperLogLog、GEO、Pub/Sub、Stream、Lua、Module)
java·数据结构·数据库·redis·redis 高级特性
xiangzhihong83 小时前
Windows环境下安装使用Redis
数据库·windows·redis
梁bk3 小时前
苍穹外卖项目总结(一)[MyBatis-Plus,文件上传,Redis]
数据库·redis·mybatis
CNRio3 小时前
Redis:内存中的数据引擎,架构解析与设计指南
数据库·redis·架构
en-route3 小时前
Redis 作为消息队列的三种使用方式与 Spring Boot 实践
数据库·spring boot·redis
Mr.Pascal12 小时前
Redis:主动更新,读时更新,定时任务。三种的优劣势对比
数据库·redis·缓存