Redis卡顿主因是fork()同步阻塞主线程,而非写盘慢;其耗时取决于内存页数、THP启用、swappiness>0等系统因素,latest_fork_usec超500ms即属危险。

因为 bgsave 本身不阻塞,但它的前置动作 fork() 是同步且不可绕过的系统调用,而 fork 耗时直接受 Redis 内存页数量、内核配置和硬件环境影响——卡顿不是出在写盘,是出在“复制页表”那一瞬间。
fork() 阻塞主线程是硬性事实,不是配置问题
Redis 执行 bgsave 时,必须先调用 fork() 创建子进程。这个系统调用会:复制父进程的页表项、标记所有内存页为 Copy-on-Write(COW)、初始化子进程地址空间。整个过程由内核同步完成,期间 Redis 主线程完全无法响应任何命令。
-
latest_fork_usec值超过 500000(即 500ms)就已属危险,线上常见值达 800000–1200000(0.8–1.2s) - 即使实例内存只有 6GB,若存在大量小 key 导致页表碎片深、或启用了 THP,
fork()仍可能卡住几百毫秒 - 别信“bgsave 是后台操作所以安全”——日志里刚打出 “Background saving started”,阻塞早已发生完毕
哪些配置和环境会让 fork 更慢
影响 fork() 耗时的关键因素基本都与操作系统层相关,和 Redis 自身逻辑无关:
- 透明大页(THP)开启:
/sys/kernel/mm/transparent_hugepage/enabled显示[always]或[madvise]时,fork 需处理 2MB 大页,耗时翻倍甚至更高 - 内存碎片率高:
mem_fragmentation_ratio> 1.5 说明 RSS 远大于used_memory,页表更庞大,fork 更吃力 -
vm.swappiness > 0:哪怕内存充足,fork 过程中内核也可能扫描 swap 区域,引发意外延迟 - 容器或 cgroup 限制内存:fork 时需遍历受限页表,开销随限制粒度变细而上升
为什么调大 save 间隔不一定管用
save 触发是“条件满足即 fork”,不是“定时器到期才 fork”。高频写入场景下,save 60 10000 可能每分钟都触发一次;而低频写入时,save 900 1 反而变成“只要改一个 key,15 分钟后必 fork”,节奏固定、毫无弹性。
- 写入密集服务(如计数器):每秒改几百次 key → 很快凑满 10000 次变更 → 高频 fork
- 配置中心类服务(几小时一改):
save 900 1会导致稳定地每 15 分钟 fork 一次,比写入密集还难预测 - 禁用自动 RDB 不能只删 conf 里的
save行:Redis 会 fallback 到默认save 900 1,必须显式设为save ""
真正有效的缓解手段
核心思路不是“让 bgsave 更快”,而是“减少 fork 的压力源”和“把 fork 移出主路径”:
- 关 THP:
echo never > /sys/kernel/mm/transparent_hugepage/enabled,然后重启 Redis - 单实例内存建议 ≤16GB;超大内存务必拆分,别指望靠调参硬扛
- 错峰执行:避免在业务高峰前后手动
BGSAVE,也别在bgrewriteaof运行时触发bgsave - 从节点生成 RDB:主节点禁用自动
save,改由从节点定时执行bgsave,彻底避开主进程 fork
最易被忽略的一点:卡顿未必来自你正在看的那个 Redis 实例——它可能是上游主节点 fork 卡住,导致 replication buffer 积压,下游从节点反复重试全量同步,连带把你这台也拖慢。











