Redis持久化(一)-RDB文件的创建和载入

本文详细介绍了Redis RDB文件的创建与管理,包括SAVE与BGSAVE命令的区别,RDB文件载入流程,以及数据丢失风险。讨论了RDB的优点和缺点,以及在服务器状态和数据持久化策略上的关键点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

Redis是一个键值对数据库服务器,服务器中通常包含着任意个非空数据库,而每个非空数据库中又可以包含任意个键值对,为了方便起见,将 服务器中的非空数据库 以及 它们的键值对 统称为 数据库状态

因为Redis是内存数据库,它将 自己的 数据库状态 储存在 内存里面,所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见

为了解决这个问题,Redis提供了RDB持久化功能,这个功能可以将Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失

RDB持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将 某个时间点上的 数据库状态(所有的键值对) 保存到 一个RDB文件中

RDB持久化功能所生成的 RDB文件 是一个 经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态
在这里插入图片描述

在这里插入图片描述

因为RDB文件是保存在硬盘里面的,所以即使Redis服务器进程退出,甚至运行Redis服务器的计算机停机,但只要RDB文件仍然存在,Redis服务器就可以用它来还原数据库状态

RDB文件的创建

有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE
SAVE命令 会阻塞 Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求:

redis> SAVE       //等待直到RDB文件创建完毕
OK

SAVE命令复杂度:O(N)

SAVE命令直接阻塞服务器进程的做法不同,BGSAVE命令会派生出一个子进程,然后 由子进程 负责创建RDB文件,服务器进程(父进程)继续处理命令请求

redis> BGSAVE     //派生子进程,并由子进程创建RDB文件
Background saving started

BGSAVE命令复杂度:O(N),N为Redis服务器所有数据库包含的键值对总数量

创建RDB文件的实际工作由rdb.c/rdbSave函数完成,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()

RDB文件的管理

RDB文件保存在dir配置指定的目录下,文件名通过dbfilename配置指定。可以通过执行config set dir {newDir}config set dbfilename {newFileName}运行期动态执行,当下次运行时RDB文件会保存到新目录

当遇到坏盘或磁盘写满等情况时,可以通过config set dir {newDir}在线修改文件路径到可用的磁盘路径,之后执行bgsave进行磁盘切换,同样适用于AOF持久化文件

压缩:Redis默认采用LZF算法对生成的RDB文件做压缩处理,压缩后的文件远远小于内存大小,默认开启,可以通过参数config set rdbcompression {yes|no}动态修改

虽然压缩RDB会消耗CPU,但可大幅降低文件的体积,方便保存到硬盘或通过网络发送给从节点,因此线上建议开启

校验:如果Redis加载损坏的RDB文件时拒绝启动,并打印如下日志:

# Short read or OOM loading DB. Unrecoverable error, aborting now.

这时可以使用Redis提供的redis-check-dump工具检测RDB文件并获取对应的错误报告

RDB文件的载入

和使用SAVE命令或者BGSAVE命令创建RDB文件不同,RDB文件的载入工作 是在 服务器启动时 自动执行的
所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件存在,它就会自动载入RDB文件

因为AOF文件的更新频率通常比RDB文件的更新频率高,所以:

  • 如果服务器开启了AOF持久化功能,那么服务器会优先使用AOF文件来还原数据库状态
  • 只有在AOF持久化功能处于关闭状态时,服务器才会使用RDB文件来还原数据库状态

服务器判断该用哪个文件来还原数据库状态的流程如下图所示:
在这里插入图片描述
载入RDB文件的实际工作由rdb.c/rdbLoad函数完成,这个函数和rdbSave函数之间的关系可以用下图表示:
在这里插入图片描述

RBD文件载入流程(重要!!!)

首先,当Redis服务器启动时,它会在工作目录中查找是否有RDB文件出现,如果有就打开它,然后读取文件的内容并执行以下载入操作:

  • 检查文件开头的标识符是否为"REDIS",如果是则继续执行后续的载入操作,不是则抛出错误并终止载入操作
  • 检查文件的RDB版本号,以此来判断当前Redis服务器能否读取这一版本的RDB文件
  • 根据文件中记录的设备附加信息,执行相应的操作和设置
  • 检查文件的数据库数据部分是否为空,如果不为空就执行以下子操作:
    • 根据文件记录的数据库号码,切换至正确的数据库
    • 根据文件记录的键值对总数量以及带有过期时间的键值对数量,设置数据库底层数据结构
    • 一个接一个地载入文件记录的所有键值对数据,并在数据库中重建这些键值对
  • 如果服务器启用了复制功能,那么将之前缓存的Lua脚本重新载入缓存中
  • 遇到EOF标识,确认RDB正文已经全部读取完毕
  • 载入RDB文件末尾记录的CRC64校验和,把它与载入数据期间计算出的CRC64校验和进行对比,以此来判断被载入的数据是否完好无损
  • RDB文件载入完毕,服务器开始接受客户端请求

RDB文件载入时的服务器状态

服务器在载入RDB文件期间,会一直处于阻塞状态,直到载入工作完成为止

SAVE命令执行时的服务器状态

SAVE命令执行时,Redis服务器会被阻塞,所以当SAVE命令正在执行时,客户端发送的所有命令请求都会被拒绝

只有在服务器执行完SAVE命令、重新开始接受命令请求之后,客户端发送的命令才会被处理

BGSAVE命令执行时的服务器状态

因为BGSAVE命令的保存工作是由子进程执行的,所以在子进程创建RDB文件的过程中,Redis服务器仍然可以继续处理客户端的命令请求

但是,在BGSAVE命令执行期间,服务器处理SAVEBGSAVEBGREWRITEAOF三个命令的方式会和平时有所不同

首先,BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝,服务器禁止SAVE命令和BGSAVE命令同时执行 是为了 避免父进程(服务器进程)和子进程同时执行两个rdbSave调用,防止产生竞争条件

其次,在BGSAVE命令执行期间,客户端发送的BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE命令也会产生竞争条件

最后,BGREWRITEAOFBGSAVE两个命令不能同时执行:

  • 如果BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令 会被延迟到 BGSAVE命令执行完毕之后执行
  • 如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。因为BGREWRITEAOFBGSAVE两个命令 的 实际工作 都由 子进程执行,所以这两个命令在操作方面并没有什么冲突的地方,不能同时执行它们只是一个性能方面的考虑,并发出两个子进程,并且这两个子进程都同时执行大量的磁盘写入操作,这怎么想都不会是一个好主意

BGSAVE运作流程

在这里插入图片描述

  1. 执行bgsave命令,Redis父进程判断 当前是否存在 正在执行的子进程,如RDB/AOF子进程,如果存在bgsave命令直接返回
  2. 父进程 执行fork操作 创建子进程,fork操作过程中父进程会阻塞,通过info stats命令查看latest_fork_usec选项,可以获取最近一个fork操作的耗时,单位为微秒
  3. 父进程fork完成后,bgsave命令返回“Background saving started”信息并不再阻塞父进程,可以继续响应其他命令
  4. 子进程创建RDB文件,根据父进程内存 生成 临时快照文件,完成后 对原有(rdb)文件 进行原子替换???。执行lastsave命令可以获取最后一次生成RDB的时间,对应info统计的rdb_last_save_time选项
  5. 进程发送信号给父进程表示完成,父进程更新统计信息

在运行fork()函数的时候,操作系统(类Unix操作系统)会使用写时复制(copy-on-write)策略
即fork()函数发生的一刻 父子进程 共享 同一内存数据,当父进程要更改其中某片数据时(如执行一条写命令),操作系统 会将该片数据 复制一份 以保证 子进程的数据不受影响
所以新的RDB文件 存储的是 运行fork()函数那一刻 的 内存数据

写时复制策略 也保证了 在运行fork()函数的时刻 虽然看上去 生成了两份内存副本,但实际上内存的占用空间并不会增加一倍。这就意味着当操作系统内存只有2 GB而Redis数据库的内存有1.5 GB时,运行fork()函数后内存占用空间并不会增加到3 GB(超出物理内存)
为此需要确保Linux操作系统允许应用程序申请超过可用内存(物理内存和交换分区)的空间,方法是在/etc/sysctl.conf文件加入vm.overcommit_memory = 1,然后重启系统或者执行sysctl vm.overcommit_memory=1确保设置生效

另外需要注意的是,当进行快照的过程中,如果写入操作较多,造成运行fork()函数前后数据差异较大,会使得 内存使用空间 显著超过 实际数据大小,因为内存中不仅保存了当前的数据库数据,而且保存着运行fork()函数时刻的内存数据。进行内存占用空间估算时很容易忽略这一问题,造成内存占用空间超限

数据丢失(重要!!!)

RDB文件记录的是服务器在开始创建文件的那一刻,服务器中包含的所有键值对数据,这种数据持久化方式通常被称为时间点快照(point-in-time snapshot)
时间点快照持久化的一个特点是,系统在停机时将丢失最后一次成功实施持久化之后的所有数据

对于一个只使用RDB持久化的Redis服务器来说,服务器停机时 丢失的数据量 将取决于 最后一次成功执行的RDB持久化操作,以及该操作开始执行的时间

创建RDB文件的时间间隔越长,停机时丢失的数据也就越多

SAVE命令的停机情况

因为SAVE命令是一个同步操作,它的开始和结束都位于同一个原子时间之内,所以如果使用SAVE命令进行持久化,那么服务器在停机时 将丢失 最后一次成功执行SAVE命令之后 产生的所有数据

BGSAVE命令的停机情况

因为BGSAVE命令是一个异步命令,它的开始和结束并不位于同一个原子时间之内,所以如果使用BGSAVE命令进行持久化,那么服务器在停机时 丢失的数据量 将取决于 最后一次 成功执行 的BGSAVE命令 的 开始时间

RDB文件优缺点(重要!!!)

优点:

  • RDB是一个紧凑压缩的二进制文件,代表Redis在某个时间点上的数据快照。非常适用于备份,全量复制等场景。比如每6小时执行bgsave备份,并把RDB文件拷贝到远程机器或者文件系统中(如hdfs),用于灾难恢复
  • Redis加载RDB恢复数据远远快于AOF的方式

缺点:

  • RDB方式数据没办法做到实时持久化/秒级持久化,因为bgsave每次运行都要执行fork操作创建子进程,属于重量级操作,频繁执行成本过高
  • RDB文件使用特定二进制格式保存,Redis版本演进过程中有多个格式的RDB版本,存在老版本Redis服务无法兼容新版RDB格式的问题
  • 针对RDB不适合实时持久化的问题,Redis提供了AOF持久化方式来解决
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值