Redis RDB 快照性能优化:fork 阻塞主线程的成因、影响与调优

来源:互联网 时间:2026-11-02

每天凌晨三点二十,接口响应时间从十几毫秒涨到一点二秒,持续三四秒后自己恢复。监控上是一条尖刺,白天完全没有。这个时间点和备份任务撞在一起,但备份脚本只读不写。

redis.conf里默认有save规则,满足条件就触发一次bgsave。bgsave不阻塞主线程只对了一半,真正干活的是子进程,可fork这一步必须由主线程发起,发起期间它不处理任何命令。

fork要复制父进程的页表,页表就是记录内存地址与物理页对应关系的那张表,内存占用越大表越长,复制耗时越久。物理内存本身没有复制,复制的只是这张表。实例四十个G的时候,这一步通常在两百到五百毫秒之间,机器老一点、内存碎片多一点,会超过一秒。

这个数有现成的指标,你在INFO stats里找latest_fork_usec,单位是微秒。把它和接口变慢的时间点对上,两边的秒数能咬合,结论就不用再争。同一段输出里rdb_last_bgsave_status是ok,说明快照本身没失败;失败的话它会变成err,等下次满足save条件再试。

fork之后还有一层开销。父子进程共享内存页,任何一方要写,内核就把那一页复制一份出来,这叫写时复制。快照期间写入越频繁,复制的页越多,内存占用可能接近翻倍。

透明大页会把这一层放大。内核默认以两个M为单位管理大页,一个页被写就要复制两个M,而原来只需要复制四K。Redis官方在文档里明确建议关闭THP,理由就是fork和写时复制的延迟会变差。检查方式是cat /sys/kernel/mm/transparent_hugepage/enabled,方括号落在always上就是开着。临时关掉用echo never写进同一个文件,重启失效,要持久化就改grub参数或者用tuned的profile。这一条和内存分配器也有关系,jemalloc对大页的利用方式和glibc不同。

缓解的手段按代价排。先把save规则放宽,改写成save 86400 1,一天最多落一次盘,尖刺次数跟着降。再把RDB挪到从库上做,主库只留AOF,主库的fork就没了。从库的内存配置要和主库一样,否则fork时间反而更长。

AOF重写同样会fork,bgrewriteaof一样有这一步。只关RDB不改AOF的触发条件,尖刺只是换了个时间点出现。auto-aof-rewrite-percentage默认是一百,文件长到上一次重写后的一倍就触发,写入量大的库一天能触发好几次。

四十个G的实例,fork一次两百多毫秒,加上写时复制的抖动,接口侧感知到一到两秒是正常的。内存再往上走,这个数会线性变差,一百个G的实例fork超过一秒很常见。

主库的save规则删到只剩一条,RDB交给从库,THP在grub里关掉。三点二十那条尖刺变成了一个平缓的小包,latest_fork_usec稳定在三十万以下,接口的P99回到四十毫秒以内。

相关文章

A5创业网 版权所有

返回顶部