================================================================================
JRedisX 开发文档 v1.0
从零手写企业级内存数据库
================================================================================
【文档信息】
项目名: JRedisX
版本: v1.0
日期: 2026-07-20
目标: 从零独立开发,不依赖 Redis 官方源码,仅参考 RESP 开放协议规范
定位: 学习 Java 核心技术 + 打造知名开源项目
================================================================================
第一章 项目宣言
================================================================================
1.1 独立性声明
本项目所有代码从零独立设计与实现,不复制、不依赖 Redis 官方 C 源码,
不直接移植任何第三方 Redis 实现。仅参考以下公开资料:
- RESP (Redis Serialization Protocol) 开放协议规范
- Redis 命令文档(公开接口定义)
- 通用数据结构与算法公开知识
核心目标有两个:
(1) 通过从零构建一个完整的网络内存数据库,彻底掌握 Java 核心技术
(2) 打造一个高质量、可运行、有影响力的开源项目
1.2 为什么叫 JRedisX
J = Java
Redis = 兼容 Redis 协议与命令生态
X = eXtended / eXpert / eXecution / 无限可能
1.3 核心目标
[功能目标]
- 完整兼容 RESP2/RESP3 协议
- 支持 String/Hash/List/Set/ZSet/Stream 全部数据类型
- 支持 AOF/RDB 持久化、主从复制、Sentinel、Cluster
- 支持 ACL、Lua 脚本、事务、慢查询等企业级特性
[性能目标]
- 单机 String GET/SET QPS >= 100K(8 核环境)
- P99 延迟 < 1ms(本地网络)
- 内存效率接近原生 Redis(通过紧凑编码 + 堆外优化)
[学习目标]
- 深入掌握 Netty NIO、多线程并发、内存管理
- 理解跳表、哈希表、时间轮等核心数据结构
- 掌握分布式系统基础:复制、共识、分区容错
1.4 技术栈
- JDK 17+(推荐 ZGC 降低 GC 停顿)
- Netty 4.1.x(网络 I/O,不重复造轮子)
- JCTools(无锁并发集合,学习其设计思想)
- Maven 构建
- JUnit 5 + JMH(测试与基准)
================================================================================
第二章 总体架构
================================================================================
2.1 架构分层
┌─────────────────────────────────────────────────────────────┐
│ Layer 1: 接入层 (Access Layer) │
│ • Netty Server (Boss + Worker EventLoopGroup) │
│ • RESP2/RESP3 编解码器 │
│ • 连接管理 (maxclients, timeout, keepalive) │
│ • ACL 前置拦截 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Layer 2: 路由层 (Router Layer) │
│ • CRC16 计算 Slot (16384 slots) │
│ • Key → Slot → CommandThread 绑定 │
│ • 跨 Slot 命令协调 (MGET/MSET/事务) │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Layer 3: 执行层 (Execution Layer) │
│ • 注解驱动命令注册表 (@Command) │
│ • 单线程命令执行(每线程处理固定 Slot 集合,无锁) │
│ • 事务管理器 (WATCH/MULTI/EXEC/DISCARD) │
│ • Lua 脚本引擎 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Layer 4: 存储层 (Storage Layer) │
│ • 内存引擎: HashTable + SkipList + QuickList + ZipList │
│ • 过期管理: 惰性删除 + 时间轮定期清理 │
│ • 淘汰管理: LRU/LFU/Random/TTL 策略 │
│ • 内存统计与 maxmemory 控制 │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ Layer 5: 持久化与复制层 (Persistence & Replication) │
│ • AOF: 双缓冲 RingBuffer + 后台刷盘 + Rewrite │
│ • RDB: 引用切换快照(Java 无 fork 的替代方案) │
│ • 主从复制: 全量同步 + 增量同步 (Replication Buffer) │
│ • Sentinel: 监控 + 自动故障转移 (Raft 选举) │
│ • Cluster: 16384 Slot + Gossip + 客户端重定向 │
└─────────────────────────────────────────────────────────────┘
2.2 线程模型
【核心设计】Multi-Threaded Per-Slot
原生 Redis 单线程模型在 Java 下会浪费多核性能。
JRedisX 采用 "同一 Key 的操作始终在同一线程执行" 的策略,
实现无锁并行。
线程组成:
Boss Group : 1 线程 → 接收新连接
Worker Group : N 线程 → 网络读写 + RESP 解析 (N = CPU 核心数)
Command Group : N 线程 → 命令执行 (核心,每个线程绑定固定 Slot)
AOF Group : 1-2 线程 → AOF 缓冲区刷盘
RDB Group : 1 线程 → 后台快照生成
Replica Group : 1-2 线程 → 主从同步数据发送/接收
Expire Group : 1 线程 → 过期键扫描与清理
Sentinel Group : 独立线程 → 高可用监控 (Sentinel 模式下)
Cluster Group : 独立线程 → Gossip 通信 (Cluster 模式下)
Slot 分配算法:
slot = CRC16(key) & 0x3FFF // 0 ~ 16383
threadIndex = slot % commandThreadCount
路由规则:
• 单 Key 命令 → 直接投递到对应 CommandThread 队列
• 多 Key 命令 → 按 Slot 分组,并行执行,聚合结果
• 事务命令 → 检查所有 Key 是否在同一线程,是则执行,否则报错
• 全局命令 → 获取全局锁或暂停所有线程后执行
================================================================================
第三章 模块详细设计
================================================================================
3.1 协议层 (jredisx-protocol)
3.1.1 RESP 协议
RESP (Redis Serialization Protocol) 是文本协议,简单且 human-readable。
JRedisX 同时支持 RESP2 和 RESP3,根据客户端握手自动选择版本。
RESP2 类型标记:
+ Simple String
- Error
: Integer
$ Bulk String
* Array
RESP3 新增类型:
_ Null
, Double
# Boolean
! Blob Error
= Verbatim String
( Big Number
% Map
~ Set
| Attribute
> Push
编解码设计:
• RESPDecoder: ByteBuf → RedisMessage 对象树
• RESPEncoder: RedisMessage → ByteBuf
• 使用 Netty ReplayingDecoder 简化状态机实现
3.1.2 Netty Pipeline
ChannelPipeline 顺序:
[LoggingHandler] → [IdleStateHandler] → [RESPDecoder]
→ [ACLHandler] → [CommandRouter] → [ExecutionHandler]
→ [RESPEncoder]
每个 Handler 职责:
• LoggingHandler : 可选,记录连接日志
• IdleStateHandler : 检测空闲连接,超时断开
• RESPDecoder : 字节流 → RedisMessage
• ACLHandler : 命令权限校验(命令/Key/频道级别)
• CommandRouter : 计算 Slot,路由到对应 CommandThread
• ExecutionHandler: 将命令放入线程队列(异步)
• RESPEncoder : RedisMessage → 字节流
3.1.3 ACL (Access Control List)
用户定义:
USER <username> <password_hash> <flags> <permissions>
权限粒度:
• 命令级别: +GET, +SET, -FLUSHALL, +@all, -@dangerous
• Key 级别: %R~read*, %W~write*, %RW~cache:*
• 频道级别: &chat*, &news*
实现:
• 用户表: ConcurrentHashMap<String, User>
• 密码存储: SHA-256 + Salt
• 校验时机: ACLHandler 在命令路由前拦截
• 默认用户: default(无密码,拥有所有权限,可禁用)
3.2 路由层 (jredisx-router)
3.2.1 Slot 计算
CRC16 算法实现(独立实现,不依赖外部库):
private static final int[] CRC16_TABLE = { ... };
public static int crc16(byte[] bytes) {
int crc = 0;
for (byte b : bytes) {
crc = ((crc << 8) ^ CRC16_TABLE[((crc >>> 8) ^ (b & 0xFF)) & 0xFF]) & 0xFFFF;
}
return crc;
}
public static int slot(byte[] key) {
// 处理 Hash Tag: key{tag}sub 用 tag 计算
int start = indexOf(key, (byte) '{');
if (start != -1) {
int end = indexOf(key, (byte) '}', start);
if (end != -1 && end > start + 1) {
return crc16(copyOfRange(key, start + 1, end)) & 0x3FFF;
}
}
return crc16(key) & 0x3FFF;
}
3.2.2 线程绑定
每个 CommandThread 维护:
• MPSC 命令队列 (JCTools SpscArrayQueue 或自定义)
• 负责的 Slot 集合 (BitSet 或 int[])
• 独立的数据库实例 (HashTable 等)
为什么 MPSC:
• 多个 Worker 线程生产命令 → 单个 CommandThread 消费
• JCTools 提供无锁实现,性能极高
3.2.3 跨 Slot 命令
MGET key1 key2 key3:
1. 计算每个 key 的 slot 和 target thread
2. 按 thread 分组,生成子任务
3. 并行发送到各线程队列
4. 使用 CompletableFuture 聚合结果
5. 按原始顺序返回
事务限制:
• WATCH 的 Key 必须在同一线程
• 否则返回 CROSSSLOT 错误
• 这是为了简化实现,保证原子性
3.3 执行层 (jredisx-command)
3.3.1 命令注册表
采用注解驱动,彻底避免巨型 switch-case:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Command {
String name(); // 命令名,如 "GET"
int arity(); // 参数个数,负数表示可变
String[] flags() default {}; // 特性标记: READ, WRITE, FAST, ADMIN
}
示例命令:
@Command(name = "GET", arity = 2, flags = {"READ", "FAST"})
public class GetCommand implements RedisCommand {
@Override
public RedisMessage execute(RedisContext ctx, List<RedisMessage> args) {
byte[] key = args.get(1).getBytes();
RedisObject value = ctx.getDatabase().get(key);
if (value == null) {
return RedisMessage.NULL_BULK;
}
if (value.getType() != DataType.STRING) {
return RedisMessage.wrongTypeError();
}
return RedisMessage.bulkString(value.getStringValue());
}
}
注册流程:
1. 启动时扫描 classpath 下所有 @Command 注解类
2. 实例化并注册到 ConcurrentHashMap<String, RedisCommand>
3. 支持运行时热加载(Module 扩展机制)
3.3.2 命令分类
┌─────────────────┬────────────────────────────────────────────┐
│ 类型 │ 命令示例 │
├─────────────────┼────────────────────────────────────────────┤
│ String │ GET, SET, MGET, MSET, INCR, DECR, APPEND │
│ │ GETSET, SETEX, SETNX, STRLEN, SETRANGE │
├─────────────────┼────────────────────────────────────────────┤
│ Hash │ HGET, HSET, HGETALL, HDEL, HINCRBY │
│ │ HKEYS, HVALS, HLEN, HMGET, HMSET, HEXISTS │
├─────────────────┼────────────────────────────────────────────┤
│ List │ LPUSH, RPUSH, LPOP, RPOP, LRANGE, LINDEX │
│ │ LLEN, LREM, LSET, LTRIM, BLPOP, BRPOP │
├─────────────────┼────────────────────────────────────────────┤
│ Set │ SADD, SREM, SISMEMBER, SMEMBERS, SCARD │
│ │ SINTER, SUNION, SDIFF, SPOP, SRANDMEMBER │
├─────────────────┼────────────────────────────────────────────┤
│ ZSet │ ZADD, ZRANGE, ZRANGEBYSCORE, ZREM, ZRANK │
│ │ ZCARD, ZCOUNT, ZINCRBY, ZREVRANGE, ZSCORE │
├─────────────────┼────────────────────────────────────────────┤
│ Key │ DEL, EXISTS, EXPIRE, TTL, PTTL, KEYS │
│ │ RENAME, RENAMENX, TYPE, FLUSHDB, FLUSHALL │
├─────────────────┼────────────────────────────────────────────┤
│ Server │ INFO, CONFIG, CLIENT, SLOWLOG, ACL │
│ │ COMMAND, TIME, DBSIZE, SAVE, BGSAVE │
├─────────────────┼────────────────────────────────────────────┤
│ Transaction │ MULTI, EXEC, DISCARD, WATCH, UNWATCH │
├─────────────────┼────────────────────────────────────────────┤
│ Script │ EVAL, EVALSHA, SCRIPT LOAD/FLUSH/KILL │
├─────────────────┼────────────────────────────────────────────┤
│ Stream │ XADD, XREAD, XGROUP, XACK, XPENDING │
│ │ XLEN, XRANGE, XREVRANGE, XDEL, XTRIM │
├─────────────────┼────────────────────────────────────────────┤
│ Pub/Sub │ SUBSCRIBE, PUBLISH, UNSUBSCRIBE │
│ │ PSUBSCRIBE, PUNSUBSCRIBE, PUBSUB │
├─────────────────┼────────────────────────────────────────────┤
│ Cluster │ CLUSTER INFO, CLUSTER NODES, CLUSTER SLOTS │
│ │ MIGRATE, ASKING, READONLY, READWRITE │
└─────────────────┴────────────────────────────────────────────┘
3.3.3 事务实现
事务状态机:
NORMAL → MULTI → EXEC/DISCARD → NORMAL
WATCH 机制:
• 每个 Key 维护一个 64 位 version 字段
• 每次修改 Key 时,version 自增
• WATCH 时记录当前 version
• EXEC 时检查 version 是否变化,变化则事务失败
实现细节:
• 事务队列: List<CommandTask>(线程本地存储)
• 事务执行: 串行执行队列中所有命令,中间不处理新请求
• 回滚: 不支持(和 Redis 一致),EXEC 是原子提交
3.3.4 Lua 脚本引擎
选型: LuaJ(纯 Java 实现,无需 JNI)
执行流程:
1. SCRIPT LOAD: 编译 Lua 代码,生成 SHA1 摘要,缓存编译结果
2. EVALSHA: 通过 SHA 查找缓存,直接执行
3. 脚本在 CommandThread 中同步执行,保证原子性
4. 脚本超时保护: 设置执行时间上限,超时抛出异常
脚本沙箱:
• 禁用危险操作(文件 IO、网络、反射)
• 限制脚本执行时间(默认 5s)
• 限制脚本最大内存使用
3.4 存储层 (jredisx-storage)
3.4.1 RedisObject 对象体系
所有数据对象的基类:
public abstract class RedisObject {
protected final DataType type;
protected EncodingType encoding;
protected long lru; // LRU/LFU 时钟字段
protected long expireTime; // -1 表示不过期
protected volatile int refcount; // 引用计数(用于共享对象)
public abstract long getUsedMemory();
public abstract void encode();
}
具体实现:
StringObject:
• 小字符串 (< 44 字节): byte[] 直接存储
• 大字符串: DirectByteBuffer(堆外)
• 整数字符串: 解析为 long,用 long 存储(节省内存)
HashObject:
• 小 Hash (< 512 字段, 每个 < 64 字节): ZipList 编码
• 大 Hash: HashMap<byte[], byte[]> 编码
• 渐进式迁移: 从 ZipList 升级到 HashMap 时,逐步迁移
ListObject:
• 底层: QuickList(双向链表,每个节点是 ZipList)
• ZipList 节点大小可配置(默认 8KB)
• 支持两端 O(1) 插入删除,中间 O(N)
SetObject:
• 小整数集 (< 512 个整数): IntSet(有序数组)
• 其他: HashSet<byte[]>(HashTable 实现)
• IntSet 查找: 二分查找 O(logN)
ZSetObject:
• 双索引结构:
- SkipList: 按 score 排序,支持范围查询 O(logN)
- HashMap: member → score,支持单点查询 O(1)
• 相同 score 时按 member 字典序排序
StreamObject:
• 底层: Radix Tree(基数树)
• 每个节点存储若干 StreamEntry
• 支持按 ID 范围查询和消费者组
3.4.2 核心数据结构实现
(1) 哈希表 (Dict) --- 渐进式 Rehash
public class Dict {
private DictEntry[][] ht = new DictEntry[2][];
private int rehashidx = -1; // -1 表示未在 rehash
public DictEntry get(byte[] key) {
if (isRehashing()) {
// 先在 ht[1] 查找,再在 ht[0] 查找
// 同时迁移一个桶
migrateOneBucket();
}
int idx = hash(key) & (ht[0].length - 1);
return findEntry(ht[0][idx], key);
}
public void put(byte[] key, RedisObject value) {
if (isRehashing()) {
migrateOneBucket();
}
// 插入逻辑...
// 触发扩容检查
if (needsExpand()) {
startRehash();
}
}
private void startRehash() {
int newSize = ht[0].length * 2;
ht[1] = new DictEntry[newSize];
rehashidx = 0;
}
private void migrateOneBucket() {
// 将 ht[0][rehashidx] 的所有 Entry 迁移到 ht[1]
// rehashidx++
// 如果全部迁移完成,ht[0] = ht[1]; ht[1] = null; rehashidx = -1
}
}
扩容触发: 负载因子 > 1.0
缩容触发: 负载因子 < 0.1
扩容大小: 2 的幂次,最小 4
(2) 跳表 (SkipList) --- ZSet 核心
public class SkipList {
private static final int MAX_LEVEL = 32;
private SkipListNode header;
private int level; // 当前最大层数
private long length;
class SkipListNode {
byte[] member;
double score;
SkipListNode backward;
SkipListLevel[] level;
}
class SkipListLevel {
SkipListNode forward;
int span; // 到 forward 节点的跨度
}
// 随机层数: 1/2 概率升层
private int randomLevel() {
int level = 1;
while (Math.random() < 0.5 && level < MAX_LEVEL) {
level++;
}
return level;
}
// 插入: O(logN)
// 删除: O(logN)
// 范围查询: O(logN + M),M 为结果数
}
(3) 压缩列表 (ZipList) --- 内存紧凑
ZipList 是连续内存结构:
<zlbytes> <zltail> <zllen> <entry> <entry> ... <entry> <zlend>
Entry 格式:
| prevlen | encoding | content |
prevlen: 前一个 Entry 的长度(变长编码)
encoding: 内容类型和长度
• 00xxxxxx: 1 字节,后 6 位为长度,字符串
• 01xxxxxx xxxxxxxx: 2 字节,后 14 位为长度,字符串
• 10000000: 5 字节,后 32 位为长度,字符串
• 11000000: 2 字节,int16
• 11010000: 4 字节,int32
• 11100000: 8 字节,int64
优势: 极高内存效率,CPU Cache 友好
劣势: 修改时可能需要连锁更新(prevlen 变长)
优化: 限制单个 ZipList 大小,避免连锁更新扩散
(4) 快速列表 (QuickList) --- List 核心
public class QuickList {
QuickListNode head, tail;
int count; // 总元素数
int fill; // 每个 ZipList 节点的最大字节数
// 两端插入: O(1)(如果头/尾节点未满)
// 中间插入: 找到对应节点,ZipList 插入
// 节点分裂: ZipList 超过 fill 时分裂为两个节点
}
(5) 整数集合 (IntSet) --- 小 Set 优化
public class IntSet {
byte[] contents; // 有序数组,元素类型可升级: int16 → int32 → int64
int length;
int encoding; // INT16, INT32, INT64
// 查找: 二分查找 O(logN)
// 插入: 扩容 + 移动元素 O(N),但 N 很小(< 512)
// 升级: 当插入值超出当前编码范围时,整体升级
}
3.4.3 内存管理
Java 内存优化策略:
(1) 对象头开销控制
• Java 对象头 12-16 字节(64 位 JVM,压缩指针)
• 数组额外 4 字节长度字段
• 优化: 小对象合并存储(ZipList),减少对象数量
(2) 堆外内存 (Off-Heap)
• 大 Value (> 4KB): 使用 DirectByteBuffer
• 避免 GC 扫描大对象,减少堆内存压力
• 手动管理生命周期,需要引用计数
(3) 对象池
• 高频创建的对象: CommandTask, DictEntry, RedisMessage
• 使用 ThreadLocal 对象池,避免线程竞争
• 池化对象需要 reset() 方法重置状态
(4) 字符串去重
• 常用字符串(命令名、Key 前缀)使用 intern 池
• ConcurrentHashMap<String, String> 作为全局 intern 表
(5) 内存统计
• 每个 RedisObject 实现 getUsedMemory() 方法
• 全局内存计数器: AtomicLong usedMemory
• 达到 maxmemory 时触发淘汰
3.4.4 过期策略
(1) 惰性删除 (Lazy Expiration)
• 每次访问 Key 时检查 expireTime
• 如果过期,立即删除并返回 null
• 优点: 不消耗额外资源
• 缺点: 过期 Key 不被访问则一直占用内存
(2) 定期删除 (Active Expiration)
• 改进方案: 时间轮 (TimeWheel) 分桶
• 维护: Map<Long, Set<byte[]>> expireBuckets
Key: 过期时间戳按 1s 对齐
Value: 该秒内过期的所有 Key
• 每秒扫描当前桶 + 未来几秒的桶
• 限制: 每次扫描不超过 20 个 Key,耗时不超过 25ms
• 如果过期 Key 太多,自适应增加扫描频率
(3) 内存淘汰 (Eviction)
当 usedMemory >= maxmemory 时:
• noeviction: 返回 OOM 错误(默认)
• allkeys-lru: 所有 Key 按 LRU 淘汰
• volatile-lru: 只淘汰带 TTL 的 Key,按 LRU
• allkeys-lfu: 所有 Key 按 LFU 淘汰
• volatile-lfu: 带 TTL 的 Key 按 LFU 淘汰
• allkeys-random: 所有 Key 随机淘汰
• volatile-random: 带 TTL 的 Key 随机淘汰
• volatile-ttl: 淘汰即将过期的 Key(TTL 最小的)
LRU 实现:
• 每个 RedisObject 的 lru 字段: 24 位时间戳
• 记录最后访问时间
• 淘汰时遍历采样(默认 5 个),淘汰最老的
LFU 实现:
• lru 字段高 16 位: 最后访问时间(分钟精度)
• lru 字段低 8 位: 访问计数器(对数计数器)
• 访问时: 计数器按概率增加(非线性,避免快速饱和)
• 淘汰时: 采样淘汰计数最小的
3.5 持久化层 (jredisx-persistence)
3.5.1 AOF (Append Only File)
写入流程:
1. 命令执行成功后,生成 RESP 格式的命令文本
2. 放入 AOF Buffer(每个 CommandThread 一个 ThreadLocal Buffer)
3. AOF 线程每 1s(或配置间隔)收集所有 Buffer,写入文件
4. 根据 appendfsync 策略决定刷盘时机
同步策略:
• always: 每次写入都 fsync(最安全,性能最低)
• everysec: 每秒 fsync(默认,平衡)
• no: 由 OS 决定刷盘(最快,最不安全,崩溃可能丢失 30s 数据)
AOF Buffer 设计:
• 双缓冲: Buffer A 写入文件时,Buffer B 接收新命令
• 切换: 当 Buffer A 写入完成,交换 A 和 B
• 使用 CompositeByteBuf 减少内存拷贝
AOF Rewrite(重写):
• 问题: AOF 文件越来越大,重启恢复慢
• 解决: 后台生成最小命令集
• Java 实现(无 fork):
1. 启动 Rewrite 子线程
2. 创建新的 AOF 文件
3. 遍历当前内存数据,生成命令写入新文件
4. 同时维护 Rewrite Buffer,记录重写期间的新命令
5. 重写完成后,将 Rewrite Buffer 追加到新文件末尾
6. 原子替换旧文件(rename)
• 关键: 遍历期间不阻塞主线程,使用引用快照
3.5.2 RDB (Snapshot)
触发方式:
• SAVE: 阻塞式,当前线程直接保存(不推荐生产环境)
• BGSAVE: 后台保存(子线程实现)
Java 无 fork 的替代方案:
方案: 引用快照 (Reference Snapshot)
1. BGSAVE 开始时,创建 Dict 的浅拷贝(只复制桶数组引用,不复制 Entry)
2. 后台线程遍历这个快照引用,序列化写入 RDB
3. 写期间的新修改:
• 如果修改的是已有 Key: 创建新 Entry,旧 Entry 保留(快照引用仍指向旧 Entry)
• 如果是新 Key: 直接放入主 Dict,快照不可见
4. 遍历完成后,清理快照引用(旧 Entry 如果没有被其他地方引用,自然 GC)
5. 这个方案利用了 Java GC 的引用语义,天然实现 COW 效果
RDB 文件格式:
• 头部: "REDIS" + 版本号 (如 "0011")
• 元数据: 创建时间、内存版本、Lua 脚本 SHA 等
• 数据库数据:
- SELECTDB + db_number
- RESIZEDB + key_count + expire_count
- 键值对: TYPE + KEY + VALUE + [EXPIRETIME + timestamp]
• 尾部: EOF (0xFF) + CRC64 校验
3.5.3 混合持久化
• AOF 文件前半部分: RDB 格式(全量数据,加载快)
• AOF 文件后半部分: AOF 格式(增量命令,数据丢失少)
• 重启时先加载 RDB 部分,再重放 AOF 部分
• 配置项: aof-use-rdb-preamble yes
3.6 复制层 (jredisx-replication)
3.6.1 主从复制
复制 ID (Replication ID):
• 每个 Master 有一个 40 字符的随机 Replication ID
• 主从切换时,新 Master 继承旧 ID(支持部分重同步)
复制偏移量 (Offset):
• Master 每发送一个字节给 Slave,offset 增加
• Slave 记录已接收的 offset
全量同步 (Full Resync):
1. Slave 发送 PSYNC ? -1(首次连接或 ID 不匹配)
2. Master 生成新 Replication ID
3. Master 执行 BGSAVE,生成 RDB
4. Master 将 RDB 发送给 Slave
5. 发送期间的新命令缓存到 Replication Buffer
6. RDB 发送完成后,发送 Replication Buffer 中的命令
7. 之后进入命令传播模式
增量同步 (Partial Resync):
1. Slave 发送 PSYNC <replid> <offset>
2. Master 检查 replid 是否匹配
3. 检查 offset 是否在 Replication Buffer 中
4. 匹配则直接发送 offset 之后的命令
5. 不匹配则退化为全量同步
Replication Buffer:
• 环形缓冲区 (RingBuffer),固定大小(默认 1MB,可配置)
• 存储命令的 RESP 序列化字节流
• 当 Slave 断线过久,Buffer 被覆盖,则必须全量同步
无磁盘复制 (Diskless Replication):
• Master 直接通过网络发送 RDB,不落盘
• 适合磁盘慢、网络快的场景
• 实现: BGSAVE 直接写入网络 Socket,不经过文件
3.6.2 实现架构
Master 侧:
• 每个 Slave 一个发送线程 (ReplicaSender)
• 从 Replication Buffer 读取命令,发送给 Slave
• 支持主从链式复制(Slave 的 Slave)
Slave 侧:
• 接收线程: 接收 RDB 和命令,写入本地 AOF
• 加载线程: 加载 RDB 到内存
• 重放线程: 将接收的命令放入 CommandThread 队列执行
3.7 高可用层 (jredisx-ha)
3.7.1 Sentinel (哨兵)
架构:
• 3-5 个 Sentinel 节点组成集群(奇数个,避免脑裂)
• 监控主从节点健康
• 自动故障转移
通信机制:
• 每 1s: 向所有节点(Master + Slaves + Sentinels)发送 PING
• 每 2s: 通过 Pub/Sub 频道交换信息(__sentinel__:hello)
• 每 10s: 检查主从配置,更新节点信息
故障判定:
• 主观下线 (SDOWN): 单个 Sentinel 认为 Master 不可用
- 条件: 连续 N 次 PING 超时(默认 30s 内无响应)
• 客观下线 (ODOWN): 多数 Sentinel 同意,触发故障转移
- 条件: 超过 quorum 个 Sentinel 报告 SDOWN
故障转移流程:
1. 选举 Leader Sentinel(Raft 算法,先到先得)
2. Leader 从 Slave 中选出新 Master:
- 优先级(replica-priority)
- 复制偏移量(offset 最大的,数据最新)
- RunID(最小的,稳定运行时间最长)
3. 向新 Master 发送 SLAVEOF NO ONE
4. 向其他 Slaves 发送 SLAVEOF 指向新 Master
5. 更新配置,广播新 Master 信息
6. 旧 Master 恢复后,自动变为 Slave
Raft 选举简化实现:
• 每个 Sentinel 维护一个 epoch(任期号)
• 发现 ODOWN 时,Sentinel 增加 epoch,向其他 Sentinel 请求投票
• 收到多数票后成为 Leader
• 每个 Sentinel 每轮只投一票
3.7.2 Cluster (集群)
架构:
• 无中心架构,节点对等
• 16384 个 Slot 分配到各个节点
• 每个节点维护全集群的 Slot → Node 映射表
核心机制:
• Gossip 协议:
- 每 1s 随机向几个节点发送 PING,携带自身已知节点信息
- 收到 PONG 后更新本地集群状态
- 通过 gossip 传播节点加入/离开/故障信息
• MOVED 重定向:
- 客户端访问错误 Slot,返回 MOVED slot target-node
- 客户端更新本地 Slot → Node 缓存
• ASKING 重定向:
- Slot 迁移过程中,访问源节点返回 ASKING target-node
- 客户端先发送 ASKING 命令,再发送实际命令
Slot 迁移流程:
1. 目标节点准备导入: CLUSTER SETSLOT <slot> IMPORTING <source-node-id>
2. 源节点准备导出: CLUSTER SETSLOT <slot> MIGRATING <target-node-id>
3. 逐个迁移 Key: MIGRATE 命令(原子迁移单个 Key)
4. 所有 Key 迁移完成后: CLUSTER SETSLOT <slot> NODE <target-node-id>
5. 广播新 Slot 分配
客户端路由:
• 客户端缓存 Slot → Node 映射
• 收到 MOVED 时更新缓存
• 收到 ASKING 时临时重定向(不更新缓存)
• 连接池管理: 每个节点维护一个连接池
================================================================================
第四章 核心接口与类设计
================================================================================
4.1 包结构
com.jredisx
├── server // 服务器启动与生命周期
├── protocol // RESP 编解码
├── router // Slot 路由与线程绑定
├── command // 命令注册与执行
├── storage // 数据结构与存储引擎
├── persistence // AOF / RDB 持久化
├── replication // 主从复制
├── ha // Sentinel / Cluster 高可用
├── acl // 访问控制
├── script // Lua 脚本引擎
├── pubsub // 发布订阅
├── stream // Stream 数据类型
├── config // 配置管理
├── util // 工具类
└── benchmark // 性能测试
4.2 核心接口
// 服务器入口
public class JRedisXServer {
private ServerConfig config;
private NettyServer nettyServer;
private CommandEngine commandEngine;
private StorageManager storageManager;
private PersistenceManager persistenceManager;
private ReplicationManager replicationManager;
private ClusterManager clusterManager;
public void start();
public void stop();
public ServerConfig getConfig();
}
// 配置
public class ServerConfig {
private int port = 6379;
private String bind = "0.0.0.0";
private int maxclients = 10000;
private long maxmemory = 0; // 0 = 无限制
private String maxmemoryPolicy = "noeviction";
private int commandThreads = Runtime.getRuntime().availableProcessors();
// AOF
private boolean appendonly = false;
private String appendfsync = "everysec";
private int autoAofRewritePercentage = 100;
private long autoAofRewriteMinSize = 64 * 1024 * 1024;
// RDB
private String[] saveConditions; // {"900 1", "300 10", "60 10000"}
// Replication
private String replicaof; // "host:port"
private String masterauth;
private int replBacklogSize = 1024 * 1024;
// Cluster
private boolean clusterEnabled = false;
private int clusterNodeTimeout = 15000;
}
// 协议消息
public class RedisMessage {
private MessageType type;
private Object data;
public static RedisMessage simpleString(String s);
public static RedisMessage error(String msg);
public static RedisMessage integer(long value);
public static RedisMessage bulkString(byte[] data);
public static RedisMessage array(List<RedisMessage> elements);
public static RedisMessage nullBulk();
}
// 命令接口
public interface RedisCommand {
RedisMessage execute(RedisContext ctx, List<RedisMessage> args);
}
// 命令上下文
public class RedisContext {
private CommandThread thread;
private Database database;
private ClientConnection client;
private boolean inTransaction;
private List<CommandTask> transactionQueue;
private Set<byte[]> watchedKeys;
public Database getDatabase();
public ClientConnection getClient();
public void queueTransaction(CommandTask task);
public boolean isInTransaction();
}
// 存储引擎接口
public interface StorageEngine {
RedisObject get(byte[] key);
void set(byte[] key, RedisObject value);
boolean del(byte[] key);
boolean exists(byte[] key);
boolean expire(byte[] key, long ttlMs);
long ttl(byte[] key);
long pttl(byte[] key);
List<byte[]> keys(byte[] pattern);
void flushdb();
long dbsize();
Iterator<DictEntry> scan(long cursor);
}
// 数据库(每个 CommandThread 一个实例)
public class Database implements StorageEngine {
private Dict dict; // 主存储
private ExpireManager expireManager;
private EvictionManager evictionManager;
}
// 持久化管理器
public interface PersistenceManager {
void start();
void shutdown();
}
public class AOFManager implements PersistenceManager {
private AOFBuffer buffer;
private AOFWriter writer;
private AOFRewriter rewriter;
}
public class RDBManager implements PersistenceManager {
private RDBLoader loader;
private RDBSaver saver;
}
// 复制管理器
public class ReplicationManager {
private ReplicationBuffer backlog;
private List<Replica> replicas;
private ReplicationID replid;
private long masterOffset;
public void addReplica(ClientConnection conn);
public void removeReplica(ClientConnection conn);
public void propagate(CommandTask cmd);
public void sync(Replica replica);
}
// 集群管理器
public class ClusterManager {
private ClusterNode myself;
private Map<String, ClusterNode> nodes;
private ClusterNode[] slotToNode = new ClusterNode[16384];
private GossipProtocol gossip;
public ClusterNode getNodeBySlot(int slot);
public void migrateSlot(int slot, String targetNodeId);
public void handleMoved(int slot, ClientConnection client);
}
4.3 关键数据结构类
// 哈希表条目
public class DictEntry {
byte[] key;
RedisObject value;
DictEntry next; // 链表法解决冲突
}
// 跳表
public class SkipList {
SkipListNode header;
int level;
long length;
public boolean insert(byte[] member, double score);
public boolean delete(byte[] member, double score);
public List<SkipListNode> rangeByRank(long start, long end);
public List<SkipListNode> rangeByScore(double min, double max);
public Long getRank(byte[] member);
}
// 时间轮(过期管理)
public class TimeWheel {
private static final int WHEEL_SIZE = 60; // 60 秒一圈
private Map<Integer, Set<byte[]>>[] wheels; // 多层时间轮
public void add(byte[] key, long expireTime);
public Set<byte[]> expire(long now);
}
================================================================================
第五章 性能优化
================================================================================
5.1 零拷贝
• RDB 传输: Netty FileRegion 实现 sendfile
• AOF 写入: MappedByteBuffer 内存映射文件
• 网络发送: CompositeByteBuf 合并小数据包
5.2 锁优化
• 核心原则: 同 Key 同线程,无锁执行
• 全局操作: 读写锁或线程暂停
• 跨 Slot: 按 Slot 排序后顺序获取,避免死锁
5.3 内存优化
• 小整数共享: -128 ~ 127 全局缓存
• 字符串压缩: LZ4 压缩大 Value
• 对象池: ThreadLocal 池化高频对象
• GC 优化: ZGC/Shenandoah,大对象直接内存
5.4 I/O 优化
• Pipeline 批量: 批量解析、批量执行、批量回复
• TCP 参数: TCP_NODELAY 减少延迟,TCP_CORK 批量发送
• 快速拒绝: 超 maxclients 立即断开
5.5 数据结构优化
• 自适应编码: 小数据紧凑结构,大数据自动升级
• 冷热分离: 低频 Key 压缩或移出内存
• 延迟加载: 大 Hash 字段按需加载
================================================================================
第六章 开发里程碑
================================================================================
Phase 1: 基础骨架 --- 能跑起来 (4 周)
Week 1: 项目搭建 + Netty 接入 + RESP2 编解码
• Maven 多模块项目结构
• Netty Server 启动,接收连接
• RESP2 完整编解码器实现
• redis-cli 能连接并收到 PONG
Week 2: 单线程命令执行 + String 命令
• 命令注册表 + @Command 注解
• GET, SET, DEL, EXISTS, EXPIRE, TTL
• 内存数据库(简单 HashMap 版本)
• 基础单元测试
Week 3: Hash / List / Set 基础命令
• HGET, HSET, HDEL, HGETALL
• LPUSH, RPUSH, LPOP, RPOP, LRANGE
• SADD, SREM, SISMEMBER, SMEMBERS
• 数据结构实现(先不优化编码)
Week 4: 性能基准 + 代码重构
• JMH 基准测试
• 对比 Redis 单线程性能
• 重构代码结构,确保可扩展性
• 目标: 单线程 String GET/SET QPS > 50K
Phase 2: 多线程与存储优化 --- 榨干多核 (4 周)
Week 1: Slot 路由 + 多线程执行引擎
• CRC16 实现 + Slot 计算
• CommandThread 绑定 + MPSC 队列
• MGET/MSET 跨 Slot 聚合
Week 2: ZSet + SkipList + 紧凑编码
• ZADD, ZRANGE, ZRANGEBYSCORE, ZRANK
• SkipList 完整实现
• ZipList / IntSet 紧凑编码
• QuickList 实现
Week 3: 渐进式 Rehash + 内存管理
• Dict 渐进式 Rehash
• 内存统计 + maxmemory 控制
• 对象池 + 字符串去重
Week 4: 过期策略 + 淘汰策略
• 惰性删除 + 时间轮定期删除
• LRU / LFU / Random / TTL 淘汰
• 性能压测与调优
• 目标: 多核 QPS > 150K
Phase 3: 持久化 --- 数据不丢 (3 周)
Week 1: AOF 实现
• AOF Buffer + 后台刷盘线程
• appendfsync 三种策略
• AOF Rewrite(引用快照方案)
Week 2: RDB 实现
• RDB 文件格式定义
• BGSAVE(引用快照方案)
• RDB 加载与恢复
Week 3: 混合持久化 + 故障恢复测试
• AOF + RDB 混合模式
• 随机写入后 kill -9,验证恢复
• 持久化性能压测
• 目标: everysec 模式下丢失 < 1s 数据
Phase 4: 复制与高可用 --- 生产可用 (4 周)
Week 1: 主从复制
• PSYNC 协议实现
• 全量同步 + 增量同步
• Replication Buffer(RingBuffer)
Week 2: Sentinel 哨兵
• 监控 + SDOWN/ODOWN 判定
• Raft 选举 Leader
• 自动故障转移
Week 3: Cluster 集群
• 16384 Slot 分配
• Gossip 协议通信
• MOVED / ASKING 重定向
• Slot 迁移
Week 4: 高可用压测
• 主从切换测试
• 网络分区测试
• Cluster 扩缩容测试
• 目标: 自动故障转移 < 30s
Phase 5: 企业级特性 --- 功能完善 (3 周)
Week 1: ACL + 慢查询 + 监控
• 用户/密码/权限管理
• SLOWLOG 实现
• INFO 命令完善 + Prometheus 指标
Week 2: Lua + 事务
• LuaJ 脚本引擎集成
• SCRIPT LOAD / EVAL / EVALSHA
• WATCH / MULTI / EXEC / DISCARD
Week 3: Stream + Module
• Stream 数据类型(Radix Tree)
• 消费者组(Consumer Group)
• Module 扩展机制(动态加载命令)
• 目标: 功能覆盖率 > 90%
Phase 6: 生产打磨 --- 持续迭代
• 混沌测试(Chaos Engineering)
• 长稳测试(7x24 小时运行)
• 内存泄漏排查
• 文档完善 + 社区运营
• 目标: Gitee GVP / GitHub 1000+ Stars
================================================================================
第七章 测试体系
================================================================================
7.1 单元测试
• 每个数据结构独立测试(SkipList, Dict, ZipList...)
• 边界条件: 空集合、单元素、满容量、非法输入
• 工具: JUnit 5 + AssertJ
7.2 集成测试
• 启动完整服务器,Jedis/Lettuce 客户端连接
• 命令兼容性测试(覆盖所有命令)
• 持久化一致性: 随机写入 → 重启 → 数据校验
• 复制一致性: 主从数据对比工具
7.3 性能测试
• 工具: redis-benchmark, memtier_benchmark, JMH
• 指标: QPS, P50/P99/P999 延迟, 内存占用, CPU
• 场景: String GET/SET, Hash HGETALL, ZSet ZRANGE, Pipeline
7.4 故障测试
• kill -9 节点,验证 Sentinel 故障转移
• iptables 模拟网络分区
• 内存耗尽验证淘汰策略
• 磁盘满验证 AOF 失败处理
7.5 兼容性测试
• Redis 官方测试套件适配
• 主流客户端: Jedis, Lettuce, Redisson, go-redis, ioredis
================================================================================
第八章 部署与运维
================================================================================
8.1 配置文件 (jredisx.conf)
# 网络
bind 0.0.0.0
port 6379
maxclients 10000
timeout 0
tcp-keepalive 300
# 内存
maxmemory 4gb
maxmemory-policy allkeys-lru
# 线程
io-threads 4
command-threads 8
# AOF
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes
# RDB
save 900 1
save 300 10
save 60 10000
# 复制
replicaof 192.168.1.10 6379
masterauth yourpassword
repl-backlog-size 1mb
# 集群
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
8.2 监控指标
• INFO 命令: Server, Clients, Memory, Persistence, Stats,
Replication, CPU, Cluster, Commandstats
• SLOWLOG: 慢查询记录
• 实时指标: 每秒命令数、连接数、命中率、内存使用
8.3 运维工具
• jredisx-cli: 命令行客户端
• jredisx-benchmark: 性能测试
• jredisx-check-aof: AOF 文件修复
• jredisx-check-rdb: RDB 文件检查
• jredisx-sentinel: 哨兵管理
================================================================================
第九章 风险与应对
================================================================================
R1: Java GC 导致延迟尖刺
应对: ZGC (STW < 1ms) + 堆外内存 + 对象池
R2: 多线程复杂命令原子性
应对: 同 Key 同线程无锁;跨 Key 排序加锁;事务限制同线程
R3: 内存使用高于 C 版 Redis
应对: 紧凑编码 + 对象池 + 堆外 + 大对象压缩
R4: 持久化无 fork
应对: 引用快照 + 双缓冲 + 多线程后台写入
R5: 协议兼容性差距
应对: 官方 redis.tcl 测试套件 + 持续集成 + 社区反馈
R6: 项目维护动力不足
应对: 公开里程碑 + 定期更新 + 社区互动 + 写技术博客
================================================================================
第十章 学习路径建议
================================================================================
本项目不仅是代码,更是 Java 技术体系的完整实践。
10.1 按模块学习
如果你想深入某个领域,可以单独研究对应模块:
• 网络编程 → protocol + server 模块(Netty, NIO, 协议设计)
• 并发编程 → router + command 模块(线程模型, 无锁队列, 并发安全)
• 数据结构 → storage 模块(跳表, 哈希表, 压缩列表, 时间轮)
• 系统编程 → persistence 模块(文件 IO, 内存映射, 快照)
• 分布式系统 → replication + ha 模块(复制, 共识, 分区容错)
10.2 推荐阅读顺序
1. 先通读本文档,理解整体架构
2. 从 Phase 1 开始,跟着里程碑一步步实现
3. 每完成一个 Phase,写一篇技术博客总结
4. 遇到不懂的,先查资料,再读源码,最后提问
5. 坚持 6 个月,你会对 Java 和系统编程有质的飞跃
10.3 输出建议
• 技术博客: 每个 Phase 写一篇,发布到掘金/CSDN/知乎
• 视频教程: 关键模块录屏讲解,发布到 B 站
• 开源贡献: 邀请他人参与,接受 PR,建立社区
• 演讲分享: 在技术大会上分享实现经验
================================================================================
第十一章 参考资源
================================================================================
【必读】
• 《Redis 设计与实现》--- 黄健宏(理解 Redis 内部机制)
• Redis 官方文档: https://redis.io/docs/
• RESP 协议规范: https://redis.io/docs/reference/protocol-spec/
【网络】
• Netty 官方文档: https://netty.io/wiki/
• 《Netty 实战》--- Norman Maurer
【并发】
• 《Java 并发编程实战》--- Brian Goetz
• JCTools: https://github.com/JCTools/JCTools
【算法】
• 跳表论文: "Skip Lists: A Probabilistic Alternative to Balanced Trees"
• 一致性 Hash: "Consistent Hashing and Random Trees"
【分布式】
• 《Designing Data-Intensive Applications》--- Martin Kleppmann
• Raft 论文: "In Search of an Understandable Consensus Algorithm"
================================================================================
文档结束
================================================================================