Redisson 源码深度分析

Redisson 本质是 "Redis 分布式对象的 Java 封装层",通过异步通信 + Lua 原子性 + 连接池管理 + 分布式一致性机制,将 Redis 原生命令抽象为符合 Java 习惯的分布式对象(Map/Lock/Queue 等),同时解决了分布式场景下的锁续期、过期清理、节点路由、并发等待等核心问题,兼顾高性能与易用性。

目录

  1. 核心架构总览
  2. [Netty 异步通信层](#Netty 异步通信层)
  3. [RedissonMap 分布式 Map 实现](#RedissonMap 分布式 Map 实现)
  4. [RedissonLock 分布式锁](#RedissonLock 分布式锁)
  5. [RedissonList / Deque](#RedissonList / Deque)
  6. [RedissonQueue / BlockingQueue / DelayedQueue](#RedissonQueue / BlockingQueue / DelayedQueue)
  7. [RedissonSet / SortedSet / SetCache](#RedissonSet / SortedSet / SetCache)
  8. [PubSub 消息机制](#PubSub 消息机制)
  9. [Lua 脚本执行引擎](#Lua 脚本执行引擎)
  10. 连接池管理
  11. 集群支持
  12. [响应式/异步 API(RFuture)](#响应式/异步 API(RFuture))
  13. 配置系统
  14. 速率限制器(RRateLimiter)
  15. 架构总结与设计哲学

1. 核心架构总览

1.1 入口类 Redisson

Redisson 的整体架构围绕三个核心组件构建:Redisson 类、RedissonClient 接口、ConnectionManager

Redisson 类是 RedissonClient 的唯一实现,也是用户与 Redis 交互的入口:

java 复制代码
public final class Redisson implements RedissonClient {
    private final EvictionScheduler evictionScheduler;
    private final WriteBehindService writeBehindService;
    private final ConnectionManager connectionManager;
    private final CommandAsyncExecutor commandExecutor;
    private final Config config;
}

五个核心字段各司其职:

字段 职责
connectionManager 管理所有 Redis 连接的创建、销毁、路由
commandExecutor 封装 Netty 通信,统一发送 Redis 命令/Lua 脚本
evictionScheduler 定时清理过期数据(如 SetCache 过期条目)
writeBehindService Map 异步批量写入服务,提升写入吞吐
config 客户端配置副本(深拷贝,不影响原始配置)

1.2 构造流程

java 复制代码
// Redisson.create(config) 内部流程
Redisson(ClientEndpointsCache endpointsCache, Config config) {
    Config configCopy = new Config(config);                    // 深拷贝
    connectionManager = ConnectionManager.create(configCopy);  // 创建连接管理器
    commandExecutor = connectionManager.createCommandExecutor(...);
    evictionScheduler = new EvictionScheduler(commandExecutor);
    writeBehindService = WriteBehindService.create(commandExecutor);
    connectionManager.getServiceManager().register(
        new LockRenewalScheduler(commandExecutor));            // 注册锁续期调度器
}

1.3 ConnectionManager 工厂模式

ConnectionManager 是连接管理的核心,通过静态工厂方法根据配置类型返回不同实现:

java 复制代码
static ConnectionManager create(Config configCopy) {
    BaseConfig<?> cfg = ConfigSupport.getConfig(configCopy);
    if (cfg instanceof MasterSlaveServersConfig) {
        cm = new MasterSlaveConnectionManager((MasterSlaveServersConfig) cfg, configCopy);
    } else if (cfg instanceof SingleServerConfig) {
        cm = new SingleConnectionManager((SingleServerConfig) cfg, configCopy);
    } else if (cfg instanceof SentinelServersConfig) {
        cm = new SentinelConnectionManager((SentinelServersConfig) cfg, configCopy);
    } else if (cfg instanceof ClusterServersConfig) {
        cm = new ClusterConnectionManager((ClusterServersConfig) cfg, configCopy);
    } else if (cfg instanceof ReplicatedServersConfig) {
        cm = new ReplicatedConnectionManager((ReplicatedServersConfig) cfg, configCopy);
    }
    if (!configCopy.isLazyInitialization()) {
        cm.connect();
    }
}

关键设计 :单机模式(SingleConnectionManager)内部被转换为"主从模式"处理------master 只有一个节点,简化了路由逻辑。

ConnectionManager 的关键职责:

方法 作用
calcSlot(key) 计算 key 的 CRC16 槽位(0~16383)
getEntry(name) 根据名称获取对应的连接池入口(MasterSlaveEntry)
getWriteEntry(slot) 获取写操作的目标节点(Master)
getReadEntry(slot) 获取读操作的目标节点(可路由到 Slave)
createClient(type, address) 创建到特定 Redis 节点的网络连接

1.4 分布式对象创建

所有分布式对象(Map、Lock、Queue 等)的创建都由 Redisson 中的工厂方法统一完成,核心链路:

复制代码
Redisson.getMap("name")
  -> new RedissonMap(commandExecutor, name, codec)
  -> 每个对象持有 commandExecutor 作为唯一通信通道

2. Netty 异步通信层

2.1 CommandAsyncExecutor 接口

CommandAsyncExecutor 是所有 Redis 操作的统一入口,提供同步和异步两套 API:

java 复制代码
interface CommandAsyncExecutor {
    // 同步阻塞获取结果
    <V> V get(RFuture<V> future);
    <V> V getInterrupted(RFuture<V> future) throws InterruptedException;

    // 写操作(强制路由到 Master)
    <T, R> RFuture<R> writeAsync(RedisClient client, Codec codec, RedisCommand<T> cmd, Object... params);
    <T, R> RFuture<R> writeAsync(String key, Codec codec, RedisCommand<T> cmd, Object... params);

    // 读操作(主从模式下可路由到 Slave)
    <T, R> RFuture<R> readAsync(String key, Codec codec, RedisCommand<T> cmd, Object... params);

    // Lua 脚本执行
    <T, R> RFuture<R> evalReadAsync(String key, Codec codec, RedisCommand<T> cmdType, String script, KeyspaceKeyspace... keys);
    <T, R> RFuture<R> evalWriteAsync(String key, Codec codec, RedisCommand<T> cmdType, String script, KeyspaceKeyspace... keys);

    // 编解码
    ByteBuf encode(Codec codec, Object value);
    ByteBuf encodeMapKey(Codec codec, Object value);
    ByteBuf encodeMapValue(Codec codec, Object value);
}

2.2 通信流程

复制代码
用户调用 RMap.put(key, value)
  -> RedissonMap.putAsync(key, value)
    -> commandExecutor.evalWriteAsync(name, codec, EVAL_MAP_VALUE, luaScript, ...)
      -> ConnectionManager.getEntry(name)
        -> MasterSlaveEntry.getConnection()  // 从连接池获取 RedisConnection
          -> channel.writeAndFlush(command)  // Netty 发送
            -> ChannelFuture.addListener(callback)  // 异步回调

核心要点

  1. Netty 的 EventLoopGroupServiceManager 统一管理,使用 NioEventLoopGroupEpollEventLoopGroup(Linux 原生)
  2. 定时任务使用 HashedWheelTimer(精确定时器)
  3. 通信管道是同步执行的------单个 Netty Channel 上的命令按顺序发送和返回,避免命令交错
  4. 编解码由 Codec 接口抽象,支持 JacksonCodecKryo5CodecCborJacksonCodecForyCodec 等多种序列化方案

2.3 编解码器(Codec)

每个分布式对象持有自己的 Codec 实例,负责数据序列化/反序列化:

java 复制代码
public interface Codec {
    KeyCodec getKeyCodec();
    ValueCodec getValueCodec();
}

默认 codec 为 Kryo5Codec(高性能序列化框架)。用户也可自定义:

java 复制代码
Config config = new Config();
config.setCodec(new JacksonCodec());

3. RedissonMap 分布式 Map 实现

3.1 类继承关系

复制代码
RedissonObject (基类, 持有 name + codec)
  -> RedissonExpirable (TTL/过期支持)
    -> RedissonMap (底层 Redis Hash)
      -> RedissonMapCache (支持逐 key TTL)
      -> RedissonSmartMapCache (高级缓存功能)

3.2 基础操作

底层使用 Redis Hash 数据结构:

java 复制代码
// put → HSET
@Override
public RFuture<Void> putAsync(K key, V value) {
    return commandExecutor.writeAsync(name, codec, RedisCommands.HSET, name,
        encodeMapKey(key), encodeMapValue(value));
}

// get → HGET
@Override
public RFuture<V> getAsync(Object key) {
    return commandExecutor.readAsync(name, codec, RedisCommands.HGET, name,
        encodeMapKey(key));
}

// remove → HDEL
@Override
public RFuture<V> removeAsync(Object key) {
    return commandExecutor.writeAsync(name, codec, RedisCommands.HDEL, name,
        encodeMapKey(key));
}

3.3 原子操作 ------ Lua 脚本

复杂操作使用 Lua 脚本保证原子性:

putIfAbsent(原子性地只在新 key 不存在时写入):

lua 复制代码
-- KEYS[1] = mapName, ARGV[1] = encodeKey, ARGV[2] = encodeValue
if redis.call('hsetnx', KEYS[1], ARGV[1], ARGV[2]) == 1 then
    return nil          -- 新插入,返回 nil
else
    return redis.call('hget', KEYS[1], ARGV[1])  -- 已存在,返回旧值
end

putIfExists(只覆盖已存在的 key):

lua 复制代码
local value = redis.call('hget', KEYS[1], ARGV[1])
if value ~= false then
    redis.call('hset', KEYS[1], ARGV[1], ARGV[2])
    return value
end
return nil

条件 remove(值匹配时才删除):

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

条件 replace

lua 复制代码
if redis.call('hget', KEYS[1], ARGV[1]) == ARGV[2] then
    redis.call('hset', KEYS[1], ARGV[1], ARGV[3])
    return 1
else
    return 0
end

3.4 高级操作 ------ 细粒度锁

compute / computeIfAbsent / computeIfPresent 等涉及"读-改-写"原子性的操作,使用 RLock(key) 对单个 key 加锁:

java 复制代码
public RFuture<V> computeAsync(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    RLock lock = getLock(key);     // 为每个 key 独立加锁
    lock.lock();
    try {
        V oldValue = get(key);
        V newValue = remappingFunction.apply(key, oldValue);
        if (newValue == null) {
            fastRemove(key);
        } else {
            fastPut(key, newValue);
        }
        return CompletableFuture.completedFuture(newValue);
    } finally {
        lock.unlock();
    }
}

设计亮点:不同 key 使用不同的锁,粒度极细,不会阻塞无关 key 的操作。

3.5 批量获取 getAll

使用 HMGET + 自定义 MapGetAllDecoder 一次性获取多个 key:

java 复制代码
protected RFuture<Map<K, V>> getAllOperationAsync(Set<K> keys) {
    List<Object> args = new ArrayList<>();
    args.add(getRawName());
    encodeMapKeys(args, keys);  // 添加所有 key
    RFuture<Map<K, V>> future = commandExecutor.readAsync(getRawName(), codec,
        new RedisCommand<>("HMGET", new MapValueDecoder(new MapGetAllDecoder())), args);
    return future;
}

3.6 写入延迟 WriteBehind

当配置 MapOptions.writeMode = WRITE_BEHIND 时,写入先入内存队列,再由后台线程批量刷入 Redis:

复制代码
用户调用 put()
  -> 写入内存中的 Map(立即返回)
  -> 提交 WriteBehindService 异步队列
    -> WriteBehindService 定时批量 flush -> HMSET

这极大提升了写入吞吐量,代价是短暂的数据一致性风险(flush 前 crash 会丢失数据)。


4. RedissonLock 分布式锁

4.1 类继承关系

复制代码
RedissonObject
  -> RedissonExpirable (TTL 支持)
    -> RedissonBaseLock (抽象基类, 实现 RLock 接口)
      -> RedissonLock (非公平锁)
        -> RedissonFairLock (公平锁, 带等待队列)

4.2 锁的数据结构

Redisson 分布式锁使用 Hash 结构存储:

复制代码
Hash: {lockName}
  field: "{clientId}:{threadId}"     → value: 重入次数
  • key :锁名称(如 "order-lock"
  • field{clientId}:{threadId} ------ 唯一标识锁的拥有者(客户端 UUID + 线程 ID)
  • value:重入次数
  • TTL :通过 PEXPIRE 设置过期时间(毫秒)

示例:

复制代码
127.0.0.1:6379> HGETALL mylock
1) "550e8400-e29b-41d4-a716-446655440000:42"
2) "3"           -- 当前线程重入了 3 次

4.3 加锁核心 Lua 脚本

lua 复制代码
-- RedissonLock.tryLockInnerAsync
-- KEYS[1] = lockKey
-- ARGV[1] = internalLockLeaseTime (毫秒)
-- ARGV[2] = lockFieldName ("clientId:threadId")

if ((redis.call('exists', KEYS[1]) == 0)
    or (redis.call('hexists', KEYS[1], ARGV[2]) == 1))
then
    redis.call('hincrby', KEYS[1], ARGV[2], 1)
    redis.call('pexpire', KEYS[1], ARGV[1])
    return nil              -- nil 表示加锁成功
end
return redis.call('pttl', KEYS[1])  -- 返回剩余毫秒数(锁被占用)

逻辑

  1. 锁不存在 当前线程已持有 → 重入计数 +1,重置过期时间
  2. 否则返回剩余过期时间(客户端据此计算等待时间)

4.4 解锁核心 Lua 脚本

lua 复制代码
-- RedissonLock.unlockInnerAsync
-- KEYS[1] = lockKey, KEYS[2] = channelName, KEYS[3] = unlockLatchName
-- ARGV[1] = UNLOCK_MESSAGE (0L)
-- ARGV[2] = internalLockLeaseTime
-- ARGV[3] = lockFieldName
-- ARGV[4] = PUBLISH_COMMAND ("PUBLISH")
-- ARGV[5] = timeout

local val = redis.call('get', KEYS[3])
if val ~= false then
    return tonumber(val)
end

if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then
    return nil              -- 非持有者,不允许解锁
end

local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1)
if (counter > 0) then
    -- 重入次数未归零,仅减少计数并续期
    redis.call('pexpire', KEYS[1], ARGV[2])
    redis.call('set', KEYS[3], 0, 'px', ARGV[5])
    return 0
else
    -- 重入次数归零,真正释放锁
    redis.call('del', KEYS[1])
    redis.call(ARGV[4], KEYS[2], ARGV[1])  -- 发布解锁消息
    redis.call('set', KEYS[3], 1, 'px', ARGV[5])
    return 1
end

关键点

  • counter > 0 时不删除锁,仅减少计数并重新设置 TTL(支持可重入)
  • counter == 0 时真正释放:删除锁 Hash,向 PubSub 通道广播解锁消息
  • KEYS[3](unlockLatchName)是一个信号机制,用于处理取消订阅后的屏障

4.5 看门狗机制(Watchdog)

这是 Redisson 分布式锁最令人精妙的设计之一。

触发条件 :调用 lock() 时不指定 leaseTime(即 leaseTime <= 0)。

工作流程

复制代码
lock() 调用
  -> 加锁成功,internalLockLeaseTime = 30000ms(看门狗默认超时)
  -> scheduleExpirationRenewal(threadId) 注册续期任务
    -> LockRenewalScheduler 启动后台定时任务
      -> 每 10 秒(= 30s / 3)检查一次
        -> 锁仍被当前客户端持有?
          -> 是 → 重新设置 PEXPIRE 30000
          -> 否 → 不做任何事(锁已过期或被其他线程持有)

核心源码

java 复制代码
// RedissonLock 构造函数
this.internalLockLeaseTime = getServiceManager().getCfg().getLockWatchdogTimeout(); // 30s

// tryAcquire() 中
if (leaseTime <= 0) {
    internalLockLeaseTime = getServiceManager().getCfg().getLockWatchdogTimeout();
    scheduleExpirationRenewal(threadId);
}

为什么是 leaseTime / 3 这是经典的"三倍超时"策略:

  • 业务逻辑执行时间 < 10s
  • 定时任务最多延迟 10s 触发一次
  • 最坏情况:业务执行 10s + 定时任务延迟 10s = 20s,仍小于 30s 过期时间
  • 留出了 10s 的余量

设计优势

  • 客户端正常工作时,锁永不过期
  • 客户端 crash 后,看门狗任务自动消失,锁在 30s 内自动释放(避免死锁)
  • 不需要心跳协议,纯粹靠后台定时任务实现

4.6 等待机制 ------ PubSub 通知

当锁被占用时,客户端不会 busy-wait 空转,而是通过 Redis PubSub 等待通知:

复制代码
线程 A 调用 lock()
  -> 加锁失败(锁被其他线程持有)
  -> 订阅 channel: "redisson_lock__channel:{lockName}"
  -> 获取 RedissonLockEntry(内含 CountDownLatch)
  -> latch.tryAcquire(ttl, MILLISECONDS) 阻塞等待
  -> 收到 UNLOCK_MESSAGE → latch.release() → 线程唤醒
  -> 重新尝试 tryAcquire

LockPubSub 消息处理

java 复制代码
// LockPubSub 继承自 RedisPubSubAdapter
@Override
public void onMessage(String channel, String message) {
    RedissonLockEntry entry = entries.get(channel);
    if (message.equals(UNLOCK_MESSAGE)) {       // "0L"
        value.tryRunListener();
        value.getLatch().release();              // 唤醒一个等待线程
    } else if (message.equals(READ_UNLOCK_MESSAGE)) { // "1L"
        value.tryRunAllListeners();
        value.getLatch().release(
            value.getLatch().getQueueLength());  // 唤醒所有读锁等待者
    }
}

4.7 公平锁(RedissonFairLock)

公平锁在 RedissonLock 基础上增加了等待队列,使用额外的 Redis 数据结构:

数据结构 Key 用途
Hash {lockName} 锁状态(同非公平锁)
List redisson_lock_queue:{name} FIFO 等待队列
ZSET redisson_lock_timeout:{name} 超时集合(score = 超时时间戳)

公平锁加锁逻辑

lua 复制代码
-- 1. 检查当前线程是否是等待队列头部
local queueName = KEYS[2]
local head = redis.call('lindex', queueName, 0)
if head == lockFieldName then
    -- 队首,正常加锁
    redis.call('lpop', queueName)
    redis.call('hincrby', KEYS[1], ARGV[2], 1)
    redis.call('pexpire', KEYS[1], ARGV[1])
    return nil
end

-- 2. 不是队首,加入等待队列
if redis.call('lpush', queueName, lockFieldName) == 1 then
    redis.call('zadd', KEYS[3], ARGV[4], lockFieldName)  -- 加入超时集合
end
return redis.call('pttl', KEYS[1])

公平锁的公平性保证:必须严格按 FIFO 顺序获取锁,先到先服务。

4.8 红锁(RedissonMultiLock)

RedissonMultiLock 管理多个独立的 RLock 实例,将它们作为"一个逻辑锁":

java 复制代码
RLock lock1 = redisson.getLock("lock1");
RLock lock2 = redisson.getLock("lock2");
RLock lock3 = redisson.getLock("lock3");

RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3);
multiLock.lock();  // 必须全部获取成功

获取锁算法

java 复制代码
// 依次尝试获取每个锁
for (int i = 0; i < locks.length; i++) {
    if (!tryAcquire(locks[i])) {
        failed++;
        if (failed > failCount) {  // failCount = locks.length / 2 + 1
            // 失败数超过阈值,释放已获取的锁
            for (int j = 0; j < i; j++) {
                if (locksHeld[j]) { locks[j].unlock(); }
            }
            return false;
        }
    } else {
        locksHeld[i] = true;
    }
}
// 成功获取后,对所有已获取的锁设置统一 leaseTime

失败限制 :默认允许失败 locks.size() / 2 + 1 个锁,提供容错能力。

4.9 读写锁(RedissonReadWriteLock)

java 复制代码
RReadWriteLock rwLock = redisson.getReadWriteLock("rw-lock");

// 读锁(共享锁)
RWLock.ReadLock readLock = rwLock.readLock();
readLock.lock();

// 写锁(排他锁)
RWLock.WriteLock writeLock = rwLock.writeLock();
writeLock.lock();

底层数据结构:

数据结构 用途
Hash {lockName}:w 写锁状态(field = clientId:threadId, value = 重入计数)
List {lockName}:w:queue 写锁等待队列
Hash {lockName}:r:{id} 读锁状态(field = threadId, value = 重入计数)
Set {lockName}:r 读锁集合(记录哪些 clientId 持有读锁)
ZSET {lockName}:r:timeout 读锁超时集合

核心规则

  • 写锁:互斥,独占
  • 读锁:共享,允许多个读锁同时存在
  • 读写互斥:有写锁时不能读,有读锁时不能写
  • 读读允许:多个读锁可同时持有

5. RedissonList / Deque

5.1 操作映射

RedissonList 底层使用 Redis List(内部是 QuickList ------ SDS + 双向链表 + ziplist):

Java 方法 Redis 命令 说明
add(e) RPUSH 从尾部插入
add(index, e) LINSERT + LSET 按索引插入
get(index) LINDEX 按索引获取
remove(o) LREM 移除元素
size() LLEN 获取长度
range(from, to) LRANGE 范围获取
set(index, e) LSET 按索引替换
clear() DEL 删除整个 List
readAll() LRANGE 0 -1 一次性读取全部

5.2 批量操作

java 复制代码
// getAll ------ 使用 LRANGE 批量获取
@Override
public RFuture<List<V>> getAllAsync() {
    return commandExecutor.readAsync(name, codec, RedisCommands.LRANGE, name,
        0, -1);
}

// addAll ------ 使用 RPUSH 批量插入
@Override
public RFuture<Integer> addAllAsync(Collection<V> items) {
    List<Object> params = new ArrayList<>();
    params.add(getRawName());
    for (V item : items) {
        params.add(encode(item));
    }
    return commandExecutor.writeAsync(getRawName(), codec, RedisCommands.RPUSH, params.toArray());
}

6. RedissonQueue / BlockingQueue / DelayedQueue

6.1 RedissonQueue

继承 BaseRedissonList,用 List 模拟队列:

Java 方法 Redis 命令
offer(e) RPUSH
poll() LPOP
peek() LINDEX 0
size() LLEN

6.2 RedissonBlockingQueue

实现 java.util.concurrent.BlockingQueue 接口,核心是 Redis 阻塞命令:

java 复制代码
// take() ------ 无限阻塞弹出
@Override
public RFuture<V> takeAsync() {
    return commandExecutor.writeAsync(getRawName(), codec,
        RedisCommands.BLPOP_VALUE, getRawName(), 0);  // 0 = 无限等待
}

// poll(timeout) ------ 阻塞指定秒数
@Override
public RFuture<V> pollAsync(long timeout, TimeUnit unit) {
    if (timeout < 0) return completedNull();
    return commandExecutor.writeAsync(getRawName(), codec,
        RedisCommands.BLPOP_VALUE, getRawName(), toSeconds(timeout, unit));
}

多队列轮询pollFromAny 利用 BLPOP 的多个 key 参数:

java 复制代码
// 从任意一个队列阻塞弹出
BLPOP key1 key2 key3 0

6.3 RedissonDelayedQueue(延迟队列)

延迟队列是 Redisson 中最有趣的实现之一,使用 ZSET + List 双层结构:

数据结构 Key 用途
ZSET redisson_delay_queue_timeout {encodedValue, score=到期时间戳}
List redisson_delay_queue {randomId, encodedValue} 包装值

添加元素延迟 offer

lua 复制代码
-- KEYS[1] = 原始队列, KEYS[2] = ZSET, KEYS[3] = List, KEYS[4] = PubSub channel
-- ARGV[1] = timeout (到期时间戳), ARGV[2] = encodedValue, ARGV[3] = randomId

local value = struct.pack('Bc0Lc0', string.len(ARGV[2]), ARGV[2], string.len(ARGV[3]), ARGV[3])
redis.call('zadd', KEYS[2], ARGV[1], value)          -- 加入 ZSET,score = 到期时间
redis.call('rpush', KEYS[3], value)                   -- 同时加入 List
local v = redis.call('zrange', KEYS[2], 0, 0)         -- 检查是否成为最早到期
if v[1] == value then
    redis.call('publish', KEYS[4], ARGV[1])           -- 通知调度器
end
return nil

后台调度器 QueueTransferTask

复制代码
QueueTransferTask 定时执行
  -> ZSCAN 扫描 ZSET,查找 score <= now 的元素(已过期的)
  -> 对每个过期元素,放入原始 List 队列
  -> 如果还有更早到期的,继续通知调度器重试

注意 :在较新版本中 RedissonDelayedQueue 被标记为 @Deprecated,推荐使用 RDelayedQueue 新 API。


7. RedissonSet / SortedSet / SetCache

7.1 RedissonSet

底层使用 Redis Set(哈希表实现):

Java 方法 Redis 命令
add(e) SADD
remove(o) SREM
contains(o) SISMEMBER
size() SCARD
readAll() SMEMBERS
intersect(...) SINTER
union(...) SUNION
difference(...) SDIFF

7.2 RedissonSortedSet

底层使用 Redis ZSET (有序集合),内部维护了多个 RedissonObject

java 复制代码
private RLock lock;                          // 并发锁
private RedissonList<V> list;                // 排序后的元素列表
private RBucket<String> comparatorHolder;    // 比较器字符串表示
private RBucket<Object> comparatorInstanceHolder; // 比较器实例
Java 方法 Redis 命令
add(e) ZADD
range(start, end) ZRANGE
rangeByScore(start, end) ZREVRANGEBYSCORE
rank(e) ZRANK
score(e) ZSCORE
add(e, score) ZADD score

排序原理 :内部值通过 Comparator 比较后放入 RedissonList,每次 add/remove 都通过锁保证排序后的 List 一致性。

7.3 RedissonSetCache(带 TTL 的 Set)

为 Set 增加逐元素 TTL 能力:

数据结构:ZSET,score 为到期时间戳(毫秒)。

lua 复制代码
-- 包含检查(带过期判断)
local expireDateScore = redis.call('zscore', KEYS[1], ARGV[2])
if expireDateScore ~= false then
    if tonumber(expireDateScore) <= tonumber(ARGV[1]) then
        return 0    -- 已过期
    end
    return 1
end
return 0

过期清理 :由 EvictionScheduler 定时扫描 ZSET 中 score <= now 的元素并删除:

复制代码
EvictionScheduler 后台任务
  -> ZRANGEBYSCORE {name} -inf {now}  // 获取所有过期元素
  -> 批量 ZREM 删除

8. PubSub 消息机制

8.1 RedissonTopic(发布/订阅)

java 复制代码
RTopic<String> topic = redisson.getTopic("chat-room");

// 发布
topic.addListener(new MessageListener<String>() {
    @Override
    public void onMessage(String channel, String msg) {
        System.out.println("Received: " + msg);
    }
});

// 发布消息(异步)
topic.publish("Hello, Redisson!");

发布 使用标准 Redis PUBLISH 命令:

java 复制代码
public RFuture<Long> publishAsync(Object message) {
    return commandExecutor.writeAsync(name, StringCodec.INSTANCE,
        RedisCommands.PUBLISH, name, commandExecutor.encode(codec, message));
}

8.2 PublishSubscribeService

Redisson 的 PubSub 连接管理非常精细:

复制代码
PublishSubscribeService
  ├── PubSubConnectionEntry (每个 entry = 一个 PubSub 连接)
  │   ├── channelName + MasterSlaveEntry (作为唯一键)
  │   └── List<PubSubListener<?>> (该连接上的所有 listener)
  ├── PubSubKey ((channelName, entry)) 确保共享连接
  ├── semaphorePubSub        (专用 PubSub 通道)
  ├── countDownLatchPubSub   (CountDownLatch 专用 PubSub)
  └── lockPubSub             (锁机制专用 PubSub)

连接复用:同一 channel 的不同 listener 共享同一个 PubSub 连接,减少 TCP 连接数。

8.3 锁的 PubSub 机制

锁等待是 Redisson 中最复杂的 PubSub 使用场景:

lua 复制代码
-- 解锁时广播消息
redis.call('PUBLISH', 'redisson_lock__channel:{mylock}', '0L')
-- '0L' = UNLOCK_MESSAGE (写锁释放)
-- '1L' = READ_UNLOCK_MESSAGE (读锁释放)
java 复制代码
// LockPubSub 收到消息后
if (message.equals(UNLOCK_MESSAGE)) {
    RedissonLockEntry entry = entries.get(channel);
    entry.getLatch().release();  // 唤醒一个等待线程
}

9. Lua 脚本执行引擎

9.1 脚本执行入口

Redisson 在 CommandAsyncExecutor 中统一处理 Lua 脚本:

java 复制代码
// 写脚本
commandExecutor.evalWriteAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
    "if redis.call('hsetnx', KEYS[1], ARGV[1], ARGV[2]) == 1 then " +
    "return nil else return redis.call('hget', KEYS[1], ARGV[1]) end",
    Collections.singletonList(name), encodeMapKey(key), encodeMapValue(value));

// 读脚本
commandExecutor.evalReadAsync(name, codec, RedisCommands.EVAL_MAP_VALUE,
    "-- script", KEYS, ARGV);

9.2 脚本缓存(SCRIPT HASH 优化)

ServiceManager 维护 SHA_CACHE(LRUCache,上限 500 条),缓存脚本 SHA:

复制代码
执行流程:
  1. 计算脚本的 SHA256
  2. SCRIPT EXISTS <sha>  -- 检查缓存
  3a. 已缓存 → EVALSHA <sha> <keys> <args>
  3b. 未缓存 → SCRIPT LOAD <script> → EVALSHA <sha> <keys> <args>

为什么这么做? EVAL 每次都需要传输完整脚本,EVALSHA 只需传输 40 字符的 SHA,大幅减少网络开销。

9.3 RedisCommands 命令类型

RedisCommands 定义了所有 Lua 命令的返回类型:

java 复制代码
EVAL_NULL_BOOLEAN   -- 返回 nil 或 true/false
EVAL_NULL_STRING    -- 返回 nil 或 string
EVAL_BOOLEAN        -- 返回 true/false
EVAL_STRING         -- 返回 string
EVAL_OBJECT         -- 返回 Java Object
EVAL_MAP_VALUE      -- 返回 Map 的 value(处理 nil 映射为 null)
EVAL_INTEGER        -- 返回 int
EVAL_LONG           -- 返回 long
EVAL_LIST           -- 返回 List
EVAL_SCAN           -- 用于 SCAN 命令的迭代解码

这些类型决定了返回值转换器如何解析 Redis 的原始响应。

9.4 同步执行模式

Redisson 在单个 Netty Channel 上按顺序执行命令,通过 evalWriteSyncedNoRetryAsync 确保:

  • 脚本与锁操作在同一连接上顺序执行
  • 避免命令交错导致的竞态条件

10. 连接池管理

10.1 MasterSlaveEntry

封装单个主从节点对的连接池:

复制代码
MasterSlaveEntry
  ├── MasterSlaveServersConfig (配置)
  ├── MasterConnectionPool     → 管理到 Redis Master 的连接
  │   ├── connectionLength (当前连接数)
  │   ├── connections (所有连接列表)
  │   └── availableConnections (可用连接集合)
  ├── SlaveConnectionPool      → 管理到 Redis Slave 的连接
  └── connectionUpdateListener (连接状态变更监听)

10.2 RedisConnection

封装单个 Netty Channel 到 Redis 实例的连接:

java 复制代码
class RedisConnection {
    final Channel channel;           // Netty Channel
    final RedisClient client;        // Redis 客户端地址
    final AtomicLong id;             // 连接 ID
    final Queue<RedisCommand> commands;  // 命令队列
}

10.3 连接池配置

java 复制代码
// 主从模式下的连接池配置
config.useMasterSlaveServers()
    .setMasterConnectionPoolSize(64)        // 主连接池最大大小
    .setMasterConnectionMinimumIdleSize(10)  // 最小空闲连接数
    .setIdleConnectionTimeout(10000)        // 空闲连接超时 (10s)
    .setPingConnectionInterval(30000)       // 心跳检测间隔 (30s)

10.4 连接恢复

  • IdleConnectionWatcher:检测空闲连接,触发重连
  • ReconnectionDelay:指数退避重连策略(避免频繁重连导致资源耗尽)
  • 自动重连对用户透明,无需处理断线

11. 集群支持

11.1 ClusterConnectionManager

处理 Redis Cluster 模式的核心组件:

复制代码
ClusterConnectionManager
  ├── 启动时 → CLUSTER NODES → 获取完整集群拓扑
  ├── 槽位映射 → Map<Integer, MasterSlaveEntry> (16384 个 slot)
  ├── 命令路由 → 根据 slot 确定目标节点
  └── 拓扑刷新 → 定时 SCAN 集群状态(scanInterval 配置)

11.2 槽位计算

java 复制代码
// CRC16 计算(Redis 标准算法)
int slot =CRC16(key) & 16383;

// 命令路由
MasterSlaveEntry entry = entries.get(slot);

11.3 跨槽位操作

集群模式下,如果 Lua 脚本操作多个 key(分布在不同槽位),需要使用 EVAL 而非 EVALSHA(集群可能对 EVALSHA 有限制)。Redisson 通过 MOVED/ASK 重定向处理跨槽位迁移。

11.4 读取模式

ReadMode 说明
MASTER 所有读操作都去主节点
REPLICA 读操作去从节点(读多写少场景)
MASTER_SLAVE 优先主节点,主不可用时降级到从节点

11.5 订阅模式

SubscriptionMode 说明
MASTER 只在主节点上订阅(推荐)
SLAVE 在从节点上订阅

12. 响应式/异步 API(RFuture)

12.1 RFuture 接口

Redisson 不直接使用 Java 的 CompletableFuture,而是定义了自己的 RFuture<V>

java 复制代码
public interface RFuture<V> extends CompletionStage<V> {
    V get() throws ExecutionException, InterruptedException;       // 同步阻塞
    boolean isSuccess();                                            // 是否成功
    boolean complete(V value);                                      // 手动完成
    boolean completeExceptionally(Throwable cause);                 // 异常完成
    // 继承 CompletionStage 全部方法:
    // thenApply, thenCompose, thenAccept, whenComplete, exceptionally 等
}

12.2 CompletableFuture 封装

java 复制代码
public final class CompletableFutureWrapper<V> implements RFuture<V> {
    private final CompletableFuture<V> future;

    @Override
    public V get() throws ExecutionException, InterruptedException {
        return future.get();
    }

    @Override
    public boolean complete(V value) {
        return future.complete(value);
    }

    // 实现 CompletionStage 的所有方法,委托给 CompletableFuture
    @Override
    public <U> RFuture<U> thenApply(Function<? super V, ? extends U> fn) {
        return new CompletableFutureWrapper<>(future.thenApply(fn));
    }
    // ...
}

12.3 异步链式调用

java 复制代码
redisson.getMap("mymap")
    .getAsync("key")
    .thenApply(value -> transform(value))          // 转换结果
    .thenCompose(result -> redisson.getBucket("b")
        .setAsync(result))                          // 链式写入
    .whenComplete((r, e) -> {                      // 最终回调
        if (e != null) { logger.error("failed", e); }
        else { logger.info("success: {}", r); }
    });

12.4 响应式桥接

Redisson 提供响应式库将 RFuture 桥接到响应式流:

  • RxJava2redisson-reactor --- 桥接到 Flux/Mono
  • Project Reactorredisson-reactive --- 桥接到 Reactor 的 Flux/Mono

13. 配置系统

13.1 Config 总入口

java 复制代码
public class Config {
    int threads = 16;                     // 业务线程池大小(Netty 回调线程)
    int nettyThreads = 32;               // Netty EventLoopGroup 线程数
    Codec codec = new Kryo5Codec();      // 默认序列化器
    long lockWatchdogTimeout = 30_000L;  // 看门狗超时 (30s)
    int lockWatchdogBatchSize = 100;     // 锁续期批量大小
    long fairLockWaitTimeout = 300_000L; // 公平锁最大等待时间 (5min)
    String mode = null;                  // 传输模式(NIO/EPOLL/KQUEUE)
    Protocol protocol = Protocol.RESP2;  // Redis 协议版本
    int timeout = 3000;                  // 命令超时 (3s)
    String password = null;              // 密码
}

13.2 各模式配置

单机模式

java 复制代码
Config config = new Config();
config.useSingleServer()
    .setAddress("redis://127.0.0.1:6379")
    .setConnectionPoolSize(64)
    .setConnectionMinimumIdleSize(10)
    .setTimeout(3000);

集群模式

java 复制代码
Config config = new Config();
config.useClusterServers()
    .addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
    .setScanInterval(2000)        // 集群拓扑刷新间隔
    .setReadMode(ReadMode.REPLICA)
    .setMasterConnectionPoolSize(64);

主从模式

java 复制代码
Config config = new Config();
config.useMasterSlaveServers()
    .setMasterAddress("redis://127.0.0.1:6379")
    .addSlaveAddress("redis://127.0.0.1:6380")
    .addSlaveAddress("redis://127.0.0.1:6381")
    .setReadMode(ReadMode.REPLICA);

哨兵模式

java 复制代码
Config config = new Config();
config.useSentinelServers()
    .setMasterName("mymaster")
    .addSentinelAddress("redis://127.0.0.1:26379", "redis://127.0.0.1:26380");

13.3 配置拷贝机制

java 复制代码
// Redisson 构造时深拷贝配置
Config configCopy = new Config(config);

防止多个 RedissonClient 实例共享同一配置导致线程安全问题。


14. 速率限制器(RRateLimiter)

14.1 令牌桶算法

RedissonRateLimiter 基于令牌桶算法实现,数据结构:

数据结构 Key 用途
Hash {name} 存储 rate、interval、type、keepAliveTime 配置
String {name}:value 当前可用令牌数
String {name}:{clientId}:value 客户端维度令牌数(CLIENT 模式)
ZSET {name}:permits 已消耗令牌的时间记录
ZSET {name}:{clientId}:permits 客户端维度消耗记录

14.2 核心 Lua 脚本

lua 复制代码
-- tryAcquire 核心逻辑
-- 1. 清理过期消耗记录,恢复令牌
local expiredValues = redis.call('zrangebyscore', permitsName, 0,
    tonumber(ARGV[2]) - interval);
local released = 0;
for i, v in ipairs(expiredValues) do
    local random, permits = struct.unpack('Bc0I', v);
    released = released + permits;
end;
if released > 0 then
    redis.call('zrem', permitsName, unpack(expiredValues));
    currentValue = tonumber(currentValue) + released;
end;

-- 2. 检查是否有足够令牌
if tonumber(currentValue) < tonumber(ARGV[1]) then
    -- 令牌不足,返回等待时间
    res = 3 + interval - (tonumber(ARGV[2]) - tonumber(firstValue[2]));
else
    -- 扣减令牌,记录消耗
    redis.call('zadd', permitsName, ARGV[2], struct.pack('Bc0I', random, ARGV[1]));
    redis.call('decrby', valueName, ARGV[1]);
    res = nil;  -- nil 表示成功
end;
return res;

14.3 阻塞等待

如果 tryAcquire 返回等待时间(非 nil),Redisson 使用 HashedWheelTimer 调度延迟任务重试:

java 复制代码
// acquire() 阻塞获取
long delay = tryAcquire();
if (delay > 0) {
    timer.newTimeout(task -> {
        acquire();  // 重试
    }, delay, TimeUnit.MILLISECONDS);
}

14.4 两种模式

RateType 说明
OVERALL 全局共享令牌(所有客户端共享一个令牌桶)
CLIENT 每个客户端独立令牌配额(通过 clientId 隔离)

15. 架构总结与设计哲学

15.1 整体架构层次

复制代码
用户代码
  │
  ├── RMap / RLock / RQueue / RTopic / RSet / ...          (接口层)
  │
  ├── RedissonMap / RedissonLock / RedissonBlockingQueue    (实现层)
  │
  ├── RedissonObject / RedissonExpirable                     (基类层,name + codec)
  │
  ├── CommandAsyncExecutor                                   (命令执行层)
  │     ├── writeAsync() / readAsync() / evalAsync()
  │     ├── Codec 编解码
  │     └── Lua 脚本执行 + SHA 缓存
  │
  ├── ConnectionManager                                      (连接管理层)
  │     ├── MasterSlaveEntry / ClusterNode
  │     ├── 连接池管理
  │     └── 槽位路由 / 读写分离
  │
  ├── ServiceManager                                         (基础设施层)
  │     ├── Netty EventLoopGroup
  │     ├── HashedWheelTimer(定时任务)
  │     ├── SHA_CACHE(脚本缓存)
  │     └── PubSub 连接管理
  │
  └── Redis Server                                           (存储层)

15.2 核心设计哲学

一句话总结:Redisson 用 Lua 脚本将多个 Redis 命令组合为原子操作,通过 Netty 实现异步非阻塞通信,通过 PubSub 实现分布式事件通知。

三大支柱

支柱 解决什么问题 关键技术
Lua 脚本 保证分布式操作的原子性 EVAL/EVALSHA + SHA 缓存
Netty 异步 高效网络 I/O,非阻塞通信 Channel + EventLoopGroup + Promise
PubSub 通知 分布式等待/唤醒,避免空转 Redis PubSub + CountDownLatch

设计亮点

  1. 纯 Java,零框架依赖 ------ 不依赖 Spring,不依赖任何 DI 容器,开箱即用
  2. 对象语义映射 ------ 用熟悉的 Java 集合 API 操作分布式数据,对上层透明
  3. 细粒度锁 ------ 每个 key 独立加锁,最大程度减少并发冲突
  4. 看门狗自动续期 ------ 无需客户端显式续期,crash 自动释放,优雅解决租约问题
  5. 脚本 SHA 缓存 ------ 避免重复传输脚本体,减少网络开销
  6. 多连接池 ------ 每个 Redis 节点独立连接池,连接复用,高效利用资源
  7. 异步原生 ------ 所有操作默认异步返回 RFuture,同步方法只是 .get() 阻塞封装

15.3 关键类关系图

复制代码
Redisson
  ├── RedissonMap ──────────→ Hash (HSET/HGET/HDEL/HMGET)
  ├── RedissonList ─────────→ List (RPUSH/LPOP/LINDEX/LRANGE)
  ├── RedissonSet ──────────→ Set (SADD/SREM/SISMEMBER/SMEMBERS)
  ├── RedissonSortedSet ────→ ZSET (ZADD/ZRANGE/ZRANK/ZSCORE)
  ├── RedissonSetCache ─────→ ZSET (逐元素 TTL)
  ├── RedissonBucket ───────→ String (SET/GET/SETNX)
  ├── RedissonLock ─────────→ Hash + PubSub + Lua
  ├── RedissonFairLock ─────→ Hash + List + ZSET + PubSub + Lua
  ├── RedissonReadWriteLock → Hash + List + Set + ZSET + PubSub + Lua
  ├── RedissonBlockingQueue → List (BLPOP/BRPOP)
  ├── RedissonDelayedQueue → ZSET + List + PubSub + Lua
  ├── RedissonTopic ───────→ PubSub (PUBLISH/SUBSCRIBE)
  └── RedissonRateLimiter → Hash + String + ZSET + Lua

15.4 源码学习建议

  1. Redisson.java 入口类入手,理解整体构造流程
  2. 重点研读 RedissonLock,它涵盖了 Lua 脚本、PubSub、看门狗、公平锁、读写锁等核心机制
  3. 理解 CommandAsyncExecutor,它是所有 Redis 操作的统一出口
  4. 跟随 ConnectionManager 理解连接池和集群路由
  5. 关注 RFuture 链式调用,理解异步编程范式
相关推荐
装不满的克莱因瓶1 小时前
Servlet 到 Spring MVC 架构演进:Java Web 开发二十年技术变迁史
java·spring·servlet·架构·springmvc
周末也要写八哥1 小时前
浅谈:C++中cpp 14 ~ cpp 17
开发语言·c++·算法
不会C语言的男孩1 小时前
C++ Primer 第13章:拷贝控制
开发语言·c++
z落落1 小时前
C# 静态成员 vs 非静态成员(调用规则+内存特点)+只读和常量 const常量 / readonly / static readonly 三者终极区别
java·开发语言·c#
c238561 小时前
map和set
数据结构·c++
java1234_小锋1 小时前
LangChain4j 开发Java Agent智能体- 整合SpringBoot4
java·开发语言·langchain4j
basketball6161 小时前
C++进阶:3. unique_ptr 现代C++内存管理的基石
java·jvm·c++
FFZero11 小时前
[mpv脚本系统] (三) C 函数如何注册成 Lua 模块
c++·音视频·lua
我不是懒洋洋2 小时前
从零实现一个Redis客户端:RESP协议与网络编程
开发语言·c++