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执行重写的过程。
关注公众号,和我一起学习成长:小磊学习记录