【Redis】 RDB 持久化

Metadata

title: 【Redis】 RDB 持久化
date: 2023-07-09 08:38
tags:
  - 行动阶段/完成
  - 主题场景/数据存储
  - 笔记空间/KnowladgeSpace/ProgramSpace/BasicsSpace
  - 细化主题/数据存储/Redis
categories:
  - 数据存储
keywords:
  - 数据存储/Redis
description: 【Redis】 RDB 持久化

概述

由于 Redis 是内存数据库,数据状态都存储于内存,如果不想办法将存储在内存中的数据库状态保存到磁盘里,那么一旦服务器进程退出,服务器中的数据库状态也会消失。

为解决这个问题,Redis 提供了持久化的功能,可将内存中的数据库保存到磁盘,防止意外丢失。RDS 持久化(默认持久化策略)就是将某一时间点上的状态保存到一个 RDB 文件里。RDB 文件是经过压缩的二进制文件,可通过该文件还原成数据库状态。

  • RDB文件用于保存和还原Redis服务器所有数据库中的所有键值对数据
  • SAVE命令由服务器进程直接执行保存操作,所以该命令会阻塞服务器。
  • BGSAVE命令由子进程执行保存操作,所以该命令不会阻塞服务器。
  • 服务器状态中会保存所有用save选项设置的保存条件,当任意一个保存条件被满足时,服务器会自动执行BGSAVE命令。
  • RDB文件是一个经过压缩的二进制文件,由多个部分组成。
  • 对于不同类型的键值对,RDB文件会使用不同的方式来保存它们。

RDB 文件的创建与载入

有两个命令可用于生成 RDB 文件(SAVE 和 BGSAVE)。他们之间的区别是:SAVE 会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,阻塞期间,服务器不能处理任何命令请求。而 BGSAVE 会 fork 出一个子进程,由子进程负责创建 RDB 文件,父进程继续处理命令请求。当子进程完成之后,向父进程发送信号

创建就是执行 SAVE/BGSAVE 底层调用 rdbSave 函数的过程,载入就是服务启动时读取 RDB 文件底层调用 rdbLoad 函数的过程。

RDB 创建与载入时对过期键的处理套路

执行SAVEBGSAVE时,创建一个新的 RDB 文件,程序回对数据库中键检查,已过期的就不会包含到 RDB 文件中。

在启动时,RDB 文件载入:

  • 如果服务器以主服务模式运行,程序对文件保存的键检查,未过期的载入到数据库,过期则忽略
  • 如果服务器以从服务模式运行,无论是否过期,都会载入到数据库。因为主服务器在数据同步时,会将从服务器的数据库清空,一般不会有影响。

适用场景

  • 适合大规模的数据恢复
  • 对数据完整性和一致性要求不高

缺陷

  • 在一定间隔时间做一次备份,所以如果 redis 挂了,就会丢失最后一次快照后的所有修改
  • fork 的时候,内存中的数据被克隆一份,大致 2 倍的膨胀性需要考虑内存空间。

BGSAVE 执行时的服务器状态

BGSAVE 命令执行期间,对 SAVE,BGSAVE,BGREWRITEAOF(AOF 持久化命令)三个命令的处理方式如下:

由于 SAVE,BGSAVE 底层都是调用 rdbSave 来持久化文件的,而且父子进程同时执行两个 rdbSave 调用会产生竞态条件,所以这两个指令会被服务器拒绝。BGREWRITEAOF 会被延迟到 BGSAVE 执行结束后执行。
如果 BGREWRITEAOF 正在执行,服务器会拒绝 BGSAVE 命令。由于 BGREWRITEAOF 和 BGSAVE 都会产生子进程且有大量的磁盘写入,出于性能考虑不会同时执行。

简单来说,就是 BGSAVE 执行期间,拒绝 SAVE,BGSAVE延迟执行 BGREWRITEAOF。BGREWRITEAOF 执行期间,拒绝 BGSAVE

RDB 与 AOF 共存的载入情况

RDB 文件的载入是在服务器启动时执行,Redis 并没有专门提供载入 Redis 的命令。由于 AOF 文件的更新频率更高,因此开启 AOF 持久化功能后,启动时优先加载 AOF 还原数据,只有在 AOF 处于关闭状态,才使用 RDB 文件恢复数据。

自动间隔性保存

服务器允许用户通过配置文件设置隔一定时间自动执行 BGSAVE。可通过 save 选项设多个保存条件,默认的配置如下:

save 900 1
save 300 10
save 60 10000

只要满足任意条件,900s 内对数据库进行 1 次修改或 300s 内…BGSAVE 就会被执行。

那么,服务器是如何根据 save 选项来自动执行 BGSAVE 的?
从实现角度考虑,我们需要记录配置评判依据依据更新驱动。记录配置由 saveparams 实现;评判依据是 dirty 计数器和 lastsave 属性;依据更新驱动就是 serverCron 对评判依据的动态更新。
save 配置都会在 redisServer 的 saveparam 数组中体现:

struct redisServer{
    //记录了保存条件的数组
    struct saveparam *saveparams;
    ...
};
struct saveparam{
    //秒数
    time_t seconds;
    //修改数
    int changes;
};

dirty 计数器和 lastsave 属性

这两个属性由 redisServer 持有:

  • dirty 计数器记录距离上次成功执行 SAVE 或 BGSAVE 后数据库被修改了几次。
  • lastsave 是一个 UNIX 时间戳,记录上次成功执行 SAVE 或 BGSAVE 的时间。
struct redisServer{
    //修改计数器
    long long dirty;
    //上一次执行保存的时间
    time_t lastsave;
    //...
};

检查条件是否满足

Redis 的周期性操作函数 serverCron 每隔 100 毫秒会执行一次,其中一项工作就是检查 save 选项设置的保存条件是否满足要求,满足则执行 BGSAVE。

RDB 文件结构

REDIS:长度 5 字节,保存 “REDIS”5 个字符(为书写方便,其实是 5 个单独字符),通过这个判断该文件是否为 RDB 文件。

db_version:长度 4 字节,是字符串表示的整数记录 RDB 的版本号。

database:包含 0 个或多个数据库及各数据库中键值对数据。表示那些数据库是有数据的。

EOF:常量长度 1 字节,标志 RDB 文件正文的结束。读取时遇到该值,表示键值对的载入已经结束了。

check_sum:是一个 8 字节的无符号整数,保存一个同过前几位变量计算出来的校验和。每次加载都会进行计算校验,通过这个来判断文件是否损坏。

database 部分

每个非空数据库在 RDB 文件中都可表示为 SELECTDB,db_number,key_value_pairs 三部分

  • selectdb:1 字节,标志位,标志着下一位存储的是数据库号码。
  • db_number:是一个数据库号码。
  • key_value_pairs:保存了数据库中所有键值对数据,如果有过期时间,则过期时间也会保存。

key_value_pairs 部分

不带过期时间的键值对在 RDB 文件由 TYPE,key,value 组成,带过期时间则含有 EXPIRETIME_MS,ms:

EXPIRETIME_MS:标志位,长度为 1 字节,告知程序下一个读入的是以毫秒为单位的过期时间。
ms:是 8 字节长的带符号整数,记录 UNIX 时间戳,即过期时间。
type:记录 value 的类型,长度 1 字节,这个常量其实就是 Redis 对象类型和底层编码的组装:

  • REDIS RDBTYPE_STRING
  • REDIS_ RDB_TYPE_LIST
  • REDIS_RDB_TYPE_SET
  • REDIS_RDB_TYPE_ZSET
  • REDIS_RDB_TYPE_HASH
  • REDIS_RDB_TYPE_LIST_ ZIPLIST
  • REDIS_RDB_TYPE_SET_INTSET
  • REDIS_RDB_TYPE_ZSET_ZIPLIST
  • REDIS_RDB_TYPE_HASH_ZIPLIST

服务器会根据 TYPE 来决定如何读入和解释 value 的数据。

key 就不用做过多解释~

value 的编码

根据 TYPE 的不同,value 的存储结构也大不相同。这里不详细展开,只需要知道,对于字符串对象,如果大于 20 字节,就会用 LZF 算法压缩。除字符串对象和整数集合,其他存储方式的开头都是节点数量,告诉程序应读入多少节点 / 键值对。

分析 RDB 文件

Redis 自带 RDB 文件检查工具 redis-check-dump。可以帮助在系统故障后分析快照文件,也就是 RDB 文件。