前言:

在之前的文章中介绍过Redis的持久化策略,和Redis的底层模型。这篇文章主要介绍Redis中存在的两大阻塞情况,Fork阻塞和AOF追加阻塞。


1. fork阻塞:CPU的阻塞

在Redis中,众多因素导致Redis单机内存不能过大。

  • 当面对请求暴增时,需要从库扩容,如果单机内存过大会导致扩容时间过长;
  • 当主机宕机后,切换主机需要重新挂载从库,Redis内存过大会导致挂载速度过慢;
  • 持久化过程中的fork操作

fork操作:

父进程通过fork操作可以创建子进程;子进程创建后,父子进程共享代码段,不共享进程的数据空间。但是子进程会获得父进程的数据空间副本。在操作系统层面,基本都采用写时复制,也就是父子进程视图修改数据空间之前,父子进程实际上可以共享数据空间;但是当父子进程中任何一个视图修改数据空间时,操作系统会为修改的那一部分(内存的一页)制作一个副本。

虽然fork时,子进程不会复制父进程的数据空间,但是会复制内存页表(页表相当于内存的索引、目录);父进程的数据空间越大,内存页表越大,fork时复制耗时也会越多。

在Redis中,无论RDB持久化的bgsave还是AOF重写的bgrewriteaof,都需要主进程fork子进程来进行操作。如果Redis内存过大,会导致fork操作时复制内存页表耗时过多;而在Redis主进程进行fork时,是完全阻塞的,这就意味着无法响应客户端的请求,会造成请求延迟过大。为了减轻fork操作带来的阻塞问题,除了控制Redis单机内存的大小外,还可以适度放宽AOF重写出发的条件。


2. AOF追加阻塞:硬盘的阻塞

在AOF中,如果AOF缓冲区的文件同步策略为everysec,则在主线程中,命令写入aof_buf后调用操作系统write操作,write完成后主线程返回;fsysnc同步文件操作由专门的文件同步线程每秒调用一次。

这种做法的问题在于,如果硬盘负载过高,那么fsysnc操作可能会超过1s;如果Redis主线程持续高速向aof_buf写入命令,硬盘的负载可能会越来越大,IO资源消耗会更快。如果此时Redis异常退出,会导致数据丢失可能远超过1s。

为此,Redis的处理策略是这样的:主线程每次进行AOF会对比上次fsync成功的时间;如果距上次不到2s,主线程直接返回;如果超过2s,则主线程阻塞直到fsync同步完成。因此,如果系统硬盘负载过大导致fsync速度太慢,会导致Redis主线程的阻塞;此外,使用everysec配置,AOF最多可能丢失2s的数据,而不是1s。

 

AOF追加阻塞问题定位的方法:

(1)监控info Persistence中的aof_delayed_fsync:当AOF追加阻塞发生时(即主线程等待fsync而阻塞),该指标累加。

(2)AOF阻塞时的Redis日志:

Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.

(3)如果AOF追加阻塞频繁发生,说明系统的硬盘负载太大;可以考虑更换IO速度更快的硬盘,或者通过IO监控分析工具对系统的IO负载进行分析,如iostat(系统级io)、iotop(io版的top)、pidstat等。