Java从零手写企业级内存数据库

ini 复制代码
================================================================================
                         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"

================================================================================
                              文档结束
================================================================================
相关推荐
萧瑟余晖8 小时前
JDK 20 新特性详解
java·开发语言
hdsoft_huge8 小时前
SpringBoot系列17:日志体系Logback配置,日志分割、脱敏、异常堆栈收集规范(生产级落地)
java·spring boot·logback
吃饱了得干活8 小时前
扫码登录如何实现?从原理到实战
java·spring boot·redis
JRedisX8 小时前
JRedisX 项目骨架搭建指南 从零开始 — 手把手教你搭
java
zx1154508 小时前
Java 反射 (SpringBoot篇)
java·架构
2301_794461578 小时前
Activiti/BPMN 2.0 的 4 种网关
java·服务器·开发语言
她的男孩8 小时前
低代码只能做单表 CRUD?我们一行代码没写,搭了个完整进销存
java·后端·架构
147API8 小时前
Claude Tag 进入 Slack 后,团队智能体需要哪些任务与审计字段
java·开发语言·数据库
dyonggan9 小时前
IDEA从零搭建SpringCloud Alibaba完整工程
java·spring cloud·intellij-idea