redis的持久化
Redis提供了两种持久化方式:
- RDB
- AOF
RDB
RDB(Redis Database):在指定的时间间隔内对内存中的全量数据进行快照存储。
RDB是一个非常紧凑的单一文件,它保存了某个时间点的全量数据集,非常适用于数据集的备份。保存RDB文件时父进程唯一需要做的就是调用系统函数fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能。
由于RDB的文件内容紧凑,对数据进行了压缩,不像AOF一样数据中存在过期的key和重复的指令,所以在恢复大的数据集的时候,RDB方式会更快一些。
由于RDB每次都是进行全量数据的备份,所有这种操作不可能很频繁,不然会影响IO性能,所以RDB会有一个时间间隔,假如这个时间间隔为5分钟,8点整进行了一次RDB,在8点05分点刚要进行RDB的时候,redis意外停止工作(例如停电),这样就会导致8点到8点05分点这个时间段的数据的丢失,如果你能容忍这种数据的丢失的情况,那么就可以使用RDB,一般来说,redis用来做缓存,数据丢失了没关系,可以从数据库中再次加载到redis中。但是如果把redis当成数据库使用的话,那么就不适合使用RDB这种持久化方式了。
RDB需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求。如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒,AOF也需要fork,但是你可以调节重写日志文件的频率来调节。
子进程将数据集写入到一个临时RDB文件中,当子进程完成对新RDB文件的写入时,Redis用新RDB文件替换原来的RDB文件,并删除旧的RDB文件。
fork的原理
RDB时需要考虑的两个问题:
- 假如8点开始RDB,整个过程需要持续5分钟,那么在这5分钟有些key被修改了,有些key被删除了,那么这些key会不会被存储到RDB文件中去呢?
- 如果有用另外一个进程来负责持久化,那么是不是需要将整个redis进程的内存拷贝一份,时间成本会不会很大,内存空间是不是够用?
上面的这两个问题都能从fork的原理中找到答案。
fork是linux提供的一个系统调用,其声明如下:
#include <unistd.h>
pid_t fork(void);
返回值pid_t
是进程描述符,实质就是一个int,如果fork函数调用失败,返回一个负数-1,调用成功则返回两个值:0和子进程ID,对于子进程来说返回值为0,对于父进程来说返回的是子进程的ID。
下面通过一个c语言代码来演示fork的原理:
#include<stdio.h>
#include<unistd.h>
int main() {
int i = 10;
pid_t pid = fork();
if(pid > 0) {
i--;
printf("i am parent process, id:%d\n", getpid());
printf("i am parent process, i=%d\n", i);
sleep(20);
} else if(pid == 0) {
printf("i am child process, pid:%d, ppid:%d\n", getpid(), getppid());
i++;
printf("i am child process, i=%d\n", i);
sleep(20);
}
}
运行结果如下:
i am parent process, id:14848
i am parent process, i=9
i am child process, pid:14849, ppid:14848
i am child process, i=11
说明:
- fork系统调用之后,父进程和子进程一般会交替执行,并且它们处于不同空间中。
- fork()的子进程并不是从头开始,因为在fork()之前,父进程已经为子进程搭建好了运行环境了,所以直接从有效代码处开始。
- fork底层实现采用了写时拷贝(COW,copy_on_write)技术,父进程和子进程共享页帧而不是复制页帧。然而,只要页帧被共享,它们就不能被修改,即页帧被保护。无论父进程还是子进程何时试图写一个共享的页帧,就产生一个异常,这时内核就把这个页复制到一个新的页帧中并标记为可写。原来的页帧仍然是写保护的:当其他进程试图写入时,内核检查写进程是否是这个页帧的唯一属主,如果是,就把这个页帧标记为对这个进程是可写的。
AOF
AOF(Append Of File):记录每次对服务器写的操作,以redis协议追加保存每次写的操作到文件末尾。
由于AOF中会存在一些过期的key,以及对一个key的多次写操作都会以redis协议格式追加在文件中尾部,并没有进行压缩,所以会导致AOF文件的体积不断变大,为了解决文件过大的问题,AOF会自动地在后台对文件进行重写。
Redis可以在AOF文件体积变得过大时,重写后的新AOF文件包含了恢复当前数据集所需的最小命令集合。整个重写操作是绝对安全的,因为Redis在创建新AOF文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的AOF文件也不会丢失。而一旦新AOF文件创建完毕,Redis就会从旧AOF文件切换到新AOF文件,并开始对新AOF文件进行追加操作。
AOF文件有序地保存了对数据库执行的所有写入操作,这些写入操作以Redis协议的格式保存,因此AOF文件的内容非常容易被人读懂,对文件进行分析也很轻松,导出AOF文件也非常简单。如果你不小心执行了FLUSHALL命令,但只要AOF文件未被重写,那么只要停止服务器,移除AOF文件末尾的FLUSHALL命令,并重启Redis,就可以将数据集恢复到FLUSHALL执行之前的状态。
对于相同的数据集来说,AOF文件的体积通常要大于RDB文件的体积,根据所使用的fsync策略,AOF的速度可能会慢于RDB。
实战
在/etc/redis/6379.conf/中与RDB相关的配置:
# 60秒内有至少有1000个键被改动时,自动触发RDB
# 300秒内有至少有10个键被改动时,自动触发RDB
# 900秒内有至少有1个键被改动时,自动触发RDB
save 900 1
save 300 10
save 60 10000
# 异步重写失败了,会停止写入
stop-writes-on-bgsave-error yes
# 开启rdb文件压缩
rdbcompression yes
# 加载rdb和保存rdb时文件进行校验
rdbchecksum yes
# rdb存储的文件名,保存路径在下面
dbfilename dump.rdb
# 在没有持久化的情况下删除复制中使用的RDB文件,通常情况下保持默认即可。
rdb-del-sync-files no
# RBD文件保存的目录
dir /usr/local/redis/data/6379
Redis默认开启RDB,除了配置文件中配置的自动保存规则外,也可以通过调用SAVE(同步保存)或者BGSAVE(异步保存),手动让Redis进行数据集保存操作。
在/etc/redis/6379.conf/中与AOF相关的配置:
# AOF默认关闭
appendonly no
# The name of the append only file (default: "appendonly.aof")
# AOF保存的文件名
appendfilename "appendonly.aof"
# 同步的模式
# always:每收到一个写请求就同步到文件中,这样就不会丢失数据,但是性能低(每次都有磁盘IO)。
# everysec:每秒同步一次,最多丢失一秒的数据,性能介于always和no之间。
# no:no并不是不同步,而是redis只把数据写到操作系统的缓冲区中,具体什么时候写到磁盘依赖操作系统,linux下缓存区大小为512k,缓冲区买了才会刷新到磁盘,性能最好。
# appendfsync always
appendfsync everysec
# appendfsync no
# AOF重写期间不进行同步,避免大量的磁盘IO,影响性能
no-appendfsync-on-rewrite no
# 配置重写触发机制,当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
# 如果该配置启用,在加载时发现aof尾部不正确时会向客户端写入一个log,但是会继续执行,如果设置为no,发现错误就会停止,必须修复后才能重新加载。
aof-load-truncated yes
# AOF文件的内容前面是否使用RDB
aof-use-rdb-preamble yes
Redis默认不开启AOF。
AOF和RDB可以同时开启,但是在启动时Redis只会从AOF中加载数据,因为AOF的数据更加齐全。
先开启AOF,然后用客户端向redis中写入下面的key:
127.0.0.1:6379> set name morris
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> set sex man
OK
然后在/usr/local/redis/data/6379目录下会多出一个appendonly.aof
文件,文件的内容如下:
*2
$6
SELECT
$1
0
*3
$3
set
$4
name
$6
morris
*3
$3
set
$3
age
$2
18
*3
$3
set
$3
sex
$3
man
在老的版本中,AOF的重写操作只会删除过期的key,合并相同的key,最终生成的文件还是一个纯指令的日志文件。
在新的版本中,AOF的重写操作会将老的数据以RDB的格式存储到aof文件中的前面,后面将增量的数据以redis协议的格式Append到文件尾,此时的AOF文件是一个混合体。
当然也可以使用BGREWRITEAOF
命令手动触发AOF的重写
127.0.0.1:6379> BGREWRITEAOF
Background append only file rewriting started
127.0.0.1:6379> set city hongkong
OK
127.0.0.1:6379> set country china
OK
此时再来看appendonly.aof
文件,文件的内容如下:
REDIS0009 redis-ver6.0.6
redis-bitseèRused-mem4
𮤭preambleþmemorrissexmanageÿӬᄬd{*2
$6
SELECT
$1
0
*3
$3
set
$4
city
$8
hongkong
*3
$3
set
$7
country
$5
china
AOF文件前面的内容使用了RDB的格式进行存储,使得文件的体积更小。