Redis是内存数据库,一旦服务器进程退出,服务器中的数据也会丢失。因此需要持久化功能将数据存储到磁盘中,即使重启了保证数据不丢失。
Redis的持久化方式有两种:RDB、AOF。
AOF文件的更新频率通常比RDB文件的更新频率高,所以如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态。只有在AOF关闭,服务器才会使用RDB文件来还原数据库状态。
RDB 持久化 RDB 持久化功能主要是生成一个经过压缩的二进制文件(RDB文件)来保存数据,然后在需要还原的时候通过读取RDB文件来恢复数据库状态。
创建与载入
生成RDB文件有两个Redis命令:SAVE、BGSAVE。
载入时机:RDB文件的载入工作是在服务器启动时自动执行的,所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件存在,就会自动载入。
服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止。
SAVE SAVE 命令会阻塞 Redis 服务器进程,直到RDB文件创建完毕,在阻塞期间,服务器不能处理任何命令请求。
def SAVE():
rdbSave()
BGSAVE BGSAVE 命令不阻塞父进程,会fork一个子进程,然后由子进程负责创建RDB文件,父进程继续处理命令请求。
def BGSAVE():
pid = fork()// 创建子进程
if pid == 0:
rdbSave()// 创建RDB文件
info_parent()// 向父进程发送完成信号
elif pid > 0:
handle_request_and_wait_data()// 父进程继续处理命令请求,并通过轮询等待子进程的数据
else:
handle_fork_error()// 处理出错的情况
在BGSAVE命令执行期间,客户端发送的SAVE命令、BGSAVE命令会被服务器拒绝,而BGREWRITEAOF命令会被延迟到BGSAVE命令执行完成。此外,若BGREWRITEAOF正在运行,那BGSAVE命令会被拒绝。
RDB文件结构
文章图片
- REDIS:存储的是二进制数据,用于标志所载入的文件是RDB文件。
- db_version:RDB文件的版本号。
- databases:包含多个非空数据库中的键值对数据,数据库之间分开。若所有数据库都为空,则不会有databases这个部分。
文章图片
其中一个database内容如下:包含SELECTDB的标志,第二个存储的是数据库号,最后存储的是数据库键值对。
文章图片
- EOF:标志正文结束。
- check_sum:校验和。
save选项 因为BGSAVE命令可以在不足赛服务器进程的情况下执行,所以Redis允许用户通过设置服务器配置save选项,让服务器每隔一段时间自动执行一次BGSAVE命令。
语法:save n m
解释:服务器在n(s)的时间范围内,至少进行了m次修改。
例如:save 900 1 表示服务器在900s内至少进行了1次修改。
用户通过save选项可以设置多个保存条件,只要多个条件中的一个条件满足,就会触发BGSAVE命令的执行。
dirty计数器和lastsave属性 dirty计数器记录距离上一次成功执行SAVE命令或者BGSAVE命令之后,服务器对数据库状态进行的修改次数。(若一条命令向集合添加了n个元素,那么计数器会+n,而不是+1)
lastsave属性是UNIX时间戳,记录了服务器上一次成功执行SAVE命令或BGSAVE命令的时间。
文章图片
检查保存条件是否满足 Redis的服务器周期性操作函数serverCron默认每隔100ms就会执行一次,该函数用于对正在运行的服务器进行维护,其中一项工作是检查save选项设置的保存条件是否已经满足,满足则执行BGSAVE命令。
// server.c/serverCron()
def serverCron():
for saveparam in server.saveparams:// 遍历所有保存条件
save_interval = unixtime_now()-server.lastsave// 计算距离上次执行保存操作有多少秒
// 数据库状态的修改次数超过条件所设置的次数 且距离上次保存的时间超过条件所设置的时间
if server.dirty >= saveparam.changes and save_interval > saveparam.seconds:
BGSAVE()// 保存
// ...
AOF 持久化 AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。
保存
AOF持久化功能的实现可以分为命令追加append、文件写入、文件同步sync。
追加:在AOF持久化功能处于打开状态的时候,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾。
写入与同步:在服务器每次结束一个事件循环之前,都会调用flushAppendOnlyFile函数,考虑是否需要将aof_buf缓冲区中的内容写入和保存到AOF文件里面。
def eventLoop():
while True:
processFileEvents()// 处理事件 有修改操作会追加命令到aof_buf缓冲区
processTimeEvents()// 处理时间时间
flushAppendOnlyFile() // 判断是否需要将aof_buf的内容写入aof文件
其中 flushAppendOnlyFile() 中判断是否要写入和同步只要由选项 appendfsync 的值决定,可选值有以下:
- always:总是写入和同步。
- everysec:默认值。总是写入。上次同步AOF文件的时间距离现在超过1s时进行同步,并且这个同步操作是由一个线程专门负责的。(同步时间太长会导致服务器宕机时丢失许多数据)
- no:总是写入,不同步。同步由操作系统决定。
【数据库|Redis(持久化)】
文章图片
创建伪客户端是因为:Redis命令只能在客户端上下文中执行。创建客户端相当于一个载体。
AOF 重写
我们在使用AOF持久化的时候,进行一次修改操作就写入一条命令到AOF文件中,随着时间流逝,这个文件的内容会很多,文件体积过大可能会造成不好的影响,且还原数据花费的时间就更多。但在AOF文件中,可能存在很多冗余的命令,例如对一个集合元素进行能多次新增、删除,也许一条命令就可以达到持久化数据的效果,因此我们需要重写AOF内容来减少文件的内容。
AOF重写并不是通过读取原本的AOF文件来实现的,而是读取服务器当前的数据库状态来实现的,通过读取数据转化为Redis命令,然后写入新的AOF文件。
为了避免AOF重写的时候阻塞服务器,Redis决定将AOF重写程序放到子进程里执行。这时候又有一个新的问题,如果在重写过程中,对已经重写部分的数据进行修改了,那重写的AOF不就不正确了?
为解决这个数据不一致的问题,Redis服务器设置了一个AOF重写缓冲区,从重写的子进程创建后开始使用,在重写过程中,只要执行了修改操作,会额外将命令追加到AOF重写缓冲区。在AOF重写完成后,子进程会给父进程一个信号,这时候父进程会进行阻塞,然后将AOF重写缓冲区的命令在追加到新的AOF文件中,最后用新的AOF文件替换原本旧的AOF文件。
文章图片
参考 参考《Redis设计与实现》第二部分:单机数据库的实现
推荐阅读
- #|redis底层数据结构详解
- NoSql|Redis - 底层数据结构与持久化简述
- Redis优化
- centos 7 安装lrzsz实现上传下载文件到服务器
- 数据库|oracle数据库排序后获取第一条数据
- ASP.NET MVC – SQL 数据库简介
- 部署Squid 代理服务器
- 数据库的导入和授权用户
- redis|Redis实现登录注册