Redisson 的分布式锁机制&幽默笑话理解

Redisson 是一个在 Redis 基础上实现的 Java 驻内存数据网格(In-Memory Data Grid),它提供了一系列的分布式数据结构和服务,其中分布式锁是其最核心、最强大的功能之一。

与我们手动使用 SETNX 等命令实现的简单分布式锁相比,Redisson 提供的锁机制更加健壮、易用,并且解决了许多分布式锁的经典难题。


1. 为什么需要分布式锁?

在单体应用中,我们可以使用 synchronizedReentrantLock 来保证多线程对共享资源的互斥访问。但在分布式系统中,应用部署在多个服务器上,每个服务器都有自己的 JVM,本地锁无法跨进程、跨服务器生效。此时,就需要一个所有应用实例都能访问到的 "第三方" 来协调锁的获取与释放,这个 "第三方" 通常就是 Redis。

2. Redisson 锁的核心优势

Redisson 实现的分布式锁不仅仅是简单地在 Redis 中设置一个 key,它解决了手动实现锁时常见的几个痛点:

  • 可重入性 (Reentrant):同一个线程可以多次获取同一把锁,而不会造成死锁。
  • 自动续期 (Watch Dog):解决了 "锁过期但业务未执行完" 的问题。如果持有锁的线程在锁过期前还没执行完,Redisson 会自动为锁续期。
  • 阻塞与非阻塞获取 :支持 lock() (阻塞等待) 和 tryLock() (尝试获取,立即返回) 等多种获取锁的方式。
  • 公平锁 (Fair Lock):可以保证多个线程按申请锁的顺序来获取锁,避免 "饥饿" 现象。
  • 多种锁类型:除了可重入锁,还提供读写锁、信号量、闭锁等多种高级锁。

3. Redisson 锁的实现原理详解

我们以最常用的可重入锁 (RLock) 为例,深入其内部工作机制。

3.1 获取锁 (lock())

当一个线程尝试获取锁时,Redisson 内部会执行一段 Lua 脚本。使用 Lua 脚本的好处是可以保证操作的原子性。

核心逻辑:

  1. 尝试获取锁
    • 它会使用 Redis 的 HSET 命令,以锁的名称(例如 "myLock")为 key,创建一个 Hash 数据结构。
    • Hash 的 field 是当前线程的唯一标识(例如 threadId:uuid),value 是该线程获取锁的次数(重入次数)。
    • 同时,它会使用 PEXPIRE 命令为这个 key 设置一个过期时间(默认 30 秒),防止死锁。
  2. 判断结果
    • 如果 HSET 命令返回 1 :表示这是第一次获取锁,并且设置成功。此时,线程成功获得锁,并启动一个 Watch Dog 定时任务
    • 如果 HSET 命令返回 0 :表示锁已存在,并且当前线程就是持有锁的线程。此时,它会将该线程的重入次数加 1 (HINCRBY),并重置锁的过期时间
    • 如果锁已存在,但不是当前线程持有:表示锁被其他线程占用。此时,当前线程获取锁失败。它会订阅一个与该锁相关的 Redis Channel,然后阻塞等待,直到收到锁被释放的消息,再重新尝试获取锁。
3.2 Watch Dog (看门狗) 机制

这是 Redisson 最精妙的设计之一,用于解决 "锁过期但业务未执行完" 的问题。

  • 启动:当一个线程成功获取锁(非重入)时,会启动一个后台定时任务(即 Watch Dog)。
  • 作用:这个任务会每隔一段时间(默认是锁过期时间的 1/3,即 10 秒)检查一下,如果当前线程仍然持有锁,就会自动将锁的过期时间重置为 30 秒。
  • 停止 :当线程执行完业务逻辑,调用 unlock() 方法释放锁时,这个 Watch Dog 定时任务会被取消。
  • 效果:只要持有锁的线程没有宕机,并且业务还在正常执行,Watch Dog 就会一直为它 "续命",保证锁不会在业务完成前过期。
3.3 释放锁 (unlock())

当线程执行完业务逻辑后,会调用 unlock() 方法释放锁。

核心逻辑:

  1. 执行 Lua 脚本
    • 脚本首先判断当前线程是否还是锁的持有者。
    • 如果是,就将该线程的重入次数减 1 (HINCRBY ... -1)。
    • 减 1 后,如果重入次数大于 0,说明线程还在重入状态中,脚本会重置锁的过期时间并返回。
    • 减 1 后,如果重入次数等于 0,说明线程完全释放了锁。脚本会执行 DEL 命令删除锁的 key,并向与该锁相关的 Channel 发布一个消息,通知其他等待的线程锁已释放。
  2. 后续操作
    • 当其他因获取锁失败而阻塞的线程收到这个消息后,会立即被唤醒,并再次尝试去获取锁。
    • 成功释放锁后,当前线程的 Watch Dog 定时任务也会被停止。

4. 代码示例

使用 Redisson 锁非常简单,它的 API 设计得与 Java 原生的 ReentrantLock 非常相似。

java

运行

复制代码
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

public class RedissonLockExample {

    public static void main(String[] args) {
        // 1. 创建 Redisson 客户端
        Config config = new Config();
        config.useSingleServer().setAddress("redis://127.0.0.1:6379");
        RedissonClient redisson = Redisson.create(config);

        // 2. 获取锁实例
        RLock lock = redisson.getLock("myDistributedLock");

        try {
            // 3. 获取锁
            // lock.lock(); // 阻塞式获取,默认 30 秒过期
            // 或者使用 tryLock,尝试获取锁,最多等待 100 秒,上锁后 30 秒自动解锁
            boolean isLocked = lock.tryLock(100, 30, java.util.concurrent.TimeUnit.SECONDS);

            if (isLocked) {
                // 4. 执行临界区业务逻辑
                System.out.println("线程 " + Thread.currentThread().getId() + " 成功获取锁,开始执行业务...");
                Thread.sleep(5000); // 模拟业务耗时
                System.out.println("线程 " + Thread.currentThread().getId() + " 业务执行完毕。");
            } else {
                System.out.println("线程 " + Thread.currentThread().getId() + " 获取锁失败。");
            }

        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            // 5. 确保锁被释放
            // 注意:一定要在 finally 块中释放锁!
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
                System.out.println("线程 " + Thread.currentThread().getId() + " 释放锁。");
            }
        }

        // 关闭客户端
        redisson.shutdown();
    }
}

总结

Redisson 的分布式锁机制通过结合 Lua 脚本 (保证原子性)、Hash 数据结构 (实现可重入)、Watch Dog 定时任务 (自动续期)和 Redis 发布 / 订阅(高效通知)等多种技术,构建了一个功能强大、高可用且易于使用的分布式锁解决方案。它极大地简化了分布式系统中并发控制的复杂性,是 Java 开发者在构建分布式应用时的首选工具之一。


幽默记忆

记 Redisson 锁?别死啃概念了!咱们把它脑补成 "公司公共厕所抢位大战",保准你笑完就忘不了 ------

1. 先搞懂:为啥要搞 "分布式锁"?

以前公司就 1 个办公室(单体应用),厕所门后挂个 "有人" 牌(synchronized 锁),谁先拿到谁用,没毛病。现在公司扩成 10 层楼(分布式系统,多服务器),每层都有厕所,但大家要抢 "全公司唯一的 VIP 坑位"(共享资源,比如改库存)------ 总不能 1 楼的人锁了 1 楼厕所,2 楼的人还冲进去吧?这时候就得找个 "全公司都认的裁判"(Redis),而 Redisson 就是裁判手里的 "智能叫号机",专门管谁能进坑、能蹲多久。

2. Redisson 这 "叫号机" 牛在哪?(核心优势)

它可不是普通叫号机,是带 "人性化服务" 的:

  • 可重入:自己人不用重新排队比如你进了坑,突然发现没带纸,开门去拿纸(同一线程再次请求锁)------ 裁判不会让你重新排,直接说 "知道是你,进去吧",还在小本本上记 "这人第 2 次进坑"(Hash 结构存线程标识 + 重入次数)。
  • **自动续期:怕你蹲太久被 "赶"**裁判怕你蹲坑时突然接了个长电话(业务没执行完),特意派了个 "看门大爷"(Watch Dog)------ 每隔 10 秒(默认过期时间 30 秒的 1/3)就扒门缝问:"兄弟还在不?在的话给你续 30 秒坑位时间啊!" 只要你没走,坑位就一直是你的。
  • 可堵可撤:不想等就走,想等就排想蹲坑时,你可以选 "死等"(lock ()):站在厕所门口等别人出来;也可以选 "问一句就走"(tryLock ()):问裁判 "现在有空吗?100 秒内没空我就走了",不耽误事。
  • 公平锁:谁先来谁先上,不允许加塞要是公司有人总爱插队(非公平锁导致线程饥饿),裁判就开 "公平模式"------ 按排队顺序叫号,哪怕 CEO 来了,也得排在实习生后面。

3. 它咋干活的?(实现原理,还是厕所梗)

① 抢坑(获取锁):裁判的 "一条龙操作"

裁判手里有个小本本(Redis 的 Hash 键),你冲过去要坑位时,他会:

  1. 查本本:"'VIP 坑位'这栏有人吗?"
  2. 没人?直接写 "占坑人:张三(线程 ID),次数:1",再贴个 "30 分钟后过期" 的条(PEXPIRE),然后喊 "张三进!",顺便让看门大爷盯着你(启动 Watch Dog)。
  3. 有人?再看是不是你:是你就把 "次数" 改成 2,续 30 分钟;不是你?就让你站旁边等,还让你关注 "坑位释放群"(Redis Channel),别人一出来就通知你。(全程用 Lua 脚本写在小本本上,保证没人中途改答案 ------ 总不能裁判查一半,有人抢着改本本吧?)
② 蹲坑(持有锁):大爷的 "巡逻任务"

只要你在坑里,看门大爷就每隔 10 秒来续期,直到你出来。要是你突然晕倒在坑里(线程宕机),大爷喊了几声没反应,就不续了 ------30 秒后过期条一到,直接把你 "抬" 出来,给下个人用(避免死锁)。

③ 出坑(释放锁):裁判的 "收尾工作"

你提裤子出来时,裁判会:

  1. 查本本:"张三,你这次是第 2 次进坑吧?"(确认是锁的持有者)
  2. 把次数改成 1:"哦?还有 1 次?那你是不是还要进?"(重入次数没到 0,续期继续等)
  3. 次数到 0 了:直接划掉你的名字(DEL 删除 Hash 键),然后在 "坑位释放群" 里喊 "VIP 坑位空了!下一个来!"(发布订阅通知等待线程),顺便让大爷下班(停止 Watch Dog)。

最后记个 "口诀",保证不忘:

厕所抢位靠 Redis,Redisson 来当机;重入像回家拿纸,续期靠大爷巡逻;抢坑要么死等,要么问完就走;出坑划掉名字,群里喊下一个!

怎么样?下次想到 "蹲坑",就想起 Redisson 了吧~

相关推荐
海兰1 小时前
【高速缓存】RedisVL 高级查询(全文搜索、混合搜索和 多向量搜索)
数据库·人工智能·redis·缓存
记录者t3 小时前
redis常见问题:大key
数据库·redis
青石路4 小时前
如果写Redis序列化与读Redis序列化不一致,你觉得会发生什么
java·redis
難釋懷4 小时前
Nginx代理https请求
redis·nginx·https
流星白龙14 小时前
【Redis】2.Redis重大版本
数据库·redis·junit
流星白龙14 小时前
【Redis】7.Hash表
数据库·redis·哈希算法
流星白龙15 小时前
【Redis】4.基本全局命令
数据库·redis·缓存
流星白龙18 小时前
【Redis】3.Redis安装与命令行客户端
数据库·redis·缓存
Season4501 天前
Redis命令 (generic即通用命令)
数据库·redis·bootstrap