appendfsync always直接阻塞主线程,因其每次写命令后必须同步等待fsync()完成,主线程停等磁盘落盘确认;SSD单次耗时0.2–1ms,高并发下请求排队导致P99延迟飙升至20–50ms。

appendfsync always直接阻塞主线程
因为每次写命令执行完,Redis必须同步调用 fsync() 等待磁盘返回确认,这个系统调用是阻塞的——主线程卡住不动,直到硬件完成落盘。它不是“慢”,而是“停等”。哪怕SSD单次 fsync() 只耗时0.5ms,1万QPS下,请求就在主线程排队,P99延迟轻松飙到20–50ms。
常见现象包括:redis-cli --latency 显示毛刺明显、iostat -x 1 中 %util == 100% 且 w/s 卡在磁盘IOPS上限(如机械盘卡在200)、iotop 显示 redis-server 的WRITE列长期稳定在几百KB/s而非MB/s量级。
吞吐量被钉死在磁盘IOPS天花板上
Redis QPS不再由CPU或网络决定,而由磁盘每秒能完成多少次随机写+fsync来硬性封顶。机械盘通常只有100–200 IOPS,NVMe SSD标称几万,但实际受文件系统(如ext4未开 data=writeback)、IO调度器、队列深度影响,往往打6–8折。
验证是否真被磁盘卡住,要用工具交叉看三组指标:
-
iostat -d -x 2:盯w/s(是否逼近磁盘基准值)、await(是否 >10ms)、%util(是否持续100%) -
fio --name=randwrite --ioengine=libaio --rw=randwrite --bs=4k --size=1G --runtime=60:单独压测拿到真实磁盘IOPS基线 -
INFO persistence:若aof_delayed_fsync持续 >0,说明fsync已开始积压
everysec模式为何不卡主线程
它把写入路径拆开了:主线程只负责将命令追加到内存缓冲区 aof_buf(几乎零开销),后台线程每秒统一调用一次 fsync() 批量刷盘。这样主线程和磁盘I/O解耦,QPS不再直接受限于磁盘延迟。
但注意两个关键点:
- 后台线程一旦被阻塞(比如AOF重写 + fsync抢同一块磁盘带宽),主线程仍会感知:发现上一次
fsync()没完成,新来的write()就会被挂起等待 - 此时
INFO persistence里aof_pending_bio_fsync不为0,日志出现Asynchronous AOF fsync is taking too long - 必须配合
no-appendfsync-on-rewrite yes,否则重写期间双重I/O压力会让性能雪上加霜
aof_buf满才是更隐蔽的卡点
很多人以为卡在 fsync(),其实真正让 SET 延迟突然飙升的,往往是内存缓冲区 aof_buf 写满了。默认只有8KB,在高吞吐场景(如每秒数万条小命令)下极易打满。
缓冲区满后,主线程必须同步等待后台消费(无论是fsync线程还是重写子进程),此时即使选 everysec 也会卡。典型信号是:INFO persistence 中 aof_buffer_length 长期 >1MB,或监控看到 aof_delayed_fsync 持续增长。
这个缓冲区大小无法运行时调整,必须改源码重编译。实践中更推荐用可配参数间接缓解:
-
auto-aof-rewrite-percentage 50:比默认100更早触发重写,释放空间并清空积压 -
aof-rewrite-min-size 256mb:避免小文件频繁重写,减少子进程调度抖动 - 一定配上
no-appendfsync-on-rewrite yes,尤其在everysec下
真正要命的不是配置选错,而是没意识到:AOF写入流程有三层缓冲(命令协议缓冲、内核页缓存、磁盘控制器队列),每一层都可能成为瓶颈。盯着 INFO persistence 和 iostat 交叉看,比盲目调参管用得多。











