RDB是Redis默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中,即在指定目录下生成一个dump.rdb文件,Redis重启会通过加载dump.rdb文件恢复数据
RDB持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB文件中
RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态
有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE
SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求
BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程继续处理命令请求
RDB文件的载入工作是在服务器启动时自动执行的,所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件存在,它就会自动载入RDB文件
因为AOF文件的更新频率通常比RDB文件的更新频率高,所以:
创建RDB文件的实际工作由rdb.c/rdbSave函数完成,载入RDB文件的实际工作由rdb.c/rdbLoad函数完成
1)、SAVE命令执行的服务器状态
当SAVE命令执行时,Redis服务器会被阻塞,所以当SAVE命令正在执行时,客户端发送的所有命令请求都会被阻塞。只有在服务器执行完SAVE命令、重新开始接受命令请求之后,客户端发送的命令才会被处理
2)、BGSAVE命令执行时的服务器状态
因为BGSAVE命令的保存工作是由子进程执行的,所以在子进程创建RDB文件的过程中,Redis服务器仍然可以继续处理客户单的命令请求,但是在BGSAVE命令执行期间,服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令的方式会和平时有所不同
1)在BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝,服务器禁止SAVE命令和BGSAVE命令同时执行是为了避免父进程和子进程同时执行两个rdbSave调用,防止产生竞争条件
2)在BGSAVE命令执行期间,客户端发送的BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE命令也会产生竞争条件
3)BGREWRITEAOF和BGSAVE两个命令不能同时执行:
因为BGREWRITEAOF和BGSAVE两个命令的实际工作都由子进程执行,所以这两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是一个性能方面的考虑——并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写入操作
3)、RDB文件载入时的服务器状态
服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止
用户可以通过save选项设置多个保存条件,但只要其中任意一个条件被满足,服务器就会执行BGSAVE命令
如果向服务器提供以下配置:
save 900 1
save 300 10
save 60 10000
那么只要满足以下三个条件中的任意一个,BGSAVE命令就会被执行:
1)、设置保存条件
当Redis服务器启动时,用户可以通过指定配置文件或者传入启动参数的方式设置save选项,如果用户没有主动设置save选项,那么服务器会为save选项设置默认条件:
save 900 1
save 300 10
save 60 10000
服务器程序会根据save选项所设置的保存条件,设置服务器状态redisServer结构的saveparams属性:
struct redisServer{
// 记录了保存条件的数组
struct saveparam *saveparams;
// ...
};
saveparams属性是一个数组,数组中的每个元素都是一个saveparam结构,每个saveparam结构都保存了一个save选项设置的保存条件:
struct saveparam{
// 秒数
time_t seconds;
// 修改数
int changes;
};
2)、dirty计数器和lastsave属性
struct redisServer{
// 修改计数器
long long dirty;
// 上一次执行保存的时间
time_t lastsave;
// ...
};
当服务器成功执行一个数据库修改命令之后,程序就会对dirty计数器进行更新:命令修改了多少次数据库,dirty计数器的值就增加多少
3)、检查保存条件是否满足
Redis的服务器周期性操作函数serverCron默认每隔100毫秒就会执行一次,该函数用于对正在运行的服务器进行维护,它的其中一项工作就是检查save选项所设置的保存条件是否已经满足,如果满足的话,就执行BGSAVE命令
程序会遍历并检查saveparams数组中的所有保存条件,只要有任意一个条件被满足,那么服务器就会执行BGSAVE命令
AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的,默认不开启
1)、命令追加
当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾
struct redisServer{
// AOF缓冲区
sds aof_buf;
// ...
};
2)、AOF文件的写入与同步
Redis的服务器进程就是一个事件循环,这个循环中的文件事件负责接收客户端的命令请求,以及向客户端发送命令回复,而时间事件则负责执行serverCron函数这样需要定时运行的函数
因为服务器在处理文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区里面,所以在服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面
appendfsync选项的默认值为everysec
AOF持久化的效率和安全性
服务器配置
appendsync
选项的值直接决定AOF
持久化功能的效率和安全性
当
appendsync
的值为always
时,服务器在每个时间循环都要将aof_buf
缓冲区的所有内容写入到AOF
文件,并且同步AOF
文件,所有always
的效率是appendsync
最慢的一个,但是也是最安全的,宕机出现,AOF
持久化也只会丢失一个事件循环中所产生的命令数据当
appendsync
的值为everysec
时,服务器在每个事件循环都要将aof_buf
缓冲区的所有内容写入到AOF
文件,并且每隔一秒就要在子线程中对AOF
文件进行一次同步。效率上足够快,并且就算出现故障宕机,数据库也只会丢失一秒钟的命令数据当
appendsync
的值为no
时,服务器在每个事件循环都要将aof_buf
缓冲区中的所有内容写入到AOF
文件中,至于何时对AOF
文件进行同步,则由操作系统控制。因此该模式速度最快,不过因为这种模式会在缓存中积累一段时间的写入数据,所以单次同步时长是最长的。从平摊的角度上看,no
模式和everysec
模式的效率类似。但是宕机丢失的数据最多
Redis读取AOF文件并还原数据库状态的详细步骤如下:
1)创建一个不带网络连接的伪客户端:因为Redis的命令只能在客户端上下文中执行,而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行AOF文件保存的写命令,伪客户端执行命令的效果和带网络连接的客户端执行命令的效果完全一样
2)从AOF文件中分析并读取出一条写命令
3)使用伪客户端执行被读出的写命令
4)一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被处理完毕为止
为了解决AOF文件体积膨胀的问题,Redis提供了AOF文件重写功能。通过该功能,Redis服务器可以创建一个新的AOF文件来替代现有的AOF文件,新旧两个AOF文件所保存的数据库状态相同,但新AOF文件不会包含任何浪费空间的冗余命令,所以新AOF文件的体积通常会比旧AOF文件的体积要小很多
1)、AOF文件重写的实现
AOF重写功能的实现原理:首先从数据库中读取键现在的值,然后用一条命令去记录键值对,代替之前记录这个键值对的多条命令
aof_rewrite函数生成的新AOF文件只包含还原当前数据库所必须的命令,所以新AOF文件不会浪费任何硬盘空间
2)、AOF后台重写
aof_rewrite函数可以很好地完成创建一个新AOF文件的任务,但是因为这个函数会进行大量的写入操作,所以调用这个函数的线程将被长时间阻塞,因为Redis服务器使用单个线程来处理命令请求,所以如果由服务器直接调用aof_rewrite函数的话,那么在重写AOF文件期间,服务器将无法处理客户端发来的命令请求
Redis将AOF重写程序放到子进程里执行,这样做可以同时达到两个目的:
Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在服务器创建子进程之后开始使用,当Redis服务器执行完一个写命令之后,它会同时将这个写命令发送给AOF缓冲区和AOF重写缓冲区
在子进程执行AOF重写期间,服务器进程需要执行以下三个工作:
1)执行客户端发来的命令
2)将执行后的写命令追加到AOF缓冲区
3)将执行后的写命令追加到AOF重写缓冲区
这样以来可以保证:
当子进程完成AOF重写工作之后,它会向父进程发送一个信号,父进程在接收到该信号之后,会调用一个信号处理函数,并执行以下工作:
1)将AOF重写缓冲区中的所有内容写入到新AOF文件中,这时新AOF文件所保存的数据库状态将和服务器当前的数据库状态一致
2)对新的AOF文件进行改名,原子地覆盖现有的AOF文件,完成新旧两个AOF文件的替换
参考:https://cloud.tencent.com/developer/news/307359