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 的 EventLoopGroup 由 ServiceManager 统一管理,使用 NioEventLoopGroup 或 EpollEventLoopGroup(Linux 原生)
  2. 定时任务使用 HashedWheelTimer(精确定时器)
  3. 通信管道是同步执行的------单个 Netty Channel 上的命令按顺序发送和返回,避免命令交错
  4. 编解码由 Codec 接口抽象,支持 JacksonCodec、Kryo5Codec、CborJacksonCodec、ForyCodec 等多种序列化方案

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 桥接到响应式流:

  • RxJava2 :redisson-reactor --- 桥接到 Flux/Mono
  • Project Reactor :redisson-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 链式调用,理解异步编程范式
相关推荐
\光辉岁月/36 分钟前
7.javase-面向对象
java·开发语言
weixin_531094691 小时前
heap_4内存管理
java·开发语言·算法
余额瞒着我当琳1 小时前
红黑树的实现--红黑树的概念、规则、效率、结构、旋转、查找、验证
数据结构·c++·算法
程序员南飞1 小时前
win系统安装minio
java·数据结构·算法
青少儿编程课堂1 小时前
AC自动机多模式串匹配精讲:校园关键词巡检实战解析
c++·python·算法·bfs·信息学竞赛
Escalating_xu2 小时前
【Python】使用库:标准库、第三方库、pip、项目实战与程序打包
java·python·pip
longlongzihan2 小时前
LeetCode 5. 最长回文子串:从暴力匹配到中心扩展的思维演进
c++·算法·leetcode
Wang's Blog2 小时前
Java框架 SpringCloud 快速入门: 过滤器链执行顺序
java·spring cloud
多弗朗皮卡丘2 小时前
C++继承
c++·继承
砚底藏山河2 小时前
量化实战:回测数据底座快照与版本管理进阶
java·大数据·python·金融·maven