redis–28.1–常见问题–Asynchronous AOF fsync is taking too long(disk is busy)
1、现象
1.1、redis 客户端(应用端)
org.redisson.client.RedisTimeoutException: Command still hasn't been written into connection! Check connection with Redis node: 10.202.129.96/10.202.129.96:6379 for TCP packet drops. Try to increase nettyThreads setting. Node source: NodeSource [slot=0, addr=null, redisClient=null, redirect=null, entry=null], connection: RedisConnection@1165455922 [redisClient=[addr=redis://10.202.129.96:6379], channel=[id: 0x9a938f12, L:/100.100.22.64:39794 - R:10.202.129.96/10.202.129.96:6379], currentCommand=CommandData [promise=java.util.concurrent.CompletableFuture@a8d8ed7[Not completed, 1 dependents], command=(ZREMRANGEBYSCORE), params=[redisson__cache_updates_log:{menuCache}, 0, 1688353276115], codec=org.redisson.client.codec.LongCodec], usage=1], command: (ZREMRANGEBYSCORE), params: [redisson__cache_updates_log:{menuCache}, 0, 1688353276115] after 3 retry attempts
at org.redisson.command.RedisExecutor$2.run(RedisExecutor.java:200)
at io.netty.util.HashedWheelTimer$HashedWheelTimeout.expire(HashedWheelTimer.java:672)
at io.netty.util.HashedWheelTimer$HashedWheelBucket.expireTimeouts(HashedWheelTimer.java:747)
at io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:472)
at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.lang.Thread.run(Thread.java:748)
1.2、redis 服务端
Asynchronous AOF fsync is taking too long (disk is busy?). writing the AOF buffer withoutwaiting for fsynclow down Redis
2、源码分析
从代码中分析得知,当Redis后台有fsync操作,并且等待超过了2s,就会阻塞write操作,此时Redis是不可写入的,就会打印出Asynchronous AOF fsync is taking too long (disk is busy?) 日志,触发业务超时。
2.1、下面的情况可能会导致Redis的fsync阻塞2s
-
如果开启了 appendfsync everysec 的fsync策略,并且no-appendfsync-on-rewrite参数为no,则redis在做AOF重写的时候,也会每秒将命令fsync到磁盘上,而此时Redis的写入量大而磁盘性能较差,fsync的等待就会严重;
-
单纯的写入量大,大到磁盘无法支撑这个写入。例如appendfsync参数的值是everysec,每秒进行一次fsync,而磁盘的性能很差。
3、故障分析
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。。
为了更清晰的看到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秒的数据丢失。
3、解决方案
3.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
# 查看内存的脏页字节大小,设置为0代表由系统自己控制何时调用fsync
>sysctl -a | grep vm.dirty_bytes
vm.dirty_bytes = 0
尝试修改
# 修改为一个小的值,例如32MB,达到这个数据量就fsync,让操作系统fsync这个动作更频繁一点,避免单次fsync太多数据,导致阻塞
echo "vm.dirty_bytes=33554432" >> /etc/sysctl.conf
最后执行以下命令使配置生效
sysctl -p
3.2、方案2:关闭 RDB 或 AOF 持久化
关闭RDB可减少RDB生成的时候,对磁盘的压力,而关闭AOF则可以减少AOF刷盘对磁盘的压力,从而让Redis的性能达到极致。不过这个方法只能用在纯缓存场景,如果有持久化的要求,则一般不靠谱,因为安全性大大降低,一旦宕机,则数据无法恢复。
一般建议 禁用 RDB 而使用 AOF。
折中方案,是从库开启AOF,而主库关闭AOF。
4、实际生产中的建议
避免这类问题的发生。首先,如果架构允许尽量不要在主库上同时做 RBD 和 AOF 持久化(如果要做,就使用SSD固态硬盘)。但是,这样如果不做持久化,将暴露如下问题:
- 当 redis 服务挂掉之后,重启缓存全部丢失;
- 当主机挂掉,通过脚本或sentinel将从提升为主之后,以前主库就作废;
这样做的好处就是,提升了主库的性能。坏处就是,当主库宕机之后,你只能将该主机从主从当中剔除;
上面这种方式提高了一定的复杂性,那么要解决这个问题。那么可以选择一个折中办法,那就是做AOF持久化而不使用RDB的方式。我们可以通过下面的内容来选择适合自己的持久化方式,如下;
- RDB。在这里,您可以根据您配置的保存触发器进行大量权衡。
- AOF + fsync 总是这样:这个速度非常慢,只有当你知道你在做什么的时候才应该使用它。
- AOF + fsync 每一秒:这是一个很好的妥协。
- AOF + fsync 每秒+ no-appendfsync-on-rewrite选项设置为yes:如上所示,但避免在重写期间fsync降低磁盘压力。
- AOF + fsync 永远不会。 Fsyncing在这个设置上取决于内核,甚至更少的磁盘压力和延迟尖峰的风险。