Redis AOF重写阻塞问题分析
某个业务线使用Redis
集群保存用户session
数据,数据量大约在4千万-5千万,每天发生3-4次AOF
重写,每次时间持续30-40
秒,AOF
重写期间出现Redis
主进程阻塞,应用端响应超时的问题。
环境:Redis 2.8
,一主一从。
AOF
重写是AOF
持久化的一个机制,用来压缩AOF
文件,通过fork
一个子进程,重新写一个新的AOF
文件,该次重写不是读取旧的AOF
文件进行复制,而是读取内存中的Redis
数据库,重写一份AOF
文件,有点类似于RDB
的快照方式。
在子进程进行AOF
重启期间,Redis
主进程执行的命令会被保存在AOF
重写缓冲区里面,这个缓冲区在服务器创建子进程之后开始使用,当Redis
执行完一个写命令之后,它会同时将这个写命令发送给 AOF
缓冲区和AOF
重写缓冲区。如下图:
当子进程完成AOF
重写工作之后,它会向父进程发送一个信号,父进程在接收到该信号之后,会调用一个信号处理函数,并执行以下工作:
AOF
重写缓冲区中的所有内容写入到新的AOF
文件中,保证新 AOF
文件保存的数据库状态和服务器当前状态一致。AOF
文件进行改名,原子地覆盖现有AOF
文件,完成新旧文件的替换在整个AOF
后台重写过程中,只有信号处理函数执行时会对 Redis
主进程造成阻塞,在其他时候,AOF
后台重写都不会阻塞主进程,如下图所示:
这是当时的Redis
配置:
127.0.0.1:6379> config get *append*
1) "no-appendfsync-on-rewrite"
2) "no"
3) "appendonly"
4) "yes"
5) "appendfsync"
6) "everysec"
从配置看,原因理论上就很清楚了:我们的这个Redis
实例使用AOF
进行持久化(appendonly
),appendfsync
策略采用的是everysec
刷盘。但是AOF
随着时间推移,文件会越来越大,因此,Redis
还有一个rewrite
策略,实现AOF
文件的减肥,但是结果是幂等的。我们no-appendfsync-on-rewrite
的策略是 no
,这就会导致在进行rewrite
操作时,appendfsync
会被阻塞。如果当前AOF
文件很大,那么相应的rewrite
时间会变长,appendfsync
被阻塞的时间也会更长。
这不是什么新问题,很多开启AOF
的业务场景都会遇到这个问题。解决的办法有这么几个:
no-appendfsync-on-rewrite
设置为yes
. 这样可以避免与appendfsync
争用文件句柄,但是在rewrite
期间的AOF
有丢失的风险。Redis
实例添加slave
节点,当前节点设置为master
, 然后master
节点关闭AOF
,slave
节点开启AOF
。这样的方式的风险是如果master
挂掉,尚没有同步到slave
的数据会丢失。我们采取了折中的方式:
master
节点设置将no-appendfsync-on-rewrite
设置为yes
(表示在日志重写时,不进行命令追加操作,而只是将命令放在重写缓冲区里,避免与命令的追加造成磁盘IO
上的冲突),同时auto-aof-rewrite-percentage
参数设置为0关闭主动重写AOF
文件越来越大,我们在缓存云平台上配置在凌晨低峰期定时手动执行bgrewriteaof
命令完成每日一次的AOF
重写IO
使用率高影响重写功能,我们还添加了硬盘空间报警和IO
使用率报警保障重写的正常进行那么如果rewrite
的时候对新的写操作不进行fsync
,那么新的AOF
文件里面是否会丢失这个写操作呢?
答案是不会的,Redis
会将新的写操作放在重写缓存区中,等待rewrite
操作完成的时候,将新操作直接追加到新的AOF
中。