Redis异常持久化序列引发客户端周期性冻结问题求助
咱们先从你提供的日志里的异常细节拆解起——正常情况下,Redis父进程启动后台持久化子进程后,子进程完成磁盘写入会给父进程发SIGCHLD信号,父进程收到信号后会输出Background saving terminated with success并回收子进程资源。但你的异常序列里:
[5485] 15 May 12:12:59 * Background saving started by pid 3352
[3352] 15 May 12:13:10 * DB saved on disk
[3553] 15 May 12:14:21 * DB saved on disk
[3647] 15 May 12:15:42 * DB saved on disk
[3751] 15 May 12:16:53 * DB saved on disk
父进程完全没响应子进程3352的完成信号,还在后续不断触发新的持久化子进程(3553、3647、3751),却没有记录这些子进程的启动日志,这直接指向两个核心问题:
一、异常行为的根本原因
父进程的SIGCHLD信号处理失效
Redis依赖SIGCHLD信号感知子进程的退出状态。如果父进程出现以下情况,就会丢失信号:- 父进程被阻塞在某个慢系统调用(比如磁盘IO、网络调用),无法及时处理信号;
- 系统信号队列溢出,导致子进程发送的
SIGCHLD被丢弃; - Redis旧版本的信号处理存在bug,无法正确捕获信号;
- 父进程的
waitpid调用异常,无法回收子进程,导致子进程变成僵尸进程,父进程误以为持久化还在进行。
频繁触发持久化导致多子进程并发执行
因为父进程没感知到前一个子进程的完成,当10000 changes in 60 seconds的触发条件再次满足时,Redis会再次fork新的子进程执行持久化。这些子进程连续占用磁盘IO资源,进一步加剧系统负载。
二、为什么会导致客户端冻结?
客户端冻结的直接原因是Redis父进程无法及时处理请求,具体场景包括:
- 磁盘IO资源耗尽:多个RDB持久化子进程同时写入磁盘,会把磁盘IO占满。Redis父进程如果需要写入AOF日志(若开启)或读取磁盘数据,会被阻塞在IO操作上,无法响应客户端命令;
- 父进程阻塞或资源不足:父进程因为信号处理异常、僵尸进程回收失败,会处于资源等待状态,无法处理客户端的请求队列;
- fork子进程的开销:频繁fork子进程会消耗大量CPU和内存资源(写时复制机制会导致父进程的内存页被大量复制),进一步拖慢父进程的处理速度。
三、紧急排查与解决步骤
临时恢复(生产环境优先)
- 手动执行
BGSAVE命令,观察父进程是否能正常输出完成日志。如果正常,说明信号处理可能只是临时异常; - 如果磁盘IO占用极高,可临时调整
save配置(比如注释掉save 60 10000),减少持久化触发频率,先缓解客户端冻结问题; - 若上述操作无效,谨慎重启Redis服务(确保有最新的RDB/AOF备份,避免数据丢失),重启后父进程的信号处理会恢复正常。
根本原因排查
检查系统资源与状态
- 用
iostat -x 1查看磁盘IO使用率,如果%util接近100%,说明磁盘是瓶颈; - 用
ps aux | grep defunct检查是否有Redis僵尸子进程,若有则确认是父进程回收失败; - 用
strace -p 5485跟踪父进程的系统调用,看是否阻塞在waitpid、write等操作上; - 检查系统的
ulimit -n(文件描述符限制)是否足够,避免因描述符耗尽导致日志无法输出或信号无法处理。
- 用
检查Redis配置与版本
- 查看
stop-writes-on-bgsave-error配置,如果设为yes,当持久化出错时会停止写操作,但你的情况是还在触发持久化,可能设为no; - 检查Redis版本,若使用的是4.0之前的旧版本,建议升级到最新稳定版(如7.x),旧版本存在一些信号处理和持久化的bug;
- 调整
save策略,比如将save 60 10000改为save 300 1000,减少触发频率,降低fork开销。
- 查看
优化持久化策略
- 若开启了AOF,将
appendfsync设为everysec(默认值),平衡数据安全性和IO性能; - 考虑将RDB持久化目录迁移到SSD磁盘,提升写入速度;
- 开启
rdbcompression(默认开启),减少RDB文件大小,降低磁盘IO耗时。
- 若开启了AOF,将
内容的提问来源于stack exchange,提问作者Tom Desp

