Redis哈希表渐进式Rehash期间的读写机制深度剖析



1. 重写的核心目的


AOF文件记录了所有写命令,随着服务运行时间增长,文件体积会不断膨胀,带来三个问题:


| 问题 | 影响 |

|------|------|

|磁盘空间占用| 日志文件可能比实际数据大数十倍 |

|数据恢复缓慢| Redis重启时需重放海量命令,耗时从分钟级到小时级 |

|网络传输压力| 主从复制时传输大文件占用带宽 |


举例:对同一Key执行100万次INCR,AOF会记录100万条命令。重写后只需一条SET counter 1000000


2. 重写原理:读内存而非读文件


AOF重写的核心思想是读取当前内存数据,生成最小命令集,而不是读取旧的AOF文件进行压缩。


读取内存生成写入

旧AOF文件
100万条命令

当前数据库状态

最小命令集

新AOF文件
1条命令


3. 重写完整流程


新AOF文件重写缓冲区Fork子进程主进程新AOF文件重写缓冲区Fork子进程主进程手动/自动触发BGREWRITEAOF子进程获得内存快照子进程重写完成1. 检查是否已在重写2. fork子进程3. 继续处理客户端请求4. 写命令同时写入aof_buf和重写缓冲区5. 遍历所有DB6. 每个Key生成最小命令7. 写入临时AOF文件8. 通知主进程9. 重写缓冲区内容追加到新AOF10. 原子重命名替换旧AOF11. 清空重写缓冲区


4. 重写触发方式


手动触发

# 手动执行重写 BGREWRITEAOF


自动触发(满足两个条件):

# 条件1: 文件大小超过设定值 auto-aof-rewrite-min-size 64mb # 条件2: 文件比上次重写后增长超过设定百分比 auto-aof-rewrite-percentage 100


// 自动重写检查逻辑(简化) void check_rewrite(void) { // 获取当前AOF大小 long long aof_size = server.aof_current_size; // 条件1: 文件大于最小值 if (aof_size < server.aof_rewrite_min_size) return; // 条件2: 增长率超过百分比 if (aof_size < server.aof_rewrite_base_size * (100 + server.aof_rewrite_percentage) / 100) return; // 触发重写 rewriteAppendOnlyFileBackground(); }


5. 重写期间的写操作处理


重写期间,Redis采用双写策略保证数据不丢失:


写操作

是否正在重写?

写入aof_buf

定期刷盘到AOF文件

写入aof_buf

写入aof_rewrite_buf_blocks

重写完成后追加到新AOF


关键点

  • aof_buf:常规缓冲区,保证正常AOF写入
  • aof_rewrite_buf_blocks:专门用于重写的缓冲区,存储重写期间的所有增量命令
  • 重写完成后,先追加增量命令,再原子替换文件


6. 重写过程中的内存管理


// 重写缓冲区结构 typedef struct aofrwblock { unsigned long used; // 已使用字节数 unsigned long free; // 剩余字节数 char buf[1024 * 10]; // 10KB块 } aofrwblock; // 增量命令以链表形式存储 list *aof_rewrite_buf_blocks; // 多个内存块


内存控制:当重写缓冲区增长过快(超过内存限制),Redis会主动限流:


// 如果重写缓冲区超过内存限制,降低写入速度 if (server.aof_rewrite_buf_blocks_mem > server.aof_rewrite_buf_limit) { // 主动延迟响应,让子进程有时间完成重写 usleep(1000); }


7. 磁盘空间安全策略


重写期间同时存在旧AOF新AOF(临时)两个文件,需确保磁盘空间充足:


# 重写过程中的文件状态 -rw-r--r-- 1 redis redis 10G appendonly.aof # 旧文件 -rw-r--r-- 1 redis redis 2G temp-rewrite.aof # 临时新文件


安全建议

# 配置重写时停止fsync,减少磁盘IO竞争 no-appendfsync-on-rewrite yes # 监控磁盘空间,建议预留30%以上空闲


8. 重写性能影响


| 阶段 | 影响 | 持续时间 |

|------|------|---------|

|fork()| 主线程短暂阻塞(数十ms~数百ms) | 与内存大小成正比 |

|子进程重写| CPU密集型,内存消耗(Copy-on-Write) | 分钟级 |

|文件替换| 原子操作,几乎无影响 | 毫秒级 |


优化建议

  • 业务低峰期(凌晨)手动触发重写
  • 控制单个Redis实例内存 ≤ 16GB(减少fork耗时)
  • 监控latest_fork_usec指标


9. 混合持久化对重写的影响


Redis 4.0+的混合持久化改变了重写输出格式:


混合持久化重写

遍历内存

生成RDB二进制

后续增量命令

RDB + AOF混合格式

纯AOF重写

遍历内存

生成RESP命令

纯文本AOF


配置

# 开启混合持久化后,重写生成混合格式文件 aof-use-rdb-preamble yes


优势:文件更小、恢复更快


---


总结


| 维度 | 说明 |

|------|------|

|触发方式| 手动BGREWRITEAOF或自动(大小+增长率双条件) |

|核心原理| fork子进程 → 读取内存数据 → 生成最小命令集 |

|增量处理| 重写期间的双写缓冲(aof_buf + 重写缓冲区) |

|文件安全| 先写临时文件,原子替换,防止损坏 |

|风险控制| 内存限流、磁盘空间预留、低峰期执行 |

|最佳实践| 配合混合持久化、设置合理阈值、监控重写耗时 |