Redis 内核

内存键值存储的数据结构服务器——ae 事件循环、SDS 动态字符串、RDB/AOF 持久化、主从复制。

概念

Redis 是一个内存数据结构服务器,用纯 C 编写 (约 12 万行)。它的”内核”是一个单线程事件循环处理所有客户端请求。Redis 的核心理念是:数据结构原生存储在内存中,网络 I/O 通过 epoll 多路复用,持久化通过 fork + Copy-on-Write 实现。Redis 是”如何用最少的代码构建一个高性能网络服务器”的范本。

核心组件

组件职责关键机制
ae 事件循环epoll/kqueue/select 驱动的异步 I/O文件事件 + 时间事件
SDSC 字符串的替代: O(1) 长度, 二进制安全, 预分配兼容 C 字符串 API
Dict哈希表: rehash 渐进式两个哈希表 rolling rehash
Skiplist有序集合 (ZSET) 的底层实现概率平衡替代红黑树
Ziplist/Listpack紧凑序列化存储: 连续内存, 省指针小型 list/hash/zset
RDB快照持久化: fork() → 子进程写全量数据Copy-on-Write
AOF增量持久化: 每条写命令追加到日志类似 WAL
复制/哨兵/集群高可用 + 水平扩展PSYNC, Raft

ae 事件循环

// ae 事件循环核心 (ae.c)
typedef struct aeEventLoop {
    aeFileEvent *events;   // 注册的 fd 及其回调
    aeFiredEvent *fired;   // epoll_wait 返回的就绪事件
    aeTimeEvent *timeEventHead;  // 时间事件链表 (最近到期排最前)
};
 
void aeMain(aeEventLoop *eventLoop) {
    while (!eventLoop->stop) {
        // 计算离最近时间事件的毫秒数, 作为 epoll_wait 超时
        tv = shortestTimeEventTimeout(eventLoop);
 
        // 阻塞等待 I/O 事件 (epoll_wait)
        numevents = aeApiPoll(eventLoop, tv);
 
        // 处理所有就绪的文件事件
        for (i = 0; i < numevents; i++) {
            fileEvent->rfileProc(...); // 读事件回调
            fileEvent->wfileProc(...); // 写事件回调
        }
 
        // 处理所有到期的时间事件
        processTimeEvents(eventLoop);
    }
}

核心数据结构

// SDS -- Simple Dynamic String
struct sdshdr {
    int len;     // 已使用长度 (O(1) strlen)
    int free;    // 剩余空间 (减少 realloc)
    char buf[];  // 柔性数组, 实际数据
};
 
// Dict -- 渐近式 rehash 哈希表
struct dict {
    dictEntry **table[2];   // table[0] 是当前, table[1] 是 rehash 目标
    long rehashidx;         // -1 表示未在 rehash
    // ...
};
// 每次 CRUD 操作附带移动 1 个桶, 分步完成 rehash
 
// Skiplist -- 有序集合的底层
struct zskiplistNode {
    sds ele;                      // 元素值
    double score;                 // 排序分数
    struct zskiplistLevel {
        struct zskiplistNode *forward;  // 前向指针
        unsigned int span;              // 跨度 (快速 rank 查询)
    } level[];                    // 多层 index (随机层数, 概率 1/4)
};

RDB 持久化

主进程 (Redis Server):
    fork()
      |
      +-- 子进程:
      |       遍历所有 DB (共享内存 Copy-on-Write)
      |       将所有 key-value 序列化写入 dump.rdb
      |       退出
      |
      +-- 父进程:
              继续处理客户端请求
              如果父进程修改了数据 → 触发 Copy-on-Write → 子进程看到旧版本
              (fork 时刻的快照, 不是实时数据)

AOF 持久化

每条成功执行的写命令追加到 AOF 文件:
    SET key "hello"  →  AOF: *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nhello\r\n

AOF 重写 (BGREWRITEAOF):
    在子进程中, 基于当前内存数据生成最小命令集合
    例如: 一条 RPUSH list A → RPUSH list B → RPUSH list C
    重写为: RPUSH list A B C   (合并为一条)

主从复制

1. 从库连接主库, 发送 PSYNC <replication_id> <offset>
2. 主库检测是否可以部分同步 (offset 在复制缓冲区中)
   部分同步: 发送 offset 之后的增量命令
   全量同步: fork → 生成 RDB → 发送 RDB 给从库 → 发送缓冲区增量命令
3. 从库加载 RDB → 执行增量命令 → 进入实时同步
4. 主库每条写命令同时发送给所有已连接的从库