前言文章首发于公众号“蘑菇睡不着”,欢送来访~
大家都晓得 Redis 是一个内存数据库,数据都存储在内存中,这也是 Redis 十分快的起因之一。尽管速度提上来了,然而如果数据始终放在内存中,是非常容易失落的。比方 服务器敞开或宕机了,内存中的数据就木有了。为了解决这一问题,Redis 提供了 长久化 机制。别离是 RDB 以及 AOF 长久化。
RDBRDB 长久化能够在指定的工夫距离内生成数据集的工夫点快照(point-in-time snapshot)。
RDB 在重启保留了大数据集的实例比 AOF 快。
有个两个 Redis 命令能够用于生成 RDB 文件,一个是 SAVE,另一个是 BGSAVE。
SAVE 命令会阻塞 Redis 服务器过程,直到 RDB 文件创建结束为止,在服务器过程阻塞期间,服务器不能解决任何命令申请。
> SAVE // 始终等到 RDB 文件创建结束 OK
和 SAVE 命令间接阻塞服务器过程不同的是,BGSAVE 命令会派生出一个子过程,而后由子过程负责创立 RDB 文件,服务器过程(父过程)持续解决命令过程。
执
行fork的时候操作系统(类Unix操作系统)会应用写时复制(copy-on-write)策略,即fork函数产生的一刻父子过程共享同一内存数据,当父过程要更改其中某片数据时(如执行一个写命令 ),操作系统会将该片数据复制一份以保障子过程的数据不受影响,所以新的RDB文件存储的是执行fork一刻的内存数据。
> BGSAVE // 派生子过程,并由子过程创立 RDB 文件 Background saving started
生成 RDB 文件由两种形式:一种是手动,就是上边介绍的用命令的形式;另一种是主动的形式。
接下来具体介绍一下主动生成 RDB 文件的流程。
Redis 容许用户通过设置服务器配置的 save 选项,让服务器每隔一段时间主动执行一次 BGSAVE 命令。
用户能够通过在 redis.conf 配置文件中的 SNAPSHOTTING 下 save 选项设置多个保留条件,但只有其中任意一个条件被满足,服务器就会执行 BGSAEVE 命令。
如,以下配置:
save 900 1
save 300 10
save 60 10000
上边三个配置的含意是:
如果没有手动去配置 save 选项,那么服务器会为 save 选项配置默认参数:
save 900 1
save 300 10
save 60 10000
接着,服务器就会依据 save 选项的配置,去设置服务器状态 redisServer 构造的 saveparams 属性:
struct redisServer{
// ...
// 记录了保留条件的数组
struct saveparams *saveparams;
// ...
};
saveparams 属性是一个数组,数组中的每一个元素都是一个 saveparam 构造,每个 saveparam 构造都保留了一个 save 选项设置的保留条件:
struct saveparam {
// 秒数
time_t seconds;
// 批改数
int changes;
};
除了 saveparams 数组之外,服务器状态还维持着一个 dirty 计数器,以及一个 lastsave 属性;
struct redisServer {
// ...
// 批改计数器
long long dirty;
// 上一次执行保留工夫
time_t lastsave;
// ...
}
Redis 的服务器周期性操作函数 serverCron 默认每隔 100 毫秒执行一次,该函数用于对正在运行的服务器进行保护,它的其中一项工作就是查看 save 选项所设置的保留条件是否曾经满足,如果满足的话就执行 BGSAVE 命令。
Redis serverCron 源码解析如下:
程序会遍历并查看 saveparams 数组中的所有保留条件,只有有任意一个条件被满足,服务器就会执行 BGSAVE 命令。
上面是 rdbSaveBackground 的源码流程:
下图展现了一个残缺 RDB 文件所蕴含的各个局部。
redis 文件的最结尾是 REDIS 局部,这个局部的长度是 5 字节,保留着 “REDIS” 五个字符。通过这五个字符,程序能够在载入文件时,疾速查看所载入的文件是否时 RDB 文件。
db_version 长度为 4 字节,他的值时一个字符串示意的整数,这个整数记录了 RDB 文件的版本号,比方 “0006” 就代表 RDB 文件的版本为第六版。
database 局部蕴含着零个或任意多个数据库,以及各个数据库中的键值对数据:
EOF 常量的长度为 1 字节,这个常量标记着 RDB 文件注释内容的完结,当读入程序遇到这个值后,他晓得所有数据库的所有键值对曾经载入结束了。
check_sum 是一个 8 字节长的无符号整数,保留着一个校验和,这个校验和时程序通过对 REDIS、db_version、database、EOF 四个局部的内容进行计算得出的。服务器在载入 RDB 文件时,会将载入数据所计算出的校验和与 check_sum 所记录的校验和进行比照,以此来查看 RDB 是否有出错或者损坏的状况。
举个例子:下图是一个 0 号数据库和 3 号数据库的 RDB 文件。第一个就是 “REDIS” 示意是一个 RDB 文件,之后的 “0006” 示意这是第六版的 REDIS 文件,而后是两个数据库,之后就是 EOF 完结标识符,最初就是 check_sum。
AOF长久化形式记录每次对服务器写的操作,当服务器重启的时候会从新执行这些命令来复原原始的数据,AOF命令以redis协定追加保留每次写的操作到文件开端.Redis还能对AOF文件进行后盾重写,使得AOF文件的体积不至于过大.
AOF长久化性能的实现能够分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
当 AOF 长久化性能处于关上状态时,服务器在执行完一个写命令之后,会以协定格局将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的开端。
struct redisServer {
// ...
// AOF 缓冲区
sds aof_buf;
// ..
};
如果客户端向服务器发送以下命令:
> set KEY VALUE OK
那么服务器在执行这个 set 命令之后,会将以下协定内容追加到 aof_buf 缓冲区的开端;
*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
Redis的服务器过程就是一个事件循环(loop),这个循环中的文件事件负责接管客户端
的命令申请,以及向客户端发送命令回复,而工夫事件则负责执行像 serverCron 函数这样需
要定时运行的函数。
因为服务器在解决文件事件时可能会执行写命令,使得一些内容被追加到aof_buf缓冲区
外面,所以在服务器每次完结一个事件循环之前,它都会调用 flushAppendOnlyFile 函数,考
虑是否须要将aof_buf缓冲区中的内容写入和保留到AOF文件外面,这个过程能够用以下伪代
码示意:
def eventLoop():
while True:
#解决文件事件,接管命令申请以及发送命令回复
#解决命令申请时可能会有新内容被追加到 aof_buf缓冲区中
processFileEvents()
#解决工夫事件
processTimeEvents()
#思考是否要将 aof_buf中的内容写入和保留到 AOF文件外面
flushAppendOnlyFile()
flushAppendOnlyFile函数的行为由服务器配置的 appendfsync 选项的值来决定,各个不同
值产生的行为如下表所示。
appendfsync 选项的值 | flushAppendOnlyFile 函数的行为 |
---|---|
always | 将 aof_buf 缓冲区中的所有内容写入并同步到 AOF 文件 |
everysec | 将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,如果上次同步 AOF 文件的工夫间隔当初超过一秒钟,那么再次对 AOF 文件进行同步,并且这个同步操作是由一个线程专门负责执行的 |
no | 将 aof_buf 缓冲区中的所有内容写入到 AOF 文件,但并不对 AOF 文件进行同步,何时同步由操作系统来决定 |
如果用户没有被动为appendfsync选项设置值,那么appendfsync选项的默认值为everysec。
写到这里有的小伙伴可能会对下面说的写入和同步含意弄混,这里说一下:
写入:将 aof_buf 中的数据写入到 AOF 文件中。
同步:调用 fsync 以及 fdatasync 函数,将 AOF 文件中的数据保留到磁盘中。
艰深地讲就是,你要往一个文件写货色,写的过程就是写入,而同步则是将文件保留,数据落到磁盘上。
大家之前看文章的时候是不是大多都说 AOF 最多失落一秒钟的数据,那是因为 redis AOF 默认是 everysec 策略,这个策略每秒执行一次,所以 AOF 长久化最多失落一秒钟的数据。
因为AOF文件外面蕴含了重建数据库状态所需的所有写命令,所以服务器只有读入并从新执行一遍AOF文件外面保留的写命令,就能够还原服务器敞开之前的数据库状态。 Redis读取AOF文件并还原数据库状态的具体步骤如下:
因为AOF长久化是通过保留被执行的写命令来记录数据库状态的,所以随着服务器运行 工夫的流逝,AOF文件中的内容会越来越多,文件的体积也会越来越大,如果不加以控制的 话,体积过大的AOF文件很可能对Redis服务器、甚至整个宿主计算机造成影响,并且AOF文 件的体积越大,应用AOF文件来进行数据还原所需的工夫就越多。
如 客户端执行了以下命令是:
> rpush list "A" "B" OK > rpush list "C" OK > rpush list "D" OK > rpush list "E" "F" OK
那么光是为了记录这个list键的状态,AOF文件就须要保留四条命令。
对于理论的利用水平来说,写命令执行的次数和频率会比下面的简略示例要高得多,所 以造成的问题也会重大得多。 为了解决AOF文件体积收缩的问题,Redis提供了AOF文件重写(rewrite)性能。通过该 性能,Redis服务器能够创立一个新的AOF文件来代替现有的AOF文件,新旧两个AOF文件所 保留的数据库状态雷同,但新AOF文件不会蕴含任何节约空间的冗余命令,所以新AOF文件 的体积通常会比旧AOF文件的体积要小得多。 在接下来的内容中,咱们将介绍AOF文件重写的实现原理,以及BGREWEITEAOF命令 的实现原理。
尽管Redis将生成新AOF文件替换旧AOF文件的性能命名为“AOF文件重写”,但实际上, AOF文件重写并不需要对现有的AOF文件进行任何读取、剖析或者写入操作,这个性能是通 过读取服务器以后的数据库状态来实现的。
就像下面的状况,服务器齐全能够将这六条命令合并成一条。
> rpush list "A" "B" "C" "D" "E" "F"
除了下面列举的列表键之外,其余所有类型的键都能够用同样的办法去缩小 AOF文件中的命令数量。首先从数据库中读取键当初的值,而后用一条命令去记录键值对,代替之前记录这个键值对的多条命令,这就是AOF重写性能的实现原理。
在理论中,为了防止在执行命令时造成客户端输出缓冲区溢出,重写程序在解决列表、 哈希表、汇合、有序汇合这四种可能会带有多个元素的键时,会先查看键所蕴含的元素数 量,如果元素的数量超过了redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD常量的值,那 么重写程序将应用多条命令来记录键的值,而不单单应用一条命令。 在目前版本中,REDIS_AOF_REWRITE_ITEMS_PER_CMD常量的值为64,这也就是 说,如果一个汇合键蕴含了超过64个元素,那么重写程序会用多条SADD命令来记录这个集 合,并且每条命令设置的元素数量也为64个。
AOF 重写会执行大量的写操作,这样会影响主线程,所以redis AOF 重写放到了子过程去执行。这样能够达到两个目标:
然而有一个问题,当子过程重写数据时,主过程仍然在解决新的数据,这也就会造成数据不统一状况。
为了解决这种数据不统一问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在 服务器创立子过程之后开始应用,当Redis服务器执行完一个写命令之后,它会同时将这个写 命令发送给AOF缓冲区和AOF重写缓冲区,如下图:
这也就是说,在子过程执行AOF重写期间,服务器过程须要执行以下三个工作:
这样一来能够保障:
当子过程实现AOF重写工作之后,它会向父过程发送一个信号,父过程在接到该信号之 后,会调用一个信号处理函数,并执行以下工作:
这个信号处理函数执行结束之后,父过程就能够持续像平常一样接受命令申请了。
在整个AOF后盾重写过程中,只有信号处理函数执行时会对服务器过程(父过程)造成 阻塞,在其余时候,AOF后盾重写都不会阻塞父过程,这将AOF重写对服务器性能造成的影 响降到了最低。
Redis 还能够同时应用 AOF 长久化和 RDB 长久化。 在这种状况下, 当 Redis 重启时, 它会优先应用 AOF 文件来还原数据集, 因为 AOF 文件保留的数据集通常比 RDB 文件所保留的数据集更残缺。然而 AOF 复原比较慢,Redis 4.0 推出了混合长久化。
混合长久化: 将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自长久化开始到长久化完结 的这段时间产生的增量 AOF 日志,通常这部分 AOF 日志很小。
于是在 Redis 重启的时候,能够先加载 RDB 的内容,而后再重放增量 AOF 日志就能够齐全代替之前的 AOF 全量文件重放,重启效率因而大幅失去晋升。
感觉文章不错的话,小伙伴们麻烦点个赞、关个注、转个发一下呗~你的反对就是我写文章的能源。
更多精彩的文章请关注公众号“蘑菇睡不着”。
你越被动就会越被动,咱们下期见~