写点什么

redis 持久化机制

作者:喀拉峻
  • 2022 年 3 月 14 日
  • 本文字数:2911 字

    阅读完需:约 10 分钟

redis 是基于内存的数据库,提供了内存数据持久化到文件的两种方式,一种是写 RDB 文件方式,另一种是写 AOF 文件,默认执行的是 RDB 文件持久化方式。


当在 redis.config 配置文件中开启 AOF 持久化机制时,redis 在启动时,会优先载入 AOF 文件。其中服务器在载入文件的过程中处于阻塞状态。下图为 redis 启动时载入持久化文件的流程。



一、RDB 文件的创建与载入。


创建 RDB 文件有两种命令的方式,Save 与 BGSAVE,其中 Save 命令会阻塞 redis 服务器进程。导致这段期间服务器不能接受客户端的请求,BGSAVE 命令会创建子进程来执行 RDB 文件的创建。所以 BGSAVE 不会阻塞服务器进程。SAVE 与 BGSAVE 命令都会以不同的方式来调用 rdb.c/rdbSave 函数来完成 RDB 文件的创建工作。伪代码如下:


def SAVE():
rdbSave()//创建RDB文件。
def BGSAVE():
pid = fork()//创建子进程。
if(pid == 0)
rdbSave()//子进程负责创建RDB文件。
signal_parent()//完成后向父进程发送信号。
if(pid > 0)
handle_request_and_wait_signal()//父进程继续处理命令请求,并通过轮询等待子进程的信号。
else
handle_fork_error().
复制代码


那么在服务器端执行 BGSAVE 命令期间,如果客户端发送了 SAVE 命令,BGSAVE,BGREWRITEAOF 命令时,则服务端拒绝客户端的命令请求。拒绝 save 命令是防止父进程与子进程同时执行,rdbSave 期间产生竞争条件。BGSAVE 同样是为了防止竞争条件的产生。至于 BGREWRITEAOF 则是为了性能的考虑,防止两个进程同时对磁盘进行写入操作。


二。redis 如何同步 RDB 文件 。


通常情况下,redis 通过读取配置文件定期保存数据库的状态到 RDB 文件。


例如默认配置文件配置如下图所示:



上图分别表示在 900 秒内至少执行 1 次数据库修改,300 秒内至少 10 次数据库修改,60 秒内至少 10000 次数据库修改操作,只要满足任何一条,数据库执行 RDB 文件的同步操作。


当服务器启动时会读取配置文件的以上配置信息,并将上面的配置信息写入到 redisServer 结构中的 saveparams 属性中。


struct redisServer{


......
long long dirty;//距离最近一次写入RDB文件所有数据库文件修改的总次数。
time_t lastsave;//上一次执行保存RDB文件的时间
复制代码


struct saveparam *saveparams;


.......
复制代码


}


其中 saveparam 结构如下:


struct saveparam{


time_t seconds;//秒数


int changes;//修改数
复制代码


}


其中 dirty 和 lastsave 的作用就是为了 redis 周期性执行函数 serverCron(每个 100 毫秒执行一次)根据 saveparams 的配置信息检查条件是否满足来执行 RDB 文件的同步操作。下图为定期执行保存的伪代码。



那么 RDB 文件保存的数据主要是哪些呢,通过下面的介绍我们将了解到 RDB 文件保存到数据库的状态信息主要是 key,value 信息。下面我们需要了解 RDB 文件的组织结构。


三 、RDB 文件的结构


RDB 文件总体结构如下图所示:



其中 REDIS 与 EOF 为常量部分。db_version 为 RDB 文件的版本,不同的版本对应的格式会有区别。本文讨论的是 RDB 文件的第六版本。check_sum 主要做 RDB 文件的校验和计算,防止文件篡改或者缺损,它的值是根据前面的格式内容进行计算而得。


databases 部分分两种情况,databases 为空的情况,即数据库中没有数据的情况。和数据库不为空的情况。


数据库为空的情况如下图所示:



数据库不为空的情况,如下图所示:



其中 database0 或 database3 的 格式的组成如下图所示:



其中 SELECTDB 为常量,db_number 为 对应的数据库。键值对为对应的数据库键值,其中包括带过期时间的键值对与不带过期时间的键值对。


不带过期时间的键值对的结构如下图所示:



其中 TYPE 记录了 value 的类型,值为常量,占一个字节,值为以下常量中的一种。



带有过期时间的键值对的结构如下:



其中:EXPIRETIME_MS 表示的是以毫秒为单位的过期时间对应的时间戳。ms 表示的是对应的时间值。如 下图所示:



Redis 除了提供 RDB 文件的持久化方式外,还提供了 AOF 持久化机制,与 RDB 保存数据库的键值对的方式不同的是,AOF 提供的持久化机制保存的是 redis 执行的命令 。如下图所示:



若对 redis 空数据库执行如下写命令:



则 AOF 方式将以 redis 命令请求协议的方式保存到 AOF 文件中。对于上面执行的三条命令。AOF 文件内容格式如下图所示。



其中第一行 SELECT DB 是由 redis 服务器自动添加上去的。


AOF 文件持久化的实现。


AOF 文件持久化的实现方式分为三个步骤,AOF 命令追加,写 AOF 文件,同步 AOF 文件。


其中 AOF 命令追加是将客户端请求的命令,以命令协议的方式追加到服务器状态的 redisServer 的 aof_buf 缓冲区的末尾。如下图所示:



AOF 文件的写入是将追加到 AOF 缓冲区的命令写入到 AOF 文件。这个操作是在文件事件处理程序中来做的。REDIS 服务器进程是个事件循环,在接收到客户端发送的命令请求时,文件事件会从考虑是否将 aof_buf 缓冲区中的内容写入和保存到 AOF 文件。


事件处理函数如下图所示:



其中 flushAppendOnlyFile()函数的行为由配置文件选项:appendfsync 来决定。appendfsync 的值为下表中的任何一项,默认 appendfsync 值为 everysec:



其中,这里的写入表示的是将 aof_buf 缓冲区的内容写入到 AOF 文件,此时这个写入并没有真正写入到磁盘文件,操作系统为了提高文件写入的效率,在调用 write()函数时,会将数据暂且写入到内存的一块缓冲区。待缓冲区满或者强制时间到达或者强制刷新缓冲区时才将数据同步到文件。同步表示的就是将内存缓冲区中的数据同步到磁盘。


通过上面的分析,我们可以看出来在效率与安全性上 得出:


always 的安全性 最高,在每个文件时间中都会写入 AOF 及同步 AOF 文件。最多会丢失一个事件循环的命令数据。性能最差,因为每个事件循环都要写文件及同步文件。


everysec 的安全性次之。写入的性能同 no。


最后是 no 的安全性最弱,会丢失上一次同步开始后的数据,写入的性能最好,同 everysec。


AOF 文件的载入与还原过程如下图所示:



AOF 重写


因为 AOF 文件随着命令请求的不断执行,会逐渐变的越来越大。例如如下图所示:



为了保存 list 键的数据状态,需要保存 6 条命令,


其实 list 的状态我们可以用一条命令来保存,如:


RPUSH list "C" "D" "E" "F" "G"


其实 AOF 重写是保存的是当前数据库的键值对的状态。它根据值所对应的类型,执行对应的命令操作。 因为在进行 AOF 命令重写时会进行大量的写入操作,这时如果在服务器进程中来进行重写操作,会阻塞服务器进程,导致其无法处理客户端的请求,为了避免这种情况的发生,AOF 重写是在子进程中来完成的。子进程会拷贝服务器进程的数据副本,这样做的好处是保证不再使用锁的情况下保证数据的安全性。同样,使用子进程带来的一个问题是当子进程进行 AOF 重写时,服务器进程在这段期间会接收客户端的命令请求,这导致重写后的 AOF 文件与服务器的状态不一致的情况。为了解决这个情况。redis 引入了重写缓冲区的概念,这个重写缓冲区在 redis 创建 AOF 子进程重写后使用。重写期间,命令会被写入到缓冲区与重写缓冲区。当子进程完成了 AOF 文件的重写,会像父进程发送一个重写完成的信号,父进程在接收到信号后,会调用信号处理函数,执行以下操作:


1.重写缓冲区的内容同步到新的 AOF 文件中。


2.重命名新的 AOF 文件,覆盖原有的 AOF 文件。


此时,信号处理函数再父进程执行,会短暂阻塞服务器进程。


下图为 AOF 执行重写的过程。



用户头像

喀拉峻

关注

左手Java右手Python,中间纹个C++ 2021.06.26 加入

还未添加个人简介

评论

发布
暂无评论
redis持久化机制_网络安全_喀拉峻_InfoQ写作平台