Redis 源码硬核解析系列专题 - 第六篇:内存管理与持久化机制

第六篇:内存管理与持久化机制

1. 引言

Redis作为一个内存数据库,其内存管理和持久化机制直接影响性能和数据可靠性。Redis通过自定义内存分配器优化内存使用,同时提供RDB和AOF两种持久化方式保证数据不丢失。本篇将深入剖析Redis的内存管理(zmalloc.c)以及RDB(rdb.c)和AOF(aof.c)的实现细节。


2. 内存管理

2.1 Redis的内存分配器

Redis默认使用jemalloc(可在deps/中找到),也支持tcmalloc或标准libc。自定义封装在zmalloc.c中。

代码片段zmalloc.h):

1
2
3
4
void *zmalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
size_t zmalloc_size(void *ptr);

硬核解析

  • zmalloc():调用jemalloc分配内存,记录使用量。
  • zmalloc_size():返回实际分配块大小,用于内存统计。
  • 优势:减少碎片,提升分配效率。

代码片段zmalloc.c):

1
2
3
4
5
6
7
void *zmalloc(size_t size) {
    void *ptr = je_malloc(size + PREFIX_SIZE);
    if (!ptr) zmalloc_oom_handler(size);
    *((size_t*)ptr) = size; // 记录大小
    update_zmalloc_stat_alloc(size + PREFIX_SIZE);
    return (char*)ptr + PREFIX_SIZE;
}

硬核解析

  • PREFIX_SIZE:额外空间存储元数据(通常8字节)。
  • update_zmalloc_stat_alloc():更新全局内存统计。

3. 持久化机制

Redis提供两种持久化方式:RDB(快照)和AOF(日志)。

3.1 RDB持久化(rdb.c

RDB通过生成内存数据的快照保存到磁盘。

代码片段rdbSave()):

1
2
3
4
5
6
int rdbSave(char *filename, rdbSaveInfo *rsi) {
    rio rdb;
    rioInitWithFile(&rdb, fopen(filename, "w"));
    rdbSaveRio(&rdb, rsi);
    return C_OK;
}

硬核解析

  • rio:Redis的I/O抽象层,支持缓冲写。
  • 格式:RDB文件包含版本号、数据库键值对和校验和。

后台保存rdbSaveBackground()):

1
2
3
4
5
6
7
8
int rdbSaveBackground(char *filename, rdbSaveInfo *rsi) {
    pid_t childpid = fork();
    if (childpid == 0) {
        rdbSave(filename, rsi);
        exit(0);
    }
    return C_OK;
}

硬核解析

  • fork():创建子进程,避免阻塞主线程。
  • COW(Copy-On-Write):子进程共享内存,快照时仅复制修改页面。

Mermaid RDB流程

graph TD
    A["rdbSaveBackground()"] --> B["fork()"]
    B --> C{"子进程?"}
    C -->|Yes| D["rdbSave()"]
    D --> E["写RDB文件"]
    E --> F["exit(0)"]
    C -->|No| G["返回C_OK"]
3.2 AOF持久化(aof.c

AOF记录每次写操作,类似日志追加。

代码片段feedAppendOnlyFile()):

1
2
3
4
5
void feedAppendOnlyFile(client *c, int dictid) {
    sds buf = catClientCommandString(c);
    if (server.aof_state == AOF_ON)
        rioWrite(&server.aof_buf, buf, sdslen(buf));
}

硬核解析

  • catClientCommandString():将命令转为字符串(如SET key value)。
  • rioWrite():追加到缓冲区,定期刷盘。

AOF重写rewriteAppendOnlyFileBackground()):

1
2
3
4
5
6
7
8
int rewriteAppendOnlyFileBackground(void) {
    pid_t childpid = fork();
    if (childpid == 0) {
        rewriteAppendOnlyFile(tempfile);
        exit(0);
    }
    return C_OK;
}

硬核解析

  • 重写目的:压缩AOF文件,合并重复操作。
  • 后台执行:fork子进程,主线程继续服务。

Mermaid AOF重写流程

graph TD
    A["rewriteAppendOnlyFileBackground()"] --> B["fork()"]
    B --> C{"子进程?"}
    C -->|Yes| D["rewriteAppendOnlyFile()"]
    D --> E["生成临时AOF"]
    E --> F["替换原文件"]
    F --> G["exit(0)"]
    C -->|No| H["返回C_OK"]

4. 内存与持久化的优化

  • 内存
    • jemalloc减少碎片。
    • LRU淘汰(evictionPoolPopulate())回收内存。
  • 持久化
    • RDB快照适合快速恢复。
    • AOF日志保证数据完整性,结合重写控制文件大小。

5. 总结与调试建议

  • 收获:理解Redis内存分配与持久化实现。
  • 调试技巧
    • INFO MEMORY查看内存统计。
    • strace跟踪fork和文件写操作。
  • 下一步:探索主从复制与集群。
updatedupdated2025-03-312025-03-31