redis:AOF与RDB
学习自:
《Redis开发与运维》pdf 317-350页
1、前言
Redis支持RDB与AOF两种持久化机制,持久化的目的是避免因进程退出造成的数据丢失问题。下次重启时利用之前的持久化文件即可实现数据恢复。
2、RDB(快照)
RDB持久化是将当前进程数据生成快照保存到dump.rdb文件(在硬盘中)的过程,触发RDB持久化过程分为手动触发、自动触发。
快照:将内存数据以二进制文件的形式保存起来。
RDB实质上是Redis内部的一个定时器事件,它每隔一段固定时间就会去检查当前数据发生改变的次数和时间频率。当满足了配置文件中规定的持久化触发条件时,Redis就会通过操作系统调用fork()来创建一个子进程,该子进程和父进程享有相同的地址空间。
Redis通过子进程遍历整个内存空间来获取存储的数据,从而完成数据持久化操作。
1)触发机制
a、手动触发
手动触发对应save、bgsave命令:
①save(已废弃)
阻塞当前redis服务器,直到RDB过程完成,对于内存较大的实例会造成长期阻塞,不适用于线上环境。
save命令对应的日志:
1 | DB saved on disk |
②bgsave(background save)
Redis进程执行fork创建子进程,RDB持久化过程由子进程负责,完成后自动结束。
阻塞只发生在fork阶段,一般时间很短。
bgsave命令对应的日志:
1 2 3 4 | * Background saving started by pid 3151 * DB saved on disk * RDB: 0 MB of memory used by copy-on-write * Background saving terminated with success |
bgsave是针对save阻塞问题作出的优化,因此Redis内部所有涉及RDB的操作都用bgsave方式,而save已经废弃。
如果重写过程中,发生了内存修改操作,父进程负责创建所修改内存页的副本,第三行就说明了这部分内存消耗,可以理解为RDB重写消耗的内存量。
b、自动触发
自动触发的情景有很多:
①使用save配置项,如"save m n",代表m秒内数据集存在n次修改,自动触发bgsave;
②如果从节点执行全量复制,主节点自动执行bgsave生成RDB文件并发送给从节点;
③执行debug reload命令重新加载redis,也会自动触发save;
④默认情况下执行shutdown,如果没有开启AOF持久化功能,则自动执行bgsave。
2)bgsave流程
bgsave是主流的触发RDB持久化的方式,下图展示了它的运作流程
具体过程
1)执行bgsave,此时Redis父进程判断是否存在正在执行的RDB/AOF子进程,如果存在则bgsave命令直接返回;
2)父进程执行fork创建子进程,fork操作过程中阻塞父进程,可以通过命令info Stats(redis-cli下)查看latest_fork_usec选项,可以获取最近一个fork操作的耗时,单位ms;
3)父进程fork完成后,bgsave命令返回"Background saving started"信息并不再阻塞父进程,可以继续响应其他命令;
4)子进程创建RDB文件,根据父进程内存中的临时快照文件,对原有文件进行原子替换。执行lastsave命令获取最后一次生成RDB的时间,对应info统计的rdb_last_save_time选项;
5)子进程发送信号给父进程表示完成,父进程更新统计信息,具体可以通过redi-cli下info Persistence下的rdb_*查询。
3)RDB文件的处理
a、保存
RDB文件保存在conf配置项dir指定目录下,文件名通过配置项dbfilename指定。可以通过config set dir {newDir}和config set dbfilename {newFileName}实现运行时动态配置,当下次执行时,RDB会被保存到newDir。
当遇到坏盘、磁盘写满等情况,可以用config set dir {newDir}在线修改文件路径到可用的磁盘路径,之后执行bgsave进行磁盘切换,这同样适用于AOF持久化文件。
b、压缩
Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远小于内存大小,默认开启,可以通过参数config set rdbcompression {yes|no}动态修改。
虽然压缩RDB会消耗CPU,但可以大幅降低文件体积,方便保存到硬盘或通过网络发送给从节点,因此线上建议开启。
c、校验
如果Redis加载损坏的RDB文件时拒绝启动,并打印日志:
1 | Short read or OOM loading DB. Unrecoverable error, aborting now. |
这时可以用Redis提供的redis-check-dump工具检测RDB文件并获取对应的错误报告。
4)RDB优缺点
a、优点
RDB是一个压缩紧凑的二进制文件,代表Redis在某个时间点上的数据快照。适用于备份、全景复制等场景。例如每6h执行bgsave备份,并将RDB文件拷贝到远程机器或者文件系统中,用于灾备恢复。
Redis加载RDB恢复数据远远快于AOF方式。
b、缺点
RDB没法做到实时持久化/秒级持久化。因为bgsave每次都要执行fork创建子进程,属于重量级操作,频繁执行成本过高。
RDB文件用特定二进制格式编写,Redis版本演进过程中存在多个版本的RDB,因此存在老版本RDB服务无法兼容新版RDB格式的问题。
针对RDB不适合实时持久化的问题,Redis提供了AOF持久化来解决。
3、AOF
AOF(append only file):以独立日志的方式记录每次write,重启时再重新执行AOF文件中的命令达到恢复数据的目的。
AOF的主要作用是解决了数据持久化的实时性和高可靠性,目前已经是Redis持久化的主流方式。
其机制是通过将Redis的写操作追加到AOF文件中来实现的。当Redis执行一条写操作时,它会将该操作追加到AOF文件末尾。因此AOF文件中包含了Redis中所有的写操作,Redis服务器会在启动时加载AOF文件,并依次执行其中的写操作,以重建Redis数据库。
1)使用AOF
a、配置项
在Redis的配置文件redis.conf与AOF启动相关的配置项有:
- appendonly:是否开启AOF持久化
- appendfilename:aof文件名,以aof为后缀
- dir:保存路径
b、执行流程
命令写入(append)→文件同步(sync)→文件重写(rewrite)→重启加载(load)
具体流程:
①所有的写命令会追加到aof_buf(缓冲区)中;
②aof_buf根据对应策略向硬盘做同步操作;
③随着AOF越来越大,需要定期对AOF文件进行重写,达到压缩的目的;
④当Redis服务器重启时,可以加载AOF文件进行数据恢复。
接下来是对这些步骤的详细说明
2)命令写入
AOF命令写入的内容直接是文本协议格式。例如set hello world这条命令,在aof_buf中会追加如下内容:
1 | *3\r\n$3\r\nset\r\n$5\r\nhello\r\n$5\r\nworld\r\n |
关于Redis协议的格式这里不做说明,有需要的可以自行查找。
a、AOF为什么直接采用文本协议格式?
- 文本协议具有很好的兼容性;
- 开启AOF后,所有的写入命令都包含追加操作,直接采用协议格式,避免了二次处理开销;
- 文本协议具有可读性,方便直接修改和处理。
b、AOF为什么把命令追加到aof_buf中?
Redis使用单线程响应命令,如果每次写AOF都直接追加到硬盘,那么性能完全取决于硬盘负载。
先写入aof_buf中,还有另一个好处,Redis可以提供多种缓冲区同步硬盘的策略,在性能和安全性方面做出平衡。
3)文件同步
Redis提供了多种AOF缓冲区同步文件策略,在conf文件中由appendfsync控制,它的不同取值有不同含义:
- always:命令写入aof_buf后,调用系统fsync操作同步到AOF文件,fsync完成后线程返回;
- everysec:命令写入aof_buf后,调用系统write操作,witer完成后线程返回。fsync同步文件操作由专门线程每秒调用一次;
- no:命令写入aof_buf后,调用系统write操作,不对AOF文件做fsync同步,同步硬盘操作由操作系统负责,通常同步周期最长30s。
对于系统调用write、fsync的说明:
- write:write操作会触发延迟写(delayed write)机制。Linux在内核提供页缓冲区来提高磁盘IO性能。write操作在写入系统缓冲区后自动返回。同步磁盘操作依赖于系统调度机制,例如:缓冲区页空间写满或达到特定时间周期。同步文件之前,如果此时系统故障宕机,缓冲区内数据将丢失。
- fsync:fsync针对单个文件(AOF文件),做强制磁盘同步,fsync将阻塞直到写入硬盘完成后返回,保证了数据持久化。
三种方式的选择方案
- always:配置为always时,每次写入都要同步AOF文件,在一般的SATA硬盘上,Redis只能支持大约几百TPS写入,这与Redis的高性能特性背道而驰,不建议配置。
- no:由于OS每次同步AOF文件的周期不可控,而且会加大每次同步硬盘的数据量,虽然提高了性能,但是数据安全性不法保证
- everysec:建议的同步策略,也是默认配置,做到了兼顾性能和数据安全性。理论上只有在系统宕机时丢失1s的数据。
在用everysec时,Redis会使用另一条线程每秒执行fsync同步硬盘。当系统资源繁忙时,会造成Redis主线程阻塞(《Redis开发与运维》345页):
4)重写机制
随着命令不断写入AOF,文件会越来越大,为了解决这个问题,Redis引入了AOF重写机制压缩文件体积。AOF文件重写是把Redis进程内的数据转化为写命令同步到新AOF文件的过程。
重写后的AOF文件为什么可以变小?原因如下:
- 进程内已经超时的数据不再写入文件;
- 旧的AOF文件含有无效命令,如del K、hdel K、srem K等。重写使用进程内的数据直接生成,这样新的AOF文件只保留最终数据的写入命令;
- 多条写命令合并为同一个,例如3个lpush a、b、c可以转换为lpush list a b c。为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset等操作,以64个元素为界拆分为多条。
AOF重写降低了文件占用空间,另一个目的是:更小的AOF文件可以更快地被Redis加载。
AOF重写过程可以分为手动触发、自动触发:
- 手动触发:直接调用bgrewriteaof命令;
- 自动触发:根据conf配置auto-aof-rewrite-min-size、auto-aof-rewrite-percentage确定自动触发时机。
关于auto-aof-rewrite-min-size与auto-aof-rewrite-percentage与自动触发
- auto-aof-rewrite-min-size
AOF重写时文件最小体积,默认64MB
- auto-aof-rewrite-percentage
当前AOF空间(aof_current_size)与上一次重写后的AOF文件空间(aof_base_size)的比值。
AOF自动触发时机=aof_current_size>auto-aof-rewrite-minsize&&(aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewritepercentage
其中aof_current_size和aof_base_size可以在info Persistence统计信息中查看。
当触发了AOF重写,内部的运行流程为:
如果当前进程正在执行AOF重写,那么请求不被执行,并返回如下响应:
1 | ERR Background append only file rewriting already in progress |
如果当前进程正在执行bgsave操作,重写命令延迟到bgsave完成后再执行,返回如下响应:
1 | Background append only file rewriting <strong>scheduled< /strong > |
③:
a、主进程fork完毕,继续响应其他命令。所有修改命令依然写入AOF缓冲区并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确性;
b、由于fork采用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然响应命令,Redis使用“AOF重写缓冲区”保存这部分新数据,防止新AOF文件生成期间丢失这部分数据。
④子进程根据内存快照,按照命令合并规则写入新的AOF文件。每次批量写入硬盘的数据量由aof-rewrite-incremental-fsync控制,默认32MB,防止单次刷盘次数过多引起硬盘阻塞。
⑤:
a、新AOF写入完成后,子进程发送信号给父进程,父进程更新统计信息,具体见info persistence下的aof_*相关统计;
b、父进程把AOF重写缓冲区的数据写入新AOF文件;
c、使用新AOF文件替换老文件,完成AOF重写
AOF重写时,Redis的日志输出内容为:
1 2 3 4 5 6 7 8 9 | * Background append only file rewriting started by pid 8937 * AOF rewrite child asks to stop sending diffs. * Parent agreed to stop sending diffs. Finalizing AOF... * Concatenating 0.00 MB of AOF diff received from parent. * SYNC append only file rewrite performed * AOF rewrite: 53 MB of memory used by copy-on-write * Background AOF rewrite terminated with success * Residual parent diff successfully flushed to the rewritten AOF (1.49 MB) * Background AOF rewrite finished successfully |
5)重启加载
AOF与RDB都可以用于服务器重启时的数据恢复。
下图展示了Redis持久化文件加载流程:
具体流程:
①开启AOF持久化,存在AOF文件,此时优先加载AOF文件,打印如下日志:
1 | * DB loaded from append only file : 5.841 seconds |
②AOF关闭或不存在AOF文件,加载RDB文件,打印如下日志:
1 | * DB loaded from disk: 5.586 seconds |
③加载AOF/RDB文件成功后,Redis启动成功
④AOF/RDB存在问题,Redis启动失败并打印错误信息
6)文件校验
加载损坏的AOF文件时会拒绝启动,并打印如下日志:
1 2 | # Bad file format reading the append only file: make a backup of your AOF file, then use . /redis-check-aof --fix <filename> |
如何修复?
对于错误AOF文件,先备份,再用redis-check-aof--fix命令进行修复,修复后用diff-u对比数据的差异,找出丢失的数据。
AOF文件可能存在结尾不完整的情况,比如机器突然断电导致AOF尾部文件命令写入补全。conf文件中提供了aof-load-truncate项来兼容这种情况。加载AOF时,如果遇到这种问题会忽略并继续启动,同时打印如下警告日志:
1 2 3 | # !!! Warning: short read while loading the AOF file !!! # !!! Truncating the AOF at offset 397856725 !!! # AOF loaded anyway because aof-load-truncated is enabled |
7)持久化问题定位
①通过fork创建子进程,fork耗时
②子进程重写AOF与RDB文件的开销
主线程fork的子进程负责AOF与RDB文件重写,它的运行过程主要涉及CPU、内存、硬盘三方面的消耗。
a、CPU
①CPU开销
子进程负责把进程内的数据分批写入文件,该过程属于CPU密集操作,通常子进程对单核CPU利用率接近90%;
②CPU消耗
Redis是CPU密集型服务,不要做绑定单核CPU操作。由于子进程非常消耗CPU,会和父进程产生单核资源竞争。
不要和其他CPU密集型服务部署在一起,造成CPU过度竞争。
如果部署多个Redis实例,需要保证同一时刻只有一个子进程执行重写,详见第4节多实例部署。
b、内存消耗
①分析
子进程通过fork产生,占用内存大小等于父进程,理论上需要两倍内存来完成持久化操作,但Linux有写时复制(copy-on-write)机制——父子进程共享相同的物理内存页,当父进程处理写时,会把要修改的页创建副本,子进程在fork时会共享整个父进程内存快照;
②监控
RDB重写时,Redis日志输出内容如下:
1 2 3 4 | * Background saving started by pid 7692 * DB saved on disk * RDB: 5 MB of memory used by copy-on-write * Background saving terminated with success |
如果重写过程中存在内存修改,父进程负责创建修改页的副本,从日志中可以看出这部分内存消耗了5MB,可以等价认为RDB重写消耗了5MB内存。
AOF重写时,Redis日志输出内容如下:
1 2 3 4 5 6 7 8 9 | * Background append only file rewriting started by pid 8937 * AOF rewrite child asks to stop sending diffs. * Parent agreed to stop sending diffs. Finalizing AOF... * Concatenating 0.00 MB of AOF diff received from parent. * SYNC append only file rewrite performed * AOF rewrite: 53 MB of memory used by copy-on-write * Background AOF rewrite terminated with success * Residual parent diff successfully flushed to the rewritten AOF (1.49 MB) * Background AOF rewrite finished successfully |
父进程维护页副本消耗和RDB重写过程类似,不同之处在于AOF要重写缓冲区(aof_rewrite_buf),因此AOF重写时子进程消耗量为53MB+1.49MB。
③优化
- 同CPU优化一样,如果部署了多个Redis实例,尽量保证同一时刻只有一个子进程在工作;
- 避免在大量写入时做子进程重写操作,这将导致父进程维护大量页副本,造成内存消耗。
c、硬盘
子进程会把AOF或RDB文件写入硬盘持久化,势必造成硬盘写入压力。
优化:
- 不要和其他高硬盘负载的服务放在一起;如存储服务、消息队列等;
- AOF重写时会消耗大量磁盘IO,可以开启配置no-appendfsync-on-rewrite,表示在AOF重写期间不做fsync操作;
- 当开启了AOF的Redis用于高流量写入场景时,如果用普通机械磁盘,写入吞吐量一般在100MB/s左右,这时Redis实例的瓶颈主要在AOF同步硬盘上;
- 对于单机配置多个Redis实例的情况,可以配置不同实例分盘存储AOF文件,分摊硬盘写入压力。
当no-appendfsync-on-rewrite=yes时,极端情况下可能丢失整个AOF重写期间的数据,需要根据数据安全性决定是否配置。
8)AOF追加阻塞
当开启AOF持久化时,常用的同步硬盘策略是everysec,用于平衡性能与数据安全性。
对于这种方式,Redis用另一条线程每秒执行fsync同步硬盘。当系统硬盘资源繁忙时,会造成Redis主线程阻塞:
阻塞流程分析:
1)主线程负责写入AOF缓冲区;
2)AOF线程负责每秒执行一次同步磁盘操作,并记录最近一次同步时间;
3)主线程负责对比上次AOF同步时间:
- ≤2s,主线程直接返回
- >2s,主线程阻塞,直到同步操作完成
通过上述三个流程可以发现两个问题:
1)everysec最多可能丢失2s数据,而非1s;
2)如果系统fsync缓慢,将会导致Redis主线程阻塞影响效率。
AOF阻塞问题定位:
1)AOF阻塞时,Redis输出如下日志,用于记录AOF fsync阻塞导致拖慢Redis服务的行为:
1 2 | 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 |
2)AOF追加阻塞发生时,在info Persistence中,aof_delayed_fasync会累加,看这个指标方便定位AOF阻塞问题;
3)AOF同步最多允许2s延迟,当延迟发生时说明硬盘存在高负载问题,可以通过监控工具如iotop定位消耗硬盘IO资源的进程。
优化AOF追加阻塞问题主要是优化系统硬盘负载。
4、多实例部署
Redis单线程架构导致无法充分利用CPU多核特性,常见的解决方式是在一台机器上部署多个Redis实例。当多个实例开启AOF重写后,彼此间会产生对CPU和IO的竞争。
对于单机多Redis部署,如果同一时刻运行多个子进程,对当前系统的影响非常明显,因此需要一些措施,把子进程工作进行隔离。Redis在info Persistence中为我们提供了监控子进程运行状况的度量指标:
基于这些指标,可以通过外部轮询的方式控制AOF重写操作的执行,整个过程如下图所示:
流程说明:
1)外部程序定时轮询监控所有Redis实例;
2)对于开启AOF的实例,通过(aof_current_size-aof_base_size)/aof_base_size确认增长率;
3)当增长率超过阈值,执行bgrewriteao手动触发当前实例的AOF重写;
4)运行期间循环检查aof_rewrite_in_progress、aof_current_rewrite_time_sec指标,直到AOF重写结束;
5)确认实例的AOF重写完毕后,再检查其他实例并重复上述操作。从而保证机器内每个Redis实例AOF重写串行化执行。
5、小结
1)Redis提供了两种持久化方式:RDB与AOF
2)RDB采用一次性生成内存快照的方式,产生的文件紧凑压缩比更高,因此读取RDB恢复速度更快。由于每次生成RDB开销较大,无法实时持久化,一般用于数据冷备、复制传输;
3)save命令会阻塞主线程,现已废弃,bgsave通过fork创建子进程生成RDB避免阻塞;
4)AOF通过追加命令到文件实现持久化,通过appendfsync实现实时/秒级持久化。因此需要不断追加写命令,所以AOF文件体积逐渐变大,需要定期执行重写来降低文件体积。
5)AOF重写可以通过配置auto-aof-rewrite-min-size和auto-aof-rewrite-percentage控制自动触发,也可以用bgrewriteaof手动触发;
6)fork子进程执行期间使用copy-on-write机制与父进程共享内存,避免内存消耗翻倍。AOF重写期间还要维护重写缓冲区,保存新的写入命令避免数据丢失;
7)持久化阻塞主线程的场景有:fork阻塞、AOF追加阻塞。fork阻塞和内存量、系统有关,AOF追加阻塞说明硬盘资源紧张;
8)单机下部署多个实例时,为了防止出现多个子进程执行重写操作,建议做隔离控制,避免CPU、IO资源竞争。
回顾
1、Redis持久化:在重启Redis时利用持久化文件进行数据恢复,避免进程退出造成数据丢失的问题。
2、持久化方式:RDB、AOF,其中读取RDB的恢复速度更快。
3、RDB持久化:将当前进程数据的快照保存为dump.rdb(硬盘中)。
4、基本原理:在Redis内设置一个定时器事件,每隔一段时间就去检查当前数据变化的次数和频率,当满足了conf条件,Redis就会通过OS调用fork()创建一个子进程,父子进程享有相同的地址空间。在获取快照时,Redis会通过子进程遍历整个内存空间。
5、RDB触发方式:自动、手动
6、RDB手动触发:save(废弃)、bgsave
save:阻塞当前Redis服务器,直到RDB完成
bgsave:Redis通过OS调用fork()创建子进程,由子进程负责RDB持久化。阻塞只发生于fork()时,在子进程持久化时不阻塞。
bgsave日志:
1 2 3 4 | * Background saving started by pid 3151 * DB saved on disk * RDB: 0 MB of memory used by copy-on-write * Background saving terminated with success |
第三行标记了写时复制的内存消耗,这是因为在重写时如果发生内存修改操作,父进程会消耗额外内存来创建修改进行修改的内存页副本。
Redis内部所有涉及RDB的操作都已经改用了bgsave方式。
9、RDB自动触发,有多种场景:
- save配置项,save m n注明了m秒内n次修改后自动触发bgsave;
- 从节点执行全量复制后,主节点自动执行bgsave并将生成的RDB发给从节点;
- 执行debug reload重新加载redis,自动触发
- 执行shutdown,且未开启AOF持久化,自动触发
10、bgsave是RDB持久化的方式,流程:
1)执行bgsave,父进程先判断当前有无其他RDB/AOF子进程正在执行,如果存在,那么bgsave直接返回;
2)父进程通调用fork()创建子进程,fork过程中会阻塞父进程;
3)父进程fork完毕,bgsave日志中会显示background saving started,此时不再阻塞父进程,可以fork继续响应其他命令;
4)子进程创建RDB文件,根据父进程内存中的快照文件,对原RDB进行原子替换;
5)子进程给父进程发信号表示完成,父进程更新统计信息。
11、RDB文件的相关操作
1)保存
RDB文件的保存路径由conf文件中的dir配置项指定,文件名由dbfilename指定,这两项可以在运行时通过config set 配置项 值动态配置,下次执行时,RDB会采用这个配置;这同样适用于AOF持久化文件;
2)压缩
Redis默认用LZF算法对RDB文件进行压缩,压缩后的文件远小于内存大小,与之相关的配置项rdbcompression,通过config set rdbcompression {yes|no}动态修改;
压缩RDB会消耗CPU,但是会大幅降低文件体积,方便保存到硬盘或者传输给从节点,建议线上开启
3)校验
RDB文件损坏时,Redis会拒绝启动,并打印相关日志。
此时可以用Redis提供的redis-check-dump工具检测RDB文件并获取对应的错误报告。
12、RDB优缺点
- 优点
RDB是一个紧凑压缩的二进制文件,代表某个时间点上的Redis数据快照。适用于备份、全景复制。
在数据恢复时,通过加载RDB方式要远快于AOF方式。
- 缺点
RDB无法实时/秒级持久化,因为每次bgsave都要用fork创建子进程,属于重量级操作,频繁执行的成本过高;
RDB用特定二进制格式编写,不同redis版本下可能存在多种RDB,存在老版本RDB服务无法兼容新版本RDB文件的问题。
13、AOF可以解决RDB无法实时持久化的问题
14、AOF(Append Only File):以独立日志的方式往AOF文件末尾追加(Append)写操作,重启Redis时再重新执行AOF文件中的命令以达到恢复数据的目的。
AOF文件中有Redis中的全部写操作,Redis服务器在启动时加载AOF文件,并依次还行全部写操作,最终重建Redis数据库。
15、AOF流程:
- 写命令写入AOF缓冲区(下称aof_buf)
- 用AOF缓冲来同步AOF文件
- 定期重写AOF文件
- Redis重启时加载AOF文件
16、所有Redis中与写相关的命令写入aof_buf
17、将aof_buf内容同步到AOF文件:
三种同步策略:always、eversec、no,通过配置appendfsync控制。
- always:aof_buf通过调用系统fsync操作同步到AOF文件,fsync完成后线程返回;
- everysec:aof_buf调用系统write操作,write后线程返回。fsync同步文件由专门线程每秒调用一次;
- no:aof_buf调用系统write操作,不对AOF做fsync同步,同步硬盘的操作由OS负责,周期最长30s。
关于fsync与write:
- write:write会触发延迟写(delayed write)机制。Linux在内核提供了页缓冲区来提高磁盘IO性能,write会在写入系统缓冲区后自动返回。之后的磁盘同步操作并不是由write执行,而是由系统调度机制控制,比如当页缓冲区满或者达到指定周期,就触发同步。
- fsync:针对AOF文件做强制磁盘同步,fsync将阻塞父进程直到硬盘写完。
always、no、everysec的选择:
- always:每次写入都同步到AOF文件,受限于硬盘IO,不建议配置;
- no:周期不可控,且每次同步数据量增加,虽然提高了性能,但安全性无法保证;
- everysec:建议的同步策略,也是默认。兼顾了性能与数据安全性,理论上只有在宕机时才会丢失1s的数据(但是实际上最多会发生2s数据,具体见下)
在用everysec时,Redis会使用另一条线程每秒执行fsync同步硬盘。当系统资源繁忙时,会造成Redis主线程阻塞。
18、定期重写AOF文件
为什么要定期重写?
随着写命令不断追加到AOF文件尾,AOF文件会越来越大,为了解决这个问题Redis引入了AOF重写机制来压缩文件体积。
更小的文件体积可以①降低文件占用空间;②更快地被Redis加载。
什么是AOF文件重写?
将Redis中的数据转化为写命令同步到新AOF文件
为什么重写后的AOF文件会变小:
1)进程内已经超时的数据不会再写入文件
2)旧的AOF文件中存在无效命令(主要是各种del、rem、被覆盖的set等),重写时使用进程内的数据直接生成,这样新的AOF文件只保留最终数据的写入命令;
3)将多条写命令合并为1个,如三条lpush命令合并为一条lpush;
19、AOF重写过程的触发机制:手动、自动
20、手动触发:调用bgwriteaof命令
21、自动触发:由配置项auto-aof-rewrite-min-size和auto-aof-rewrite-percentage决定自动触发时机
- ·auto-aof-rewrite-min-size:表示运行AOF重写时文件最小体积,默认为64MB。
- ·auto-aof-rewrite-percentage:代表当前AOF文件空间(aof_current_size)和上一次重写后AOF文件空间(aof_base_size)的比值。
自动触发的两个条件:①aof_current_size>auto-aof-rewrite-minsize;②(aof_current_size-aof_base_size)/aof_base_size>=auto-aof-rewritepercentage
其中aof_current_size和aof_base_size可以在info Persistence统计信息中查看。
22、AOF重写时的系统内部流程:
1)指令bgwriteaof发出重写请求
如果当前进程正在执行AOF重写bgwriteaof,那么该请求不被执行,并返回以下响应
1 | ERR Background append only file rewriting already in progress |
如果当前进程旨在执行RDB重写bgsave,重写命令延迟到bgsave完成后,并返回以下响应
1 | Background append only file rewriting scheduled |
2)父进程执行fork创建子进程
3.1)主进程fork后,继续响应其他命令。所有修改命令(在AOF重写期间新生成的)仍会写入aof_buf并根据appendfsync策略同步到硬盘,保证原有AOF机制正确性;
3.2)fork采用写时复制技术,子进程只能共享fork时的内存数据,此时新生成的数据会保存于aof重写缓冲区(aof_rewrite_buf)中,防止新AOF文件生成期间丢失这部分数据;
4)子进程根据快照,按照命令合并规则写入新AOF文件。每次批量写入的数据量由配置aof-rewrite-incremental-fsync控制,默认32MB,防止单次刷盘数据过多造成硬盘阻塞;
5.1)新AOF文件写入完毕后,子进程发信号给父进程,父进程更新统计信息,通过info persistence下的aof_*查询;
5.2)父进程把aof重写缓冲区aof_rewrite_buf中的数据写入新AOF文件;
5.3)用新AOF文件替换老文件,完成AOF重写。
23、重启加载
Redis持久化文件的加载流程如下:
1)当AOF开启且存在AOF文件,优先加载AOF文件,打印日志:
1 | * DB loaded from append only file : 5.841 seconds |
AOF关闭或AOF文件不存在时,才加载RDB文件,打印日志:
1 | * DB loaded from disk: 5.586 seconds |
2)AOF/RDB加载完毕,Redis才算启动成功
AOF/RDB文件存在错误时,Redis启动失败并打印错误信息
24、文件校验
校验的是错误的AOF文件,当AOF文件损坏时,Redis会拒绝启动并打印日志:
1 2 | # Bad file format reading the append only file: make a backup of your AOF file, then use . /redis-check-aof --fix <filename> |
在对错误AOF文件进行备份后,可以用redis-check-aof--fix命令进行修复,修复后用diff-u进行对比,找出丢失的数据,进行人工补全。
当发生了停电事故,会导致AOF结尾不完整,Redis提供了配置项aof-load-truncated来兼容这种情况,默认启动。加载AOF后,如果遇到该问题会忽略并继续启动,同时打印如下警告日志:
1 2 3 | # !!! Warning: short read while loading the AOF file !!! # !!! Truncating the AOF at offset 397856725 !!! # AOF loaded anyway because aof-load-truncated is enabled |
25、持久化问题主要包括两个方面:①fork创建子进程会消耗时间和空间;②子进程重写AOF与RDB文件时,造成CPU、内存、硬盘的消耗。
1)cpu
子进程将数据分批写入文件,属于CPU密集型操作,对单核CPU利用率高。因此会和父进程竞争单核CPU;
在部署了多个Redis实例时,要保证同一时刻只有一个子进程在重写;
不要和其他CPU密集型服务部署在一起。
2)内存
Linux采用写时复制技术,父子进程共享相同物理内存页,父进程写时,会将要修改的页创建副本,这时会消耗内存,这是RDB的内存消耗。
此外AOF还要重写缓冲区aof_rewrite_buf,还要额外的内存消耗。
优化:
①如果部署了多个Redis,要保证同时只有一个子进程在工作;
②避免在大量写入时做子进程重写,这会导致父进程维护大量页副本,造成内存消耗。
3)硬盘
子进程会将AOF、RDB文件写入硬盘,对硬盘造成写入压力。
优化:
①不要和其他高硬盘负载服务放在一起;
②AOF重写时会消耗大量磁盘IO,开启配置no-appendfsync-on-rewrite,表示在AOF重写期间不做fsync操作;
③Redis高流量写入时,此时Redis的瓶颈在AOF同步硬盘上;
④对于单机多Redis实例的情况,可以配置不同实例分盘存储AOF文件,分摊硬盘写入压力。
26、AOF追加阻塞AOF持久化的常用策略是everysec。
这种策略下,Redis会用另一条线程每秒执行fsync同步硬盘,当系统硬盘资源繁忙时,会造成Redis主线程阻塞(当距离上次AOF同步时间超过2s时,会阻塞,直到同步完成)。
27、多实例
多实例:一台机器部署多个Redis实例。
当多个实例开启AOF重写,彼此会产生对CPU和IO的竞争,需要将子进程的工作进行隔离。对于子进程运行状况,可以通过info Persistence中的指标进行监控。
基于这些指标,多个Redis实例通过外部轮询方式控制AOF重写操作的执行。
实际上机器内的每个Redis实例的AOF重写是串行化执行的。
28、持久化时会造成主线程阻塞的情况有:fork阻塞(通过fork创建子进程)、AOF追加阻塞(第3节8)部分)。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性