
复制代码================================================================================
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
! 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()
int arity()
String[] flags() default {}
} 示例命令: @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
protected long expireTime
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 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]
}
} 扩容触发: 负载因子 > 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
} // 随机层数: 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 // 两端插入: O(1)(如果头/尾节点未满)
// 中间插入: 找到对应节点,ZipList 插入
// 节点分裂: ZipList 超过 fill 时分裂为两个节点
} (5) 整数集合 (IntSet) — 小 Set 优化 public class IntSet {
byte[] contents
int length
int encoding // 查找: 二分查找 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 yes3.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
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 // Replication
private String replicaof
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
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 > 50KPhase 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 > 150KPhase 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 扩缩容测试
• 目标: 自动故障转移 < 30sPhase 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 + AssertJ7.2 集成测试
• 启动完整服务器,Jedis/Lettuce 客户端连接
• 命令兼容性测试(覆盖所有命令)
• 持久化一致性: 随机写入 → 重启 → 数据校验
• 复制一致性: 主从数据对比工具7.3 性能测试
• 工具: redis-benchmark, memtier_benchmark, JMH
• 指标: QPS, P50/P99/P999 延迟, 内存占用, CPU
• 场景: String GET/SET, Hash HGETALL, ZSet ZRANGE, Pipeline7.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
appendonly yes
appendfsync everysec
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
aof-use-rdb-preamble yes
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 150008.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 官方文档:
• RESP 协议规范: 【网络】
• Netty 官方文档:
• 《Netty 实战》— Norman Maurer【并发】
• 《Java 并发编程实战》— Brian Goetz
• 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"================================================================================
文档结束
================================================================================