Redis 在执行 BGSAVE 或 BGREWRITEAOF 时可能记录 Can't save in background: fork: Cannot allocate memory。这不等于 RDB 文件已损坏,通常表示操作系统没有允许 Redis 创建持久化子进程。
Redis 的后台持久化依赖 fork() 和写时复制。主进程仍可服务请求,但写入活动会让父子进程各自持有被修改页面,因此需要为持久化阶段预留内存和磁盘吞吐余量。

确认失败发生在fork还是写盘阶段
先读取 Redis 自身状态和服务日志:
redis-cli INFO persistence
redis-cli INFO memory
sudo journalctl -u redis-server --since '30 minutes ago' --no-pager
重点查看 rdb_bgsave_in_progress、rdb_last_bgsave_status、rdb_last_cow_size、aof_rewrite_in_progress 和 aof_last_bgrewrite_status。不同版本可能没有全部字段,但日志中的 fork 与 Cannot allocate memory 足以说明错误发生在创建后台进程时。
再确认系统是否出现 OOM 或容器内存限制:
free -h
sudo dmesg -T | grep -Ei 'oom|out of memory|killed process' | tail -30
systemctl show redis-server -p MemoryMax -p MemoryCurrent
不要只看 used_memory 就下结论。 fork 是否成功还受宿主机可提交内存、页表、容器 cgroup 限制、交换空间和并发写入量影响。
理解写时复制为何仍会消耗额外内存
Redis 官方文档说明,RDB 快照会 fork 子进程,由子进程把数据写入临时文件,完成后原子替换旧文件。父子进程最初共享内存页,但在快照期间发生写入时,被修改的页会产生副本。
因此,数据集为 8GB 并不意味着额外内存永远接近零。写入速率、内存碎片、大页和快照持续时间都会影响写时复制开销。可记录快照后的 rdb_last_cow_size,把它作为本机容量规划的历史依据,而不是照搬另一台服务器的比例。
redis-cli INFO memory | grep -E 'used_memory:|used_memory_rss:|mem_fragmentation_ratio'
redis-cli INFO persistence | grep -E 'rdb_last_cow_size|aof_last_cow_size'
生产容量必须同时容纳常态数据、进程开销和持久化峰值;把 maxmemory 设置到容器上限附近会让后台保存没有安全余量。
检查容器与systemd的真实限制
Redis 运行在 Docker 中时,宿主机空闲内存不代表容器可以使用。检查容器限制和实时用量:
docker inspect redis --format '{{.HostConfig.Memory}} {{.HostConfig.MemorySwap}}'
docker stats redis --no-stream
若由 systemd 管理,确认 MemoryMax、MemoryHigh 等限制。Kubernetes 环境则应查看 Pod 的 memory limit、重启原因和节点压力。调整限制前,先核实节点总容量与其他工作负载,避免把单个实例的故障变成整机 OOM。
合理方案包括降低 Redis maxmemory、减少同机高峰任务、为实例增加内存,或在业务低峰执行重写。提升限制之前必须确认新的上限确实有物理或交换空间支撑,而不是把保护阈值简单删除。
谨慎评估Linux内存overcommit
在 Linux 上查看当前策略:
sysctl vm.overcommit_memory
grep -E 'CommitLimit|Committed_AS|MemAvailable|SwapFree' /proc/meminfo
某些 Redis 部署建议使用 vm.overcommit_memory=1,以免内核在 fork 时根据提交估算提前拒绝。但这个参数改变的是整台主机的内存承诺策略,不会创造真实内存,也不会阻止随后发生 OOM。
如经过容量评估决定调整,可先临时设置并验证:
sudo sysctl -w vm.overcommit_memory=1
确认有效后再写入专用的 /etc/sysctl.d/ 配置文件。如果系统已经内存紧张,仅修改 overcommit 可能把立即拒绝 fork 变成稍后由 OOM killer 杀进程,风险反而更大。
需要按相同资源规格复现写入峰值时,可在 萤光云 建立隔离实例,或使用 LightNode 进行短时容量验证。测试必须使用脱敏数据,并与生产网络隔离。
重新执行持久化并观察峰值
释放足够余量或调整限制后,先确认没有正在运行的后台任务,再触发一次保存:
redis-cli INFO persistence
redis-cli BGSAVE
watch -n 1 'redis-cli INFO persistence | grep -E "rdb_bgsave_in_progress|rdb_last_bgsave_status|rdb_last_cow_size"'
如果使用 AOF,则观察 aof_rewrite_in_progress 与 aof_last_bgrewrite_status。同时监控系统内存、交换空间、磁盘时延和 Redis 延迟,避免只验证命令返回 Background saving started。
合格的验收标准是后台任务完整结束、最后状态为 ok、日志没有新的 fork/OOM 错误,并且峰值内存仍保留安全余量。
建立可持续的持久化容量策略
Redis 官方指出,RDB 与 AOF 重写都会使用 fork;数据集越大、写入越密集,后台任务持续期间的写时复制成本越高。容量规划应记录每次任务的 COW 大小、持续时间和磁盘吞吐。
不要为了绕过失败就永久关闭持久化。如果 Redis 只是可重建缓存,可以经过业务确认选择无持久化;如果承载不可丢失数据,应按恢复目标设计 RDB、AOF 与异机备份,并定期演练恢复。
修复完成后还要设置内存、fork失败和持久化状态告警,否则下次故障可能直到写入被阻止或重启恢复失败才被发现。
FAQ
增加swap一定能解决fork失败吗?
不一定。交换空间可能提高可提交内存,但会带来时延并受容器限制影响。应结合 CommitLimit、工作集和写入峰值评估,而不是把 swap 当作内存替代品。
可以把stop-writes-on-bgsave-error改成no吗?
它会让写入在快照失败后继续,并没有修复 fork 或持久化问题。只有业务明确接受数据保护降级、同时具备其他可靠持久化和监控手段时,才应短时评估这一选择。
BGSAVE成功就说明容量一定足够吗?
不一定。一次低写入时段的成功不能覆盖高峰场景。应观察 rdb_last_cow_size、任务时长和并发写入,连续验证并留出增长空间。
温馨提示
处理 Redis fork 内存失败时,先保护数据,再调容量,最后才考虑内核策略。 任何参数变更都要记录原值与回滚方法,生产环境不要在没有监控和备用连接的情况下反复触发持久化测试。


