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)上成功且总耗时不超预算时才算获取成功。这防止了主从切换时的锁丢失,但也以增加复杂度和延迟为代价,适用于极高安全需求的场景。


相关推荐
神奇小汤圆2 小时前
接口响应慢到崩溃?CompletableFuture 并行编排让效率提升 3 倍
后端
程序员cxuan2 小时前
GPT-5.6 还不发布?不过大家可以先看看 Codex 的白皮书。
人工智能·后端·程序员
白鲸开源2 小时前
Apache SeaTunnel Zeta Engine 的 Basic Auth 是怎么工作的?
java·vue.js·github
妙码生花2 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(八):设计管理员模型、热重载配置
前端·后端·go
白鲸开源2 小时前
一文读懂DolphinScheduler插件机制:如何轻松扩展任务类型与数据源
java·架构·github
ServBay2 小时前
拒绝当二等公民,Windows 开发者如何无痛开启 Claude Code 本地全栈运维?
后端·ai编程·mcp
用户34232323763172 小时前
从数据源到仪表盘——全链路端到端实战整合
后端
Apifox3 小时前
从 Postman 迁移到 Apifox:Workspace、Collection、Environment 现在可以一起导入了
前端·后端·程序员
用户7713970207064 小时前
深入解析 C# Path.ChangeExtension:原来改扩展名可以这么简单
后端