redis学习笔记-持久化
前言
redis持久化有两种方式:RDB和AOF。分别对应着全量复制和增量复制。深刻理解各自的实现方式及适用场景对redis的使用和运维十分重要。下面就分别介绍。
RDB持久化
RDB持久化即将当前Redis实例中的数据快照全部保存的磁盘的过程。可手动触发,也可根据配置自动触发。
手动触发
手动触发有两个命令可以选择: save
和bgsave
。两者区别在于save
是阻塞的,复制完成前会阻塞客户端命令执行,目前已经废弃。bgsave
是非阻塞的。执行过程中redis进程会fork出一个子进程负责复制任务,而主进程依然响应客户端请求命令。对应bgsave
命令,阻塞只存在于fork
过程中。大大减小了阻塞的时间。
如图,上次rdb同步时间在17:20
执行命令bgsave
后, 返回 Background saving started
,
查看rdb文件更新时间,已变为当前时间。
自动触发
若使用自动触发,需配置save参数。格式 save m n
。查看redis配置文件可看到相关配置。
save 900 1
save 300 10
save 60 10000
配置 save m n
含义为当数据在m秒内发生n次修改,自动执行bgsave
命令进行RDB持久化。如save 900 1
表示在900秒(15分钟内)数据有至少1次修改,则触发RDB。多个配置情况下,只要符合任一条件即可。
具体在实现上,redis服务器会记录自上次RDB备份后,有多少次修改。每100ms检查一次,看是否有符合条件的save配置,有则再次执行DRB.
另外,当在客户端执行关闭服务器命令shutdown
,若redis没有开启AOF,则自动执行bgsave
进行备份。
RDB流程
由于save的持久化命令会发送阻塞,所以目前基本是用bgsave命令触发的RDB,关于bgsave的流程如下图所示。
简要流程如下
- redis进程(即图中主进程)收到
bgsave
命令后,调用fork命令创建子进程。期间主进程是阻塞的(图中1,2)。 - 主进程创建子进程完成后,返回
Background saving started
,不再阻塞,照常响应客户端命令。(图中3) - 子进程开启RDB复制任务,根据主进程内存快照进行备份。(图4)
- 子进程完成后通知主进程,RDB任务完成。(图5)
简要来说是父进程fork出了一个子进程用于持久化任务,父进程仍响应客户端请求,耗时的RDB让子进程来做。这样的确可以大大减少阻塞时间,可有一个问题是,主进程还在接受请求,子进程怎样进行复制?难道子进程再复制一份主进程的内存用于复制,这样内存岂不是要翻倍?肯定不能这样。要是子进程共享父进程的内存,那怎样保证边接受请求边复制呢?
事实上,fork操作使用写时复制(copy-on-write)技术实现。创建的子进程共享父进程地址空间,当只有需要写入时才会复制地址空间。也就是说,redis的子进程使用共享的内存快照完成RDB备份,而主进程当收到写请求后,会将涉及到的内存页复制出一份副本,在此副本进行修改。当子进程RDB完成后,通知主进程更换副本,RDB就此完成。
RDB参数设置及注意点
- RDB文件路径由
dir
设置,文件名由dbfilename
指定。RDB文件为二进制文件,可开启压缩处理,压缩后文件体积可大大缩小。使用rdbcompression
参数指定,默认为yes
。 - RDB文件表示某时刻的redis内存快照,文件也相对较小,适用于备份,全量复制等场景。不适合实时持久化。
以下是redis配置文件有关rdb的默认配置
## 是否开启rdb文件压缩
rdbcompression yes
## rdb文件效验
rdbchecksum yes
## rdb文件名
dbfilename redis-dump-127.0.0.1-6379.rdb
## 备份文件储存目录
dir /usr/local/var/db/redis/
AOF持久化
AOF主要用于解决redis实时持久化的问题。方式为实时将redis所有写命令记录下来,用于以后恢复及备份。
开启AOF
aof默认是关闭的,使用appendonly yes
开启。用appendfilename
设置aof文件名。同rdb一样,dir
表示aof储存目录。
在配置文件设好后重启redis,即开启了aof功能。可使用info persistence
可查看redis持久化的参数,如下
aof_enabled:1
表示已开启aof功能。
AOF流程
AOF写入日志的直接就是文本格式。如set hello world
这个命令,在appendonly.aof
文件中储存的就是
*3
$3
SET
$5
hello
$5
world
这就是Redis客户端与服务器通信的的序列化协议(RESP),每行用\r\n
分割,*n表示参数个数,$n表示字节数,十分简单明了。AOF直接使用协议格式可让redis直接识别。无需特殊处理,避免二次处理的开销,也方便修改。
AOF的流程如下
- 每当遇到写命令时,执行完命令后,会将此命令再写入一个由AOF维护的缓存区(aof_buf)。
- AOF缓冲区根据指定策略将数据刷到硬盘保存落地。
用流程图表示就是
为什么不直接将命令写入磁盘很容易理解,这里需关注的是以什么策略从缓冲区写到磁盘。redis提供了3种策略。
策略 | 说明 |
---|---|
always | 每次命令写入缓冲区后都同步刷新到硬盘(使用fsync命令刷新磁盘),此方式最安全,但也是最慢的,因为每次写数据都要同步硬盘 |
everysec | 每次写命令调用系统命令write操作,write只会将数据写入系统缓冲区,然后由专门的线程每秒同步刷盘 |
no | 每次写命令调用系统命令write操作写入系统缓冲区,具体刷盘时间由操作系统决定,性能是最高的,但数据安全性不能保证 |
通常,我们选择everysec
作为刷盘策略,也是redis默认配置,可以兼顾性能和数据安全。
AOF追加阻塞
一般我们使用everysec
的同步刷盘策略。此时会有一个专门的线程每秒执行一次刷盘操作。为保证数据安全性,redis主进程会比对上次刷盘时间与当前时间的差值,如果大于2秒,则阻塞以等待刷盘完成。
也就是说,如果硬盘性能很差,fsync
执行太慢,会造成redis阻塞。以保证硬盘的数据不被真实内存数据落的太远(最大2秒的数据差)。
AOF重写机制
刚才提到,AOF直接使用redis的序列化协议进行备份,一段时间后,aof文件会很大。为解决此问题,可配置aof重写机制对原aof文件进行瘦身。
AOF重写的实质即把redis进程的内存数据转为写命令重新生成一份aof文件,以替代原aof文件。
重写后比原aof体积小的原因有以下几点
- 过期数据不再写入
- 命令可合并(
hset k1 f1 v1
,hset k1 f2 v2
可合并hmset k1 f1 v1 f2 v2
) - 重写直接使用内存数据生成,一些无效命令就不会再写入了。(如
set hello a, set hello b
等)
触发方式
AOF可有手动触发和自动触发。
手动触发使用bgrewriteaof
命令触发。
自动触发有关参数及含义如下
## aof重写时此时aof文件与上次aof文件大小百分比
auto-aof-rewrite-percentage 100
## aof重写时文件最小大小,默认64M
auto-aof-rewrite-min-size 64mb
如按默认配置,则发生重写的条件为aof文件大于64M且此时aof文件比上次重写时大100%,即是上次的2倍。
重写流程
- redis主进程收到aof重写命令(bgwriteaof),fork出子进程用于执行aof重写流程(图中1,2)。
- 主进程继续接收客户端请求,将请求写入aof_buf缓冲区。保证原有aof流程正常工作(图中3.1.1和3.1.2)。
- 同时,主进程还会将写命令写入aof_rewrite_aof(aof重写缓冲区),用于补偿aof重写期间丢失的命令(图中3.2)。
- 子进程与此同时进行aof重写过程(图中3.3)
- 子进程完成aof重写后信号通知主进程(图中4)
- 主进程收到子进程完成信号后,将aof_rewrite_aof的命令写入新aof文件(图中5)
- 新aof文件原子替换旧aof文件,aof重写流程结束(图中6)。
aof重写虽然比较复杂,但对比图,也能很容易明白其中流程。这这里有个疑问是为什么主进程在子进程重写时为什么要维护两个缓冲区?只维护一个不行吗?毕竟原有aof文件等到新aof文件生成后也是要替换的。
仔细想想就可知,有必要维护两个缓冲区。主要是需要维护原有aof逻辑不变,以防aof重写失败导致数据丢失。另一点两个缓冲区目的也不同,不宜混淆。
注意:
为防止资源过于竞争,同一时刻只能进行一个AOF重写任务,当进行重写时发现有RDB任务时,也会等RDB完成后进行。
RDB与AOF
加载顺序
在重启时,redis会首先判断是否开启aof,若开启aof且aof文件存在,则加载aof文件进行数据恢复以启动。否则判断rdb文件存在并加载rdb以启动。
性能开销
在进行RDB备份或是AOF重写时,都需要fork出子进程运行任务。此期间会大量消耗CPU,通常子进程对单核CPU的利用率达到90%,因此在有RDB或需AOF重写任务的redis,不要做绑定单核CPU操作,这会导致父子进程对CPU产生竞争。
RDB与AOF比较
- RDB是全量复制的持久化方式,一次性生成内存快照,产生二进制文件。文件读取速度快,生成较慢。适用于数据冷备。
- AOF通过追加生成持久化文件,体积较大,用于数据实时备份。
- redis阻塞的场景在fork子进程和AOF的追加阻塞阶段。