Redis RDB及AOF持久化
1、Question1:什么叫持久化?(What)
持久化是将程序数据在持久状态和瞬时状态间转换的机制。通俗的讲,就是瞬时数据(比如内存中的数据,是不能永久保存的)持久化为持久数据(比如持久化至数据库中,能够长久保存)。
2、Question2:Redis不就是数据库吗,为什么要持久化?(Why)
Redis确实是一个数据库,但它是基于内存的数据库。
类似于你用代码写了个变量String a = 123
这个a在程序运行时存入内存中,等程序停止,你还能找到a吗?显然是不行的。
所以Redis需要有一个持久化的功能,将它存储的数据记到硬盘上。
3、Question3:Redis如何进行持久化?(How)
Redis为了能把数据写到硬盘中,它提供了两种持久化功能,分别是RDB (redis database)、AOF (append only file)。下面是对两种方法的说明。
3.1、RDB持久化是什么?
RDB持久化是将Redis数据库中的数据快照进行备份,将内存中的数据库状态保存至磁盘,避免数据丢失。
image.png
RDB
3.2、RDB持久化怎么做?
RDB持久化既可以手动执行,也可以根据服务器配置选项定期执行。
1、手动执行
手动执行有两个命令可以选择,save
bgsave
save
命令会阻塞Redis服务器进程,直到RDB文件创建完毕。阻塞期间,服务器不能处理任何命令。这个命令估计没人敢用。
bgsave
命令是非阻塞的,它会创建一个子进程,由子进程负责创建RDB文件,服务器进程继续处理命令请求,这个命令看着比较靠谱。
很明显,bgsave
命令执行后,客户端会收到"Background saving started",并且可以执行命令。bgsave
命令执行期间,客户端发送的save
及bgsave
命令会被服务器拒绝。
用伪代码表示:
def SAVE():
# 创建RDB文件
rdbSave()
def BGSAVE():
# 创建子进程
pid = fork() ;
if pid == 0 :
# 子进程负责创建RDB文件
rdbSave()
# 完成后向父进程发送信号
signal_parent()
elif pid > 0:
# 父进程继续处理命令请求,并通过轮询等待子进程的信号
handle_request_and_wait_signal()
else:
# 处理出错情况
handle_fork_error()
2、服务器配置
Redis允许用户通过设置服务器配置的save选项(这个不是指save
命令),让服务器每隔一段时间自动执行一次bgsave
命令。 save选项可以被配置多个,只要有一个条件满足,就会执行bgsave
命令。配置文件为redis.conf
。在文件中可以找到以下配置:
save 900 1
save 300 10
save 60 10000
该配置表示只要满足以下三个条件中的任意一个,bgsave
命令就会被执行:
- 服务器在900秒之内,对数据库进行了至少1次修改。
- 服务器在300秒之内,对数据库进行了至少10次修改。
- 服务器在60秒之内,对数据库进行了至少10000次修改。
将save选项改为save 60 1
,往数据库写入一条数据,可以看到日志会输出以下信息:
log
3、RDB文件如何载入?
将备份文件 (dump.rdb) 移动到 redis 安装目录并启动服务即可,redis在启动时就会自动加载文件数据至内存了。Redis 服务器在载入 RDB 文件期间,会一直处于阻塞状态,直到载入工作完成为止。查看启动日志。
log
4、RDB持久化的优势与劣势
优势:
- RDB是一个非常紧凑(compact)的文件,它保存了redis 在某个时间点上的数据集。这种文件非常适合用于进行备份和灾难恢复。
- 生成RDB文件的时候,redis主进程会fork()一个子进程来处理所有保存工作,主进程不需要进行任何磁盘IO操作。
- RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
劣势:
- RDB方式数据没办法做到实时持久化/秒级持久化。因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作(内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑),频繁执行成本过高(影响性能)
- RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题(版本不兼容)
- 在一定间隔时间做一次备份,所以如果redis意外down掉的话,就会丢失最后一次快照后的所有修改(数据有丢失)
5、RDB持久化的原理
Redis有个服务器状态结构:
struct redisService{
//1、记录保存save条件的数组
struct saveparam *saveparams;
//2、修改计数器
long long dirty;
//3、上一次执行保存的时间
time_t lastsave;
}
struct saveparam{
//秒数
time_t seconds;
//修改数
int changes;
};
saveparams属性是一个数组,数组中的每个元素都是一个saveparam结构,当设置save选项为以下条件时:
save 900 1
save 300 10
save 60 10000
saveparams数组是如下结构:
dirty计数器记录距离上次执行
save
命令或者bgsave
命令后,服务器进行了多少次修改(写入、删除、更新等)lastsave属性是一个UNIX时间戳,记录上一次成功执行
save
或者bgsave
命令的时间。通过这两个属性,服务器没执行一次修改,就将dirty计数器加一。成功执行
save
或者bgsave
命令后,将执行时间记录到lastsave属性中。服务器有个周期性函数serverCron默认每隔100毫秒就会执行一次。它有一项工作就是检查save选项所保存的条件是否满足,满足的话,就执行一次
bgsave
命令。伪代码表示:
def serverCron():
#....
#遍历所有保存条件
for saveparam in server.saveparams:
#计算距离上次保存操作有多少秒
save_interval = unixtime_now() - server.lastsave ;
# 如果数据库状态的修改次数超过条件所设置的次数
# 并且距离上次保存的时间超过条件所设置的时间
# 那么执行操作
if server.ditry >= serverparam.changes and
save_interval > server.seconds:
BGSAVE()
#....
3.3、AOF持久化是什么?
AOF(Append only file) 持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。
aof
3.4、AOF持久化怎么做
将redis.conf配置项appendonly no
改为appendonly yes
,重启redis即可。
3.5、AOF原理
AOF持久化分为三个步骤 命令追加(append)、文件写入、文件同步(sync)
这里有很多人会迷惑,文件写入跟文件同步的区别两个难道不是一个操作吗?
对用户而言,是一个操作,就是将内容写入文件。但是操作系统为了提高效率,会将写入数据暂时保存在一个内存缓存中,等到缓存区被填满或者到了一定时间时,才将数据写入到磁盘中。所以,如果只是将数据写入文件而不同步,还是存在丢失数据的风险。
当AOF持久化功能打开后,服务器在执行完一个写命令后,会以协议格式将命令保存至redisServer的aof_buf缓冲区中。
struct redisServer{
//aof缓冲区
sds aof_buf ;
}
AOF有个配置参数appendfsync是决定什么时候将aof_buf缓冲区的数据写入和保存到AOF文件里面。有三个参数可选:
image.png
参数 | 描述 |
---|---|
always | 将aof_buf缓冲区的所有内容写入并同步到AOF文件中。每个事件循环都执行效率最慢,安全性最高。宕机时丢失数据会最少,会丢一个事件循环的数据。 |
everysec | 默认选项。将aof_buf缓冲区的所有内容写入到AOF文件中,如果距离上次同步AOF文件超过一秒钟,再次同步。宕机时,有可能会丢失一秒钟数据。 |
no | 这个配置项不是说不保存数据,而是每个事件循环都将aof_buf缓冲区的所有内容写入到AOF文件,是否同步取决于操作系统。速度最快,宕机时丢的数据最多。 |
3.5、AOF存在的问题
1. AOF存在的问题
AOF不同于RDB持久化,RDB保存的都是有效数据,而AOF保存的是执行命令,随着服务器运行,AOF文件会越来越大,这是它的一个弊端。
例如,RPUSH list "a" "b"
RPUSH list "c" "d"
RDB只会保存数据list a b c d
一条记录,AOF会保存两条命令。
2. AOF重写,解决AOF文件过大问题
redis提供一种方式可以解决上面的问题:AOF重写
就是每隔一段时间重新生成新的AOF文件代替现有的AOF文件。新的AOF文件不需要去对现有的AOF文件进行任何操作,它是通过读取数据库状态实现的。有两个参数会触发AOF重写,当AOF文件大小是上次rewrite后大小的一倍且文件大于64M时触发。一般都设置为3G,64M太小了。
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
AOF重新会从数据库中读取键现有的值,然后用一条命令去记录键值对。
例如,RPUSH list "a" "b"
RPUSH list "c" "d"
现有的AOF是保存了两条命令,AOF重写文件会用一条命令代替上面的两条命令,所以新的AOF文件会比现有的AOF文件占用空间小。
3. AOF重写问题
redis将AOF重新程序放到子进程里进行,不至于影响主线程,但随之而来会存在一个问题,子进程在AOF重新期间,新的命令无法进入子线程被写入AOF文件中。
4. AOF重写缓存区,解决AOF重写过程中无法保存新命令问题
redis为了解决重写的这个问题,设置了一个AOF重写缓冲区,它会记录AOF重写期间的命令,在子进程完成重写后,将AOF重写缓冲区的命令写入新的AOF文件中,并替换现有的AOF文件。