最近发现一个问题,redis在高流量写入的情况下,偶发性出现客户端延迟升高,经过排查发现redis AOF重写 fork 子进程导致。为什么要进行AOF重写,以及如何避免AOF重写呢?本文做个介绍。
AOF是redis防止数据丢失的日志备份策略,总共有三种方式
说到AOF,其实很多人都会拿它跟Rdb去做比较,Rdb是以二进制的方式存储到磁盘上。体积更小,当出现服务重启或者宕机,相对于AOF恢复速度更快。
另外一点,RDB和AOF对客户端的写入性能影响,一般情况下,AOF的写入性能是比不上RDB的,因为AOF多了一个写入操作,但是随着写入数据量越来越大,这个差距会越来越小。看完本文后,你应该能够找到答案。具体可以参考redis官网https://redis.io/topics/persistence
,这里不过多赘述。
很多同学对redis的写AOF文件和AOF重写傻傻分不清,无论是发生时机或者操作对象其实是没有任何关系的。
写AOF文件发生在客户端请求redis server,这个时候就会产生一条AOF记录,这条记录何时写入磁盘跟自身设置的AOF策略控制相关,可以同步、也可以异步写入。
如果redis server接受的写请求越来越多,那么AOF文件会越来越大,为了防止AOF文件无限膨胀(打爆磁盘)以及不利于redis server 宕机后的恢复,所以要进行重写。其实说白了就是一个重复命令合并的过程。
对于上图几个关键点:
如下是源码所示:
//如果AOF功能启用、没有RDB子进程和AOF重写子进程在执行、AOF文件大小比例设定了阈值,以及AOF文件大小绝对值超出了阈值,进一步判断AOF文件大小比例是否超出阈值
if (server.aof_state == AOF_ON &&
server.rdb_child_pid == -1 &&
server.aof_child_pid == -1 &&
server.aof_rewrite_perc
&& server.aof_current_size > server.aof_rewrite_min_size)
{.....}
看到这里,再想想,为什么redis之所以添加各种条件限制AOF的发生?
尽可能减少CPU和IO消耗
上文中也说了,AOF主要耗时发生在fork一个子进程并且会阻塞主进程,这是为什么呢?
因为fork子进程时,子进程是会拷贝父进程的页表,即虚实映射关系,但是fork不会把所有的内存数据都copy到子进程,只会copy一部分有用的数据到子进程中。
所以fork在复制内存页的时候会大量的消耗CPU资源,如果复制的内存页越大,fork阻塞的时间就会越久。拷贝内存页完成,子进程与父进程指向相同的内存地址,这个时候就会放开主进程的阻塞,对外提供操作。每当有新的写命令,就会触发操作系统的COW写时复制机制,此时就会把这新的命令写到AOF日志缓冲区,等待数据重写完成后,重写的日志与缓冲区修改的数据进行合并,这样保证了父子进程之间的数据同步。
正常情况 fork 耗时应该是每 GB 消耗 20ms 左右,当然也可以用 info stats 命令查看 latest_fork_usec 指标, 获取最近一次 fork 操作耗时, 单位微秒。
总的来说,没有好不好,只有是否合适。软件系统的设计都是偏向于解决某个领域的问题,具体情况要看具体使用场景,比如可以考虑关闭AOF,当服务流量低峰时手动触发AOF。也可以从自身的业务出发尽可能减少写请求。