Redisson 本质是 "Redis 分布式对象的 Java 封装层",通过异步通信 + Lua 原子性 + 连接池管理 + 分布式一致性机制,将 Redis 原生命令抽象为符合 Java 习惯的分布式对象(Map/Lock/Queue 等),同时解决了分布式场景下的锁续期、过期清理、节点路由、并发等待等核心问题,兼顾高性能与易用性。
目录
- 核心架构总览
- [Netty 异步通信层](#Netty 异步通信层)
- [RedissonMap 分布式 Map 实现](#RedissonMap 分布式 Map 实现)
- [RedissonLock 分布式锁](#RedissonLock 分布式锁)
- [RedissonList / Deque](#RedissonList / Deque)
- [RedissonQueue / BlockingQueue / DelayedQueue](#RedissonQueue / BlockingQueue / DelayedQueue)
- [RedissonSet / SortedSet / SetCache](#RedissonSet / SortedSet / SetCache)
- [PubSub 消息机制](#PubSub 消息机制)
- [Lua 脚本执行引擎](#Lua 脚本执行引擎)
- 连接池管理
- 集群支持
- [响应式/异步 API(RFuture)](#响应式/异步 API(RFuture))
- 配置系统
- 速率限制器(RRateLimiter)
- 架构总结与设计哲学
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) // 异步回调
核心要点:
- Netty 的
EventLoopGroup由ServiceManager统一管理,使用NioEventLoopGroup或EpollEventLoopGroup(Linux 原生) - 定时任务使用
HashedWheelTimer(精确定时器) - 通信管道是同步执行的------单个 Netty Channel 上的命令按顺序发送和返回,避免命令交错
- 编解码由
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,重置过期时间
- 否则返回剩余过期时间(客户端据此计算等待时间)
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 |
设计亮点:
- 纯 Java,零框架依赖 ------ 不依赖 Spring,不依赖任何 DI 容器,开箱即用
- 对象语义映射 ------ 用熟悉的 Java 集合 API 操作分布式数据,对上层透明
- 细粒度锁 ------ 每个 key 独立加锁,最大程度减少并发冲突
- 看门狗自动续期 ------ 无需客户端显式续期,crash 自动释放,优雅解决租约问题
- 脚本 SHA 缓存 ------ 避免重复传输脚本体,减少网络开销
- 多连接池 ------ 每个 Redis 节点独立连接池,连接复用,高效利用资源
- 异步原生 ------ 所有操作默认异步返回 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 源码学习建议
- 从
Redisson.java入口类入手,理解整体构造流程 - 重点研读
RedissonLock,它涵盖了 Lua 脚本、PubSub、看门狗、公平锁、读写锁等核心机制 - 理解
CommandAsyncExecutor,它是所有 Redis 操作的统一出口 - 跟随
ConnectionManager理解连接池和集群路由 - 关注
RFuture链式调用,理解异步编程范式