1 Redis高可用

我们知道,在web服务器中,高可用是指服务器可以正常访问的时间,衡量的标准是在多长时间内可以提供正常服务(99.9%、99.99%、99.999% 等等)。但是在Redis语境中,高可用的含义似乎要宽泛一些,除了保证提供正常服务(如主从分离、快速容灾技术),还需要考虑数据容量的扩展、数据安全不会丢失等。
在Redis中,实现高可用的技术主要包括持久化、复制、哨兵和集群,下面分别说明它们的作用,以及解决了什么样的问题。

  1. 持久化:持久化是最简单的高可用方法(有时甚至不被归为高可用的手段),主要作用是数据备份,即将数据存储在硬盘,保证数据不会因进程退出而丢失。
  2. 复制:复制是高可用Redis的基础,哨兵和集群都是在复制基础上实现高可用的。复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复。缺陷:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制。
  3. 哨兵:在复制的基础上,哨兵实现了自动化的故障恢复。缺陷:写操作无法负载均衡;存储能力受到单机的限制。
  4. 集群:通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案。

2 持久化概述

持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将Redis中的数据以某种形式(数据或命令)从内存保存到硬盘;当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。

Redis持久化分为RDB持久化和AOF持久化:前者将当前数据保存到硬盘,后者则是将每次执行的写命令保存到硬盘(类似于MySQL的binlog);由于AOF持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此AOF是目前主流的持久化方式,不过RDB持久化仍然有其用武之地。

3 RDB持久化

RDB持久化是将当前进程中的数据生成快照保存到硬盘(因此也称作快照持久化),保存的文件后缀是rdb;当Redis重新启动时,可以读取快照文件恢复数据。RDB持久化的触发分为手动触发和自动触发两种。

3.1 手动触发

  1. save:在命令行执行save命令,将以同步的方式创建rdb文件保存快照,会阻塞服务器的主进程,生产环境中不要用
  2. bgsave:在命令行执行bgsave命令,将通过fork一个子进程以异步的方式创建rdb文件保存快照,除了fork时有阻塞,子进程在创建rdb文件时,主进程可继续处理请求

3.2 自动触发

  1. # redis.conf
  2. # 自动触发rdb策略,可配置多条
  3. save "" # 关闭自动触发rdb
  4. save 900 1 # 900s达到1 changed触发
  5. save 300 10 # 300s达到10 changed触发
  6. save 60 10000 # 60s达到10000 changed触发
  7. dbfilename dump.rdb # 文件名
  8. dir /var/lib/redis/6379 # 存储路径
  9. # 当bgsave出现错误时,Redis是否停止执行写命令
  10. # 设置为yes,则当硬盘出现问题时,可以及时发现,避免数据的大量丢失
  11. # 设置为no,则Redis无视bgsave的错误继续执行写命令
  12. # 当对Redis服务器的系统(尤其是硬盘)使用了监控时,该选项考虑设置为no
  13. stop-writes-on-bgsave-error yes
  14. # 是否压缩rdb文件 推荐no 相对于硬盘成本,cpu资源更贵
  15. rdbcompression no
  16. # 是否开启RDB文件的校验,在写入文件和读取文件时都起作用
  17. # 关闭checksum在写入文件和启动文件时大约能带来10%的性能提升,但是数据损坏时无法发现
  18. rdbchecksum yes

3.2.1 save m n实现原理

Redis的save m n,是通过serverCron函数、dirty计数器、和lastsave时间戳来实现的。

  • serverCron:是Redis服务器的周期性操作函数,默认每隔100ms执行一次;该函数对服务器的状态进行维护,其中一项工作就是检查save m n配置的条件是否满足,如果满足就执行bgsave。
  • dirty计数器:是Redis服务器维持的一个状态,记录了上一次执行bgsave/save命令后,服务器状态进行了多少次修改(包括增删改);而当save/bgsave执行完成后,会将dirty重新置为0。
  • 例如,如果Redis执行了set mykey helloworld,则dirty值会+1;如果执行了sadd myset v1 v2 v3,则dirty值会+3;注意dirty记录的是服务器进行了多少次修改,而不是客户端执行了多少修改数据的命令。
  • lastsave时间戳:是Redis服务器维持的一个状态,记录的是上一次成功执行save/bgsave的时间。

save m n的原理如下:每隔100ms,执行serverCron函数;在serverCron函数中,遍历save m n配置的保存条件,只要有一个条件满足,就进行bgsave。对于每一个save m n条件,只有下面两条同时满足时才算满足:

  1. 当前时间-lastsave > m
  2. dirty >= n

3.2.2 其他自动触发机制

除了save m n以外,还有一些其他情况会触发bgsave:

  • 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点
  • 执行shutdown命令时,自动执行rdb持久化

3.3 关于持久化的思考

Q:如果9:00-9:30这段时间在持久化数据,那么所对应的值是哪个时间段的呢?
此时的数据应该是9:00的,因为redis在进行RDB持久化时,会fock出一个进程,在子进程中进行保存数据。

Q:fock出的进程是拷贝了数据副本吗,为什么父进程修改的值不会影响子进程?
linux在fock进程时,为了保证创建进程高效采用的是拷贝指针;而此时修改数据必然会影响其他线程,所以写数据时会复制一个副本出来(copy on wirte)。

3.4 执行流程

  1. Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof(后面会详细介绍该命令)的子进程,如果在执行则bgsave命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
  2. 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
  3. 父进程fork后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,并可以响应其他命令
  4. 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换
  5. 子进程发送信号给父进程表示完成,父进程更新统计信息

bgsave执行流程 (2).png

3.5 RDB文件

3.5.1 存储路径

RDB文件的存储路径既可以在启动前配置,也可以通过命令动态设定。

  • 配置:dir配置指定目录,dbfilename指定文件名。默认是Redis根目录下的dump.rdb文件。
  • 动态设定:Redis启动后也可以动态修改RDB存储路径,在磁盘损害或空间不足时非常有用;执行命令为config set dir {newdir}config set dbfilename {newFileName}

3.5.2 RDB文件格式
REDIS db_version SELECTDB 0 pairs SELECTDB 3 pairs EOF check_sum

其中各个字段的含义说明如下:

  1. REDIS:常量,保存着“REDIS”5个字符。
  2. db_version:RDB文件的版本号,注意不是Redis的版本号。
  3. SELECTDB 0 pairs:表示一个完整的数据库(0号数据库),同理SELECTDB 3 pairs表示完整的3号数据库;只有当数据库中有键值对时,RDB文件中才会有该数据库的信息(上图所示的Redis中只有0号和3号数据库有键值对);如果Redis中所有的数据库都没有键值对,则这一部分直接省略。其中:SELECTDB是一个常量,代表后面跟着的是数据库号码;0和3是数据库号码;pairs则存储了具体的键值对信息,包括key、value值,及其数据类型、内部编码、过期时间、压缩信息等等。
  4. EOF:常量,标志RDB文件正文内容结束。
  5. check_sum:前面所有内容的校验和;Redis在载入RBD文件时,会计算前面的校验和并与check_sum值比较,判断文件是否损坏。

3.5.3 压缩

Redis默认采用LZF算法对RDB文件进行压缩。虽然压缩耗时,但是可以大大减小RDB文件的体积,因此压缩默认开启;可以通过命令关闭:config set rdbcompression no
需要注意的是,RDB文件的压缩并不是针对整个文件进行的,而是对数据库中的字符串进行的,且只有在字符串达到一定长度(20字节)时才会进行。

3.6 启动时加载

RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会在Redis服务器启动时检测RDB文件,并自动载入。服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。Redis载入RDB文件时,会对RDB文件进行校验,如果文件损坏,则日志中会打印错误,Redis启动失败。

4 AOF

RDB持久化是将进程数据写入文件,而AOF(Append Only File)持久化,则是将Redis执行的每次写命令记录到单独的日志文件中(有点像MySQL的binlog);当Redis重启时再次执行AOF文件中的命令来恢复数据。与RDB相比,AOF的实时性更好,因此已成为主流的持久化方案。

4.1 开启AOF

Redis服务器默认开启RDB,关闭AOF;要开启AOF,需要在配置文件中配置:

  1. # redis.conf
  2. appendonly yes # 开启aof
  3. appendfilename "appendonly.aop" #文件名称
  4. # 写aof文件策略
  5. appendfsync always # 每次写的时候 flush
  6. appendfsync everysec # 每秒调用一次 flush
  7. appendfsync no # 依赖操作系统来执行(一般大概30s一次的样子)
  8. # 自动触发aof文件重写
  9. auto-aof-rewrite-percentage 100 # 当AOF文件 增长率 大于该配置项时自动开启重写(这里指超过原大小的100%)。
  10. auto-aof-rewrite-min-size 64M # 当AOF文件 大小 大于该配置项时自动开启重写
  11. # AOF重写期间是否禁止fsync
  12. # 如果开启该选项,可以减轻文件重写时CPU和硬盘的负载,但是可能会丢失AOF重写期间的数据
  13. no-appendfsync-on-rewrite no
  14. # 如果AOF文件结尾损坏,Redis启动时是否仍载入AOF文件
  15. aof-load-truncated yes
  16. # 开始混合模式
  17. aof-use-rdb-preamble yes

4.2 执行流程

AOF的执行流程包括:

  1. 命令追加append):将Redis的写命令追加到缓冲区aof_buf
  2. 文件写入(write)和文件同步(sync):根据不同的同步策略将aof_buf中的内容同步到硬盘
  3. 文件重写(rewrite):定期重写AOF文件,达到压缩的目的

4.2.1 命令追加

edis先将写命令追加到缓冲区,而不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。
命令追加的格式是Redis命令请求的协议格式,它是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点;具体格式略。在AOF文件中,除了用于指定数据库的select命令(如select 0 为选中0号数据库)是由Redis添加的,其他都是客户端发送来的写命令。

4.2.2 文件写入和文件同步

Redis提供了多种AOF缓存区的同步文件策略,策略涉及到操作系统的write函数和fsync函数,说明如下:
为了提高文件写入效率,在现代操作系统中,当用户调用write函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失;因此系统同时提供了fsyncfdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。

AOF缓存区的同步文件策略由参数appendfsync控制,各个值的含义如下:

  • always:命令写入aof_buf后立即调用系统fsync操作同步到AOF文件,fsync完成后线程返回。这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。
  • no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
  • everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。

4.2.3 文件重写

随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。
文件重写是指定期重写AOF文件,减小AOF文件的体积。需要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作。
关于文件重写需要注意的另一点是:对于AOF持久化来说,文件重写虽然是强烈推荐的,但并不是必须的;即使没有文件重写,数据也可以被持久化并在Redis启动的时候导入;因此在一些实现中,会关闭自动的文件重写,然后通过定时任务在每天的某一时刻定时执行。

文件重写之所以能够压缩AOF文件,原因在于:

  • 过期的数据不再写入文件
  • 无效的命令不再写入文件,如有些数据被重复设值、有些数据被删除等等
  • 多条命令可以合并为一个

文件重写的触发:

  • 手动触发:直接调用bgrewriteaof命令,该命令的执行与bgsave有些类似:都是fork子进程进行具体的工作,且都只有在fork时阻塞。
  • 自动触发:根据auto-aof-rewrite-min-sizeauto-aof-rewrite-percentage参数,以及aof_current_size和aof_base_size状态确定触发时机。

文件重写流程:

  1. Redis父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在bgsave命令则等bgsave执行完成后再执行。
  2. 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的。
  3. 父进程完成fork后对于所有新执行的写入命令,父进程一边将它们累积到一个内存缓存中,一边将这些改动追加到现有 AOF 文件 的末尾,这样样即使在重写的中途发生停机,现有的 AOF 文件也还是安全的。
  4. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件
  5. 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有数据追加到新 AOF 文件的末尾。
  6. 使用新的AOF文件替换老文件,完成AOF重写。

aof重写流程.png
关于文件重写的流程,有两点需要特别注意:(1) 重写由父进程fork子进程进行;(2) 重写期间Redis执行的写命令,需要追加到新的AOF文件中,为此Redis引入了aof_rewrite_buf缓存。

4.3 启动时加载

前面提到过,当AOF开启时,Redis启动时会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会载入RDB文件恢复数据。当AOF开启,但AOF文件不存在时,即使RDB文件存在也不会加载(更早的一些版本可能会加载,但3.0不会)。

4.3.1 文件修复

服务器可能在程序正在对 AOF 文件进行写入时停机, 如果停机造成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件, 从而确保数据的一致性不会被破坏。当发生这种情况时, 可以用以下方法来修复出错的 AOF 文件:

  • 为现有的 AOF 文件创建一个备份。
  • 使用 Redis 附带的 redis-check-aof 程序,对原来的 AOF 文件进行修复:redis-check-aof –fix
  • (可选)使用 diff -u 对比修复后的 AOF 文件和原始 AOF 文件的备份,查看两个文件之间的不同之处。
  • 重启 Redis 服务器,等待服务器载入修复后的 AOF 文件,并进行数据恢复。

但如果是AOF文件结尾不完整(机器突然宕机等容易导致文件尾部不完整),且aof-load-truncated(默认开启)参数开启,则日志中会输出警告,Redis忽略掉AOF文件的尾部,启动成功。

4.3.2 伪客户端

因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时命令是直接从文件中读取的,并不是由客户端发送;因此Redis服务器在载入AOF文件之前,会创建一个没有网络连接的客户端,之后用它来执行AOF文件中的命令,命令执行的效果与带网络连接的客户端完全一样。

5 方案选择

5.1 RDB和AOP的优缺点

5.1.1 RDB持久化

优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。

缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。

5.1.2 AOF持久化

优点:与RDB持久化相比,AOF优点在于支持秒级持久化、兼容性好,并且可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 进行重写。

缺点:文件大、恢复速度慢、对性能影响大。

5.2 持久化策略选择

在介绍持久化策略之前,首先要明白无论是RDB还是AOF,持久化的开启都是要付出性能方面代价的:对于RDB持久化,一方面是bgsave在进行fork操作时Redis主进程会阻塞,另一方面,子进程向硬盘写数据也会带来IO压力;对于AOF持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO压力更大,甚至可能造成AOF追加阻塞问题(后面会详细介绍这种阻塞),此外,AOF文件的重写与RDB的bgsave类似,会有fork时的阻塞和子进程的IO压力问题。相对来说,由于AOF向硬盘中写数据的频率更高,因此对Redis主进程性能的影响会更大。

在实际生产环境中,根据数据量、应用对数据的安全要求、预算限制等不同情况,会有各种各样的持久化策略;如完全不使用任何持久化、使用RDB或AOF的一种,或同时开启RDB和AOF持久化等。此外,持久化的选择必须与Redis的主从策略一起考虑,因为主从复制与持久化同样具有数据备份的功能,而且主机master和从机slave可以独立的选择持久化方案。

下面分场景来讨论持久化策略的选择,下面的讨论也只是作为参考,实际方案可能更复杂更具多样性。

  1. 如果Redis中的数据完全丢弃也没有关系(如Redis完全用作DB层数据的cache),那么无论是单机,还是主从架构,都可以不进行任何持久化。
  2. 在单机环境下(对于个人开发者,这种情况可能比较常见),如果可以接受十几分钟或更多的数据丢失,选择RDB对Redis的性能更加有利;如果只能接受秒级别的数据丢失,应该选择AOF。
  3. 但在多数情况下,我们都会配置主从环境,slave的存在既可以实现数据的热备,也可以进行读写分离分担Redis读请求,以及在master宕掉后继续提供服务。

在这种情况下,一种可行的做法是:

  • master:完全关闭持久化(包括RDB和AOF),这样可以让master的性能达到最好
  • slave:关闭RDB,开启AOF(如果对数据安全要求不高,开启RDB关闭AOF也可以),并定时对持久化文件进行备份(如备份到其他文件夹,并标记好备份的时间);然后关闭AOF的自动重写,然后添加定时任务,在每天Redis闲时(如凌晨12点)调用bgrewriteaof。

这里需要解释一下,为什么开启了主从复制,可以实现数据的热备份,还需要设置持久化呢?因为在一些特殊情况下,主从复制仍然不足以保证数据的安全,例如:

  • master和slave进程同时停止:考虑这样一种场景,如果master和slave在同一栋大楼或同一个机房,则一次停电事故就可能导致master和slave机器同时关机,Redis进程停止;如果没有持久化,则面临的是数据的完全丢失。
  • master误重启:考虑这样一种场景,master服务因为故障宕掉了,如果系统中有自动拉起机制(即检测到服务停止后重启该服务)将master自动重启,由于没有持久化文件,那么master重启后数据是空的,slave同步数据也变成了空的;如果master和slave都没有持久化,同样会面临数据的完全丢失。需要注意的是,即便是使用了哨兵(关于哨兵后面会有文章介绍)进行自动的主从切换,也有可能在哨兵轮询到master之前,便被自动拉起机制重启了。因此,应尽量避免“自动拉起机制”和“不做持久化”同时出现。
    1. 异地灾备:上述讨论的几种持久化策略,针对的都是一般的系统故障,如进程异常退出、宕机、断电等,这些故障不会损坏硬盘。但是对于一些可能导致硬盘损坏的灾难情况,如火灾地震,就需要进行异地灾备。例如对于单机的情形,可以定时将RDB文件或重写后的AOF文件,通过scp拷贝到远程机器,如阿里云、AWS等;对于主从的情形,可以定时在master上执行bgsave,然后将RDB文件拷贝到远程机器,或者在slave上执行bgrewriteaof重写AOF文件后,将AOF文件拷贝到远程机器上。一般来说,由于RDB文件文件小、恢复快,因此灾难恢复常用RDB文件;异地备份的频率根据数据安全性的需要及其他条件来确定,但最好不要低于一天一次。

5.3 fork阻塞:CPU的阻塞

在Redis的实践中,众多因素限制了Redis单机的内存不能过大,例如:

  • 当面对请求的暴增,需要从库扩容时,Redis内存过大会导致扩容时间太长;
  • 当主机宕机时,切换主机后需要挂载从库,Redis内存过大导致挂载速度过慢;
  • 以及持久化过程中的fork操作,下面详细说明。

5.3.1 fock操作

父进程通过fork操作可以创建子进程;子进程创建后,父子进程共享代码段,不共享进程的数据空间,但是子进程会获得父进程的数据空间的副本。在操作系统fork的实际实现中,基本都采用了写时复制技术,即在父/子进程试图修改数据空间之前,父子进程实际上共享数据空间;但是当父/子进程的任何一个试图修改数据空间时,操作系统会为修改的那一部分(内存的一页)制作一个副本。
虽然fork时,子进程不会复制父进程的数据空间,但是会复制内存页表(页表相当于内存的索引、目录);父进程的数据空间越大,内存页表越大,fork时复制耗时也会越多。

在Redis中,无论是RDB持久化的bgsave,还是AOF重写的bgrewriteaof,都需要fork出子进程来进行操作。如果Redis内存过大,会导致fork操作时复制内存页表耗时过多;而Redis主进程在进行fork时,是完全阻塞的,也就意味着无法响应客户端的请求,会造成请求延迟过大。
对于不同的硬件、不同的操作系统,fork操作的耗时会有所差别,一般来说,如果Redis单机内存达到了10GB,fork时耗时可能会达到百毫秒级别(如果使用Xen虚拟机,这个耗时可能达到秒级别)。因此,一般来说Redis单机内存一般要限制在10GB以内;不过这个数据并不是绝对的,可以通过观察线上环境fork的耗时来进行调整。观察的方法如下:执行命令info stats,查看latest_fork_usec的值,单位为微秒。
为了减轻fork操作带来的阻塞问题,除了控制Redis单机内存的大小以外,还可以适度放宽AOF重写的触发条件、选用物理机或高效支持fork操作的虚拟化技术等,例如使用Vmware或KVM虚拟机,不要使用Xen虚拟机。

5.4 AOP追加的阻塞:硬盘的阻塞

前面提到过,在AOF中,如果AOF缓冲区的文件同步策略为everysec,则:在主线程中,命令写入aof_buf后调用系统write操作,write完成后主线程返回;fsync同步文件操作由专门的文件同步线程每秒调用一次。
这种做法的问题在于,如果硬盘负载过高,那么fsync操作可能会超过1s;如果Redis主线程持续高速向aof_buf写入命令,硬盘的负载可能会越来越大,IO资源消耗更快;如果此时Redis进程异常退出,丢失的数据也会越来越多,可能远超过1s。
为此,Redis的处理策略是这样的:主线程每次进行AOF会对比上次fsync成功的时间;如果距上次不到2s,主线程直接返回;如果超过2s,则主线程阻塞直到fsync同步完成。因此,如果系统硬盘负载过大导致fsync速度太慢,会导致Redis主线程的阻塞;此外,使用everysec配置,AOF最多可能丢失2s的数据,而不是1s。

5.4.1 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等。