redission原理

解析Redisson核心原理与设计思想

Redisson 是 Redis 官方推荐的 Java 分布式协调组件,它将 Redis 的丰富数据结构扩展为 Java 熟悉的并发对象(如分布式锁、Map、队列、信号量),并提供了完整的集群、哨兵、主从支持。本文将深入剖析 Redisson 的架构基石、分布式锁的完整链路、看门狗机制、异步编程模型以及如何通过 Lua 保证原子性,以"好看懂、能落地"的方式展示其原理。

放一张核心加锁逻辑总图


1. 架构全景:异步 · 原子 · 高可用

Redisson 的底层通信完全基于 Netty ,所有 Redis 命令执行都是异步非阻塞的,通过 RFuture 抽象将回调转换为 Java 的 CompletionStage 或直接的异步接口。这种设计让它天然支持高吞吐。

整体架构可概括为三个核心支柱:

  • 异步引擎:Netty + 线程池 + 连接池,命令执行零阻塞,支持发布/订阅事件流。
  • 原子脚本 :所有复合操作(如加锁、续期、解锁)均通过 Lua 脚本 在 Redis 服务端一次性执行,杜绝并发竞争。
  • 对象映射 :将 Redis 的 String、Hash、List、ZSet 等数据结构,封装为 Java 友好的 RMapRLockRQueue 等接口,提供本地缓存、监听回调等增强。

设计哲学:尽量将逻辑移到 Redis 内部执行(Lua),客户端只负责发起指令和监听结果,避免分布式共识的复杂开销。


2. 分布式锁的完整解剖

RLock 是 Redisson 中最广泛使用的特性,其实现远非简单的 SETNX 可比,它要解决可重入、自动续期、非阻塞等待、集群安全四大难题。

2.1 加锁:一个 Lua 脚本定乾坤

加锁的核心是一段精心设计的 Lua 脚本(简化版如下):

lua 复制代码
-- KEYS[1] 锁名 , ARGV[1] 客户端标识(UUID:线程ID) , ARGV[2] 锁租约时间(ms)
if (redis.call('exists', KEYS[1]) == 0) then
    redis.call('hincrby', KEYS[1], ARGV[1], 1);
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return nil;
end;
if (redis.call('hexists', KEYS[1], ARGV[1]) == 1) then
    redis.call('hincrby', KEYS[1], ARGV[1], 1);
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return nil;
end;
return redis.call('pttl', KEYS[1]);

原理拆解:

  • 锁在 Redis 中存储为一个 Hash ,键为锁名,字段为 客户端UUID:线程ID,值为重入次数。
  • 首次加锁:Hash 不存在,直接创建并设置租约时间(默认30秒)。
  • 重入加锁:判断当前客户端是否已持有锁(hexists),是则重入次数+1并刷新租约。
  • 如果锁被其他客户端持有,返回剩余过期时间(pttl),客户端据此进行等待。

这种方式天然支持了可重入强一致性------由 Redis 单线程串行化执行脚本。

2.2 看门狗:锁的自动续命

如果业务执行时间超过锁的租约(30秒),锁会被自动释放,造成并发问题。Redisson 提供了经典的 Watch Dog 机制:

  • 加锁成功后,客户端会启动一个 后台定时任务 (基于 Netty 的 HashedWheelTimer 时间轮),每隔 租约/3 (默认 10 秒)触发一次续期。

  • 续期执行一条简单的 Lua 脚本:

    lua 复制代码
    if (redis.call('hexists', KEYS[1], ARGV[1]) == 1) then
        redis.call('pexpire', KEYS[1], ARGV[2]);
        return 1;
    end;
    return 0;

    该脚本判断锁是否仍被同一客户端持有,是则将过期时间重置为租约值。

  • 当锁被主动释放或客户端宕机,续期任务会因锁不存在而自动失效,不会无谓续期。

注意 :如果指定了 tryLock(waitTime, leaseTime, unit) 中的 leaseTime,则不会启动看门狗,锁会在到达时间后自动释放,适合短任务。

2.3 解锁与等待唤醒:Pub/Sub 而非轮询

解锁同样使用 Lua 脚本保证原子性:

lua 复制代码
if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then return nil; end;
local count = redis.call('hincrby', KEYS[1], ARGV[1], -1);
if (count > 0) then
    redis.call('pexpire', KEYS[1], ARGV[2]);
    return 0;
else
    redis.call('del', KEYS[1]);
    redis.call('publish', KEYS[2], ARGV[3]);  -- 通知等待线程
    return 1;
end;

当锁的 Hash 被删除时,Redis 会向频道 redisson_lock__channel:{锁名} 发送 解锁消息 ,正在 lock() 上阻塞的其他线程会订阅该频道,收到消息后立即竞争锁。这比自旋轮询节省大量网络开销,保障了公平性与低延迟。

2.4 RedLock:应对集群分区的增强

在哨兵或集群模式下,Redisson 实现了 RedLock 算法:它会在所有独立的 master 节点上依次尝试获取锁,当在大多数节点(N/2+1)上成功且总耗时不超预算时才算获取成功。这防止了主从切换时的锁丢失,但也以增加复杂度和延迟为代价,适用于极高安全需求的场景。


相关推荐
小旭95271 小时前
Spring Cloud 集成分布式日志 ELK+Swagger 接口文档实战
java·分布式·后端·elk·spring cloud
属鼠哥1 小时前
HDFS 短路读取:mmap 与 Unix Domain Socket 铸就的零拷贝艺术
后端
好好风格1 小时前
Scrapling:现代 Web 抓取,正在从“写选择器”走向“自适应”
linux·后端
屋外雨大,惊蛰出没1 小时前
spring boot+mybatis开发基础复习
java·spring boot·后端
这个DBA有点耶1 小时前
死锁排查进阶:从日志到根因的完整分析链
java·开发语言·数据库·sql·运维开发·学习方法·dba
叫我少年1 小时前
C# 文件级 using(global using)
后端
郝学胜_神的一滴1 小时前
系统设计 014:缓存深度实战:如何用 Cache 优雅优化数据库读写?
前端·后端·面试
ai程序羊沸沸1 小时前
Spring Cloud 微服务入门:从组件清单到问题驱动的学习路径
后端·微服务
JAVA9651 小时前
JAVA面试-并发篇 06-ReentrantLock如何实现公平锁的以及可重入吗
java·开发语言·面试