报错

应用程序连接 redis 时总是抛出连接失败或超时之类的错误。通过观察在 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.” 频率极其频繁。

原因

因为启用了 AOF(appendonly yes),并且 appendfsync 参数为 everysec,造成的磁盘 I/O 负载高。

分析
  • 首先我们要知道,redis 是一个多路复用的单进程应用程序。多路,指的是多个网络地址,复用是指重复利用单个线程。当打开 AOF 持久化功能后, Redis 处理完每个事件后会调用 write(2) 将变化写入 kernel 的 buffer,如果此时 write(2) 被阻塞,Redis 就不能处理下一个事件。Linux 规定执行 write(2) 时,如果对同一个文件正在执行fdatasync(2)将 kernel buffer写入物理磁盘,或者有system wide sync在执行,write(2)会被Block住,整个Redis被Block住。
  • 如果系统IO 繁忙,比如有别的应用在写盘,或者Redis自己在AOF rewrite或RDB snapshot(虽然此时写入的是另一个临时文件,虽然各自都在连续写,但两个文件间的切换使得磁盘磁头的寻道时间加长),就可能导致 fdatasync(2) 迟迟未能完成从而 Block 住 write(2),Block 住整个 Redis。而,我们的生产主库和从库都同时开启了AOF已经RDB save的方式做持久化。
  • 为了更清晰的看到fdatasync(2)的执行时长,可以使用 “strace -p (pid of redis server) -T -e -f trace=fdatasync”,但会影响系统性能。Redis提供了一个自救的方式,当发现文件有在执行 fdatasync(2) 时,就先不调用 write(2),只存在 cache 里,免得被 Block。但如果已经超过两秒都还是这个样子,则会硬着头皮执行 write(2),即使 redis 会被 Block 住。此时那句要命的 log 会打印:“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.”。之后用redis-cli INFO 可以看到 aof_delayed_fsync 的值被加1。因此,对于 fsync 设为 everysec 时丢失数据的可能性的最严谨说法是:如果有 fdatasync 在长时间的执行,此时 redis 意外关闭会造成文件里不多于两秒的数据丢失。如果 fdatasync 运行正常,redis 意外关闭没有影响,只有当操作系统 crash 时才会造成少于1秒的数据丢失。
解决方法
1.修改系统参数配置

最后发现,原来是AOF rewrite时一直埋头的调用 write(2),由系统自己去触发 sync。在RedHat Enterprise 6里,默认配置vm.dirty_background_ratio=10,也就是占用了 10% 的可用内存才会开始后台 flush,而我的服务器有 8G 内存。很明显一次 flush 太多数据会造成阻塞,所以最后果断设置 “sysctl vm.dirty_bytes=33554432(32M)” 。AOF rewrite时定时也执行一下 fdatasync 嘛, antirez 回复新版中,AOF rewrite 时 32M 就会重写主动调用 fdatasync。
查看一下系统内核参数

>sysctl -a | grep dirty_background_ratio
vm.dirty_background_ratio = 10
>sysctl -a | grep vm.dirty_bytes
vm.dirty_bytes = 0

尝试修改
echo "vm.dirty_bytes=33554432" >> /etc/sysctl.conf 最后执行 “sysctl -p” 使配置生效;

2.关闭 RDB 或 AOF 持久化

通过上面我们知道该故障问题,其实就是因为磁盘大量IO 造成的请求阻塞。那么禁用 RDB 或 AOF 任一持久化即可,当然,建议禁用 RDB 而使用 AOF。