小蚂蚁 发表于 2021-12-7 17:33:49

Redis | 第5章 Redis 中的持久化技术《Redis设计与实现》

@(第5章 Redis 中的持久化技术)
前言
参考资料:《Redis设计与实现 第二版》;
第二部分为单机数据库的实现,主要由以下模块组成:数据库、持久化、事件、客户端与服务器;
本篇将介绍 Redis 中的持久化技术,主要有两种:RDB持久化和AOF持久化;
与本章相关的 Redis 命令总结在下篇文章,欢迎点击收藏,本篇将不再重复:
《Redis常用命令及示例总结(API)》:https://blog.51cto.com/dlhjw/4744855
1. RDB 持久化

1.1 RDB 文件的创建与载入

[*]Redis使用 SAVE 和BGSAVE 命令生成 RDB 文件;

[*]SAVE:会阻塞 Redis 服务器进程,直到 RDB 文件创建完毕为止,阻塞期间服务器不能处理任何命令请求;
[*]BGSAVE:会派生一个子进程,由指进程负责创建 RDB 文件,父进程继续处理命令请求。BGSAVE 执行期间,会发生以下特殊情况:
[*]在 BGSAVE 命令执行期间,客户端发送 SAVE 和 BGSAVE 命令会被服务器拒绝,防止产生竞争条件。客户端发送 BGREWRITEAOF 命令会被延迟;
[*]在 BGREWRITEAOF 命令执行期间,客户端发送 BGSAVE 命令会被服务器拒绝;

[*]创建 RDB 文件由 rdb.c/rdbSave 函数完成;
[*]载入 RDB 文件由 rdb.c/rdbLoad 函数完成;
[*]RDB 文件的载入工作是在服务器启动时自动执行,只要 Redis 服务器在启动时检测到 RDB 文件存在,就会自动载入 RDB 文件;
[*]AOF 文件的更新频率通常比 RDB 文件更新频率高:

[*]当服务器开启了 AOF 持久化功能时,会优先使用 AOF 文件还原数据库状态;
[*]当服务器关闭了 AOF 持久化功能时,才会使用 RDB 文件来还原数据库状态;

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

1.2 自动间隔性保存
1.2.1 设置保存条件

[*]服务器会根据 save 选项所设置的保存值,设置服务器状态 redisServer结构的 saveparams 属性;
[*]redisServer 的结构定义:
redisServer{
//...
//记录了保存条件的数组
struct saveparam *saveparams;
//
}
[*]saveparams 属性是一个数组,每个 saveparam 结构保存了一个 save 设置的保存条件;
[*]saveparam 的结构定义: saveparam{
//秒数
time_t seconds;
//修改值
int changes;
}

1.2.2 dirty 计数器和 lastsave 属性

[*]dirty 属性和 lastsave 属性在 redisServer 结构体里:
redisServer{
//...
//修改计数器
long long dirty;
//上一次执行保存的时间
time_t lastsave;
};

[*]dirty 计数器记录距离上一次成功执行 SAVE 命令或者 BGNAME 命令之后,服务器对数据库状态进行了多少次修改;
[*]lastsave 属性是一个 UNIX 时间戳,记录了服务器上一次成功执行 SAVE 命令或 BGSAVE 命令的时间;

1.2.3 检查保存条件是否满足

[*]Redis 的服务器周期性操作函数 serverCron 默认每隔 100ms 会执行一次,其中包括检查 save 选项所设置的保存条件是否满足(遍历并检查 saveparams 数组中的所有保存条件),满足则执行 BGSAVE 命令;
<br>

1.3 RDB 文件
1.3.1 RDB 的文件结构

[*]RDB 文件结构的逻辑图:

[*]各个字段含义:
字段长度储存值说明REDIS5字节“REDIS”在载入文件时,快速检查所载入的文件是否为 RDB 文件db_version4字节字符串表示的整数RDB 文件的版本号databases0个或任意多个数据库,以及数据库中的键值对数据EOP1字节EOP 常量表示 RDB 文件正文内容的结束check_sum8字节无符号整数前4个部分的校验和
1.3.2 database 的文件结构

[*]database 为 RDB 文件的结构组成部分;
[*]databases 部分的逻辑结构:


[*]各字段含义:
字段长度存储值说明SELECTDB1字节常量表示接下来读入数据库号码db_number1、2或5字节数字表示数据库号码key_value_pairs长度不定数据库所有的键值对数据
1.3.3 key_value_pairs 的文件结构

[*]key_value_pairs 为 databases 的结构组成部分;有两种类型,一种不带过期时间,一种带过期时间;
[*]key_value_pairs 部分的逻辑结构:



[*]各字段含义:
字段长度存储值说明EXPIRETIME_MS1字节数值表示过期时间ms8字节数值以毫秒为单位的 UNIX 时间戳TYPE1字节常量代表一种对象类型或底层编码key长度不定字符串对象表示键对象value长度不定各种对象表示值对象
1.3.4 value 的编码

[*]value 为 key_value_pairs 的结构组成成分;
[*]value 值对象的结构和长度会根据 TYPE 类型的不同而不同;
[*]value可以是字符串对象、列表对象、集合对象、哈希表对象、有序集合对象、INTSET编码的集合和ZIPLIST编码的列表、哈希表或有序集合;
[*]value的格式与编码对应请见 《第3章 对象》1.1 对象的定义;
[*]字符串对象的格式与示例:

[*]字符串对象可分为:压缩字符串和无压缩字符串两种:




[*]列表与集合对象的格式:



[*]哈希表对象的格式:



[*]有序集合对象的格式:



[*]INTSET 编码集合的格式:

[*]将整数集合转换成字符串即可;

[*]ZIPLIST编码的列表、哈希表或有序集合的格式:

[*]将压缩列表转换成一个字符串对象,然后再保存到 RDB 文件;

<br>

1.4 RDB 文件的示例

[*]不包含任何键值对的 RDB 文件:
REDIS标识db_versionEOF标识check_numREDIS0006377334 263 c 360 z 334 362 v

[*]包含字符串键的 RDB 文件:
REDIS标识db_versionSELECTDBdb_number,0 号数据库TYPE,\0 表示字符串keyvalueEOF标识check_numREDIS0006376\0\0003 MSG005 HELLO377207 z = 304 f T L 343
[*]包含带有过期时间的字符串键的 RDB 文件:
REDIS标识db_versionSELECTDB 切换数据库EXPIRETIME_MSmsTYPE,\0 表示字符串keyvalueEOF标识check_numREDIS0006376 \0374\ 2 365 336 @ 001 \0 \0\0003 MSG005 HELLO377212 231 x 247 252 } 021 306
[*]包含一个集合键的 RDB 文件:
REDIS标识db_versionSELECTDB 切换数据库常量 REDIS_RDB_TYPE_SETkey集合大小第一个元素第二个元素第三个元素EOF常量check_numREDIS0006376 \0002004 LANG003004 RUBY004 JAVA001 C377202 312 r 352 346 305 * 023
<br>
2 AOF 持久化与 RDB 持久化的区别

[*]AOF 持久化:保存 Redis 服务器所执行的命令来记录数据库状态;
[*]RDB 持久化:保存数据库中的键值对来记录数据库状态不同;
<br>
3. AOF 持久化

3.1 AOF 持久化的实现

[*]AOF 持久化功能可分为:追加(append)、文件写入、文件同步(sync)三个步骤;
[*]AOF 文件中的所有命令都以 Redis 命令请求协议的格式保存;
[*]当 AOF 持久化功能打开时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的末尾:
redisServer{
//...
//AOF 缓冲区
sds aof_buf;
};
[*]AOF 文件的写入与同步依赖事件循环 loop,每次循环主要有三个工作:

[*]处理文件事件:负责接收客户端的命令请求,以及向客户端发送命令回复;
[*]处理时间事件:执行需要定时运行的函数;
[*]flushAppendOnlyFile():考虑是否将 aof_buf 中的内容追加到 AOF 文件中;

[*]flushAppendOnlyFile() 函数的行为由服务器配置的 appendfsync 选项的值决定,该值有三种不同的行为:
appendfsync 选项的值flushAppendOnlyFile 函数的行为效率与安全性always将 aof_buf 缓冲区中的所有内容写入并同步到 AOF 文件效率最慢,安全性最高everysec将 aof_buf 缓冲区中的所有内容写入并同步到 AOF 文件,如果上次同步 AOF 文件的事件距离现在超过 1s ,则对再次 AOF 文件进行同步,并且这个同步由一个线程专门负责效率高no将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,但不对 AOF 文件进行同步,何时同步由操作系统决定效率最高,安全性最低<br>

3.2 AOF 文件的载入与数据还原

[*]服务器创建一个不带网络连接的伪客户(fake client),伪客户端读入并执行 AOF 文件即可;

<br>

3.3 AOF 重写

[*]AOF 重写不需要对现有 AOF 文件进行任何读取、分析或写入操作,而是通过读取服务器当前数据库状态实现;
[*]AOF 重写功能的实现原理:从数据库读取键现在的值,然后用一条命令记录键值对,代替之前记录这个键值对的多条命令;
[*]为了避免执行命令时造成客户端输入缓冲区溢出,重写程序在处理列表、哈希表、集合、有序集合这四种键时,会检查元素数量,超过一定数量(64)时会使用多条命令记录这个键的情况;

[*]这个数量由常量 redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD 确定;
<br>


3.4 AOF 后台重写

[*]AOF 重写需要解决2个问题:

[*]重写不能阻碍服务器处理客户端请求:使用子进程解决;
[*]子进程在 AOF 重写期间,父进程服务器对数据库状态进行修改,会使服务器当前状态与重写后 AOF 状态不一致:设置AOF 重写缓冲区解决;

[*]Redis 将 AOF 重写程序放到子进程里执行:

[*]子进程进行 AOF 重写期间,服务器进程(父进程)可以继续处理命令请求;
[*]子进程带有服务器进程的数据副本,使用子进程而不是线程,避免使用锁的情况下保证数据安全;



[*]Redis 服务器设置一个 AOF 重写缓冲区,以保证:

[*]AOF 缓冲区的内容会定期被写入和同步到 AOF 文件,对现有 AOF 文件的处理工作如常进行;
[*]从创建子进程开始,服务器执行的所有写命令都会被记录到 AOF 重写缓冲区里;

[*]子进程完成 AOF 重写工作后,向父进程发送一个信号,父进程接到信号后调用信号处理函数,执行以下工作:

[*]将 AOF 重写缓冲区中的所有内容写入到新 AOF 文件,此时新 AOF 文件保存的数据库状态将与服务器当前的数据库状态一致;
[*]对新的 AOF 文件进行改名,原子地覆盖现有的 AOF 文件,完成新旧两个 AOF 文件的替换;

[*]只有号处理函数执行时会对服务器进程(父进程)造成阻塞;

<br>
最后
::: hljs-center
新人制作,如有错误,欢迎指出,感激不尽!
:::
::: hljs-center
欢迎关注公众号,会分享一些更日常的东西!
:::
::: hljs-center
如需转载,请标注出处!
:::
::: hljs-center

:::

            </div>
      
      <div id="asideoffset"></div>

https://blog.51cto.com/dlhjw/4731839
页: [1]
查看完整版本: Redis | 第5章 Redis 中的持久化技术《Redis设计与实现》