文章首发于公众号“蘑菇睡不着”,欢送来访~
前言
大家都晓得 Redis 是一个内存数据库,数据都存储在内存中,这也是 Redis 十分快的起因之一。尽管速度提上来了,然而如果数据始终放在内存中,是非常容易失落的。比方 服务器敞开或宕机了,内存中的数据就木有了。为了解决这一问题,Redis 提供了 长久化 机制。别离是 RDB 以及 AOF 长久化。
RDB
什么是 RDB 长久化?
RDB 长久化能够在指定的工夫距离内生成数据集的工夫点快照(point-in-time snapshot)。
RDB 的长处?
- RDB 是一种示意某个即时点的 Redis 数据的紧凑文件。RDB 文件实用于备份。例如,你可能想要每小时归档最近24小时的 RDB 文件,每天保留近30天的 RDB 快照。这容许你很容易的复原不同版本的数据集以容灾。
- RDB 非常适合于劫难复原,作为一个紧凑的繁多文件,能够被传输到近程的数据中心。
- RDB 最大化了 Redis 的性能。因为 Redis 父过程长久化时惟一须要做的是启动(fork)一个子过程,由子过程实现所有残余的工作。父过程实例不须要执行像磁盘IO这样的操作。
RDB 在重启保留了大数据集的实例比 AOF 快。
RDB 的毛病?
- 当你须要在Redis进行工作(例如停电)时最小化数据失落,RDB可能不太好。你能够配置不同的保留点(save point)来保留RDB文件(例如,至多5分钟和对数据集100次写之后,然而你能够有多个保留点)。然而,你通常每隔5分钟或更久创立一个RDB快照,所以一旦Redis因为任何起因没有正确敞开而进行工作,你就得做好最近几分钟数据失落的筹备了。
- RDB须要常常调用fork()子过程来长久化到磁盘。如果数据集很大的话,fork()比拟耗时,后果就是,当数据集十分大并且CPU性能不够弱小的话,Redis会进行服务客户端几毫秒甚至一秒。AOF也须要fork(),然而你能够调整多久频率重写日志而不会有损(trade-off)持久性(durability)。
RDB 文件的创立与载入
有个两个 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
上边三个配置的含意是:
- 服务器在 900 秒内,对数据库进行了至多 1 次批改。
- 服务器在 300 秒内,对数据库进行了至多 10 次批改。
- 服务器在 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; // ...}
- dirty 计数器记录间隔上一次胜利执行 SAVE 或 BGSAVE 命令之后,服务器对数据库状态(服务器中的所有数据库)进行了多少次批改(包含写入、删除、更新等操作)。
- lastsave 属性是一个 UNIX 工夫戳,记录了服务器上一次执行 SAVE 或 BGSAVE 命令的工夫。
查看条件是否满足触发 RDB
Redis 的服务器周期性操作函数 serverCron 默认每隔 100 毫秒执行一次,该函数用于对正在运行的服务器进行保护,它的其中一项工作就是查看 save 选项所设置的保留条件是否曾经满足,如果满足的话就执行 BGSAVE 命令。
Redis serverCron 源码解析如下:
程序会遍历并查看 saveparams 数组中的所有保留条件,只有有任意一个条件被满足,服务器就会执行 BGSAVE 命令。
上面是 rdbSaveBackground 的源码流程:
RDB 文件构造
下图展现了一个残缺 RDB 文件所蕴含的各个局部。
redis 文件的最结尾是 REDIS 局部,这个局部的长度是 5 字节,保留着 “REDIS” 五个字符。通过这五个字符,程序能够在载入文件时,疾速查看所载入的文件是否时 RDB 文件。
db_version 长度为 4 字节,他的值时一个字符串示意的整数,这个整数记录了 RDB 文件的版本号,比方 “0006” 就代表 RDB 文件的版本为第六版。
database 局部蕴含着零个或任意多个数据库,以及各个数据库中的键值对数据:
- 如果服务器的数据库状态为空(所有数据库都是空的),那么这个局部也为空,长度为 0 字节。
- 如果服务器的数据库状态为非空(有至多一个数据库非空),那么这个局部也为非空,依据数据库所保留键值对的数量、类型和内容不同,这个局部的长度也会有所不同。
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 长久化
AOF长久化形式记录每次对服务器写的操作,当服务器重启的时候会从新执行这些命令来复原原始的数据,AOF命令以redis协定追加保留每次写的操作到文件开端.Redis还能对AOF文件进行后盾重写,使得AOF文件的体积不至于过大.
AOF 的长处?
- 应用AOF 会让你的Redis更加持久: 你能够应用不同的fsync策略:无fsync,每秒fsync,每次写的时候fsync.应用默认的每秒fsync策略,Redis的性能仍然很好(fsync是由后盾线程进行解决的,主线程会尽力解决客户端申请),一旦呈现故障,你最多失落1秒的数据.
- AOF文件是一个只进行追加的日志文件,所以不须要写入seek,即便因为某些起因(磁盘空间已满,写的过程中宕机等等)未执行残缺的写入命令,你也也可应用redis-check-aof工具修复这些问题.
- Redis 能够在 AOF 文件体积变得过大时,主动地在后盾对 AOF 进行重写: 重写后的新 AOF 文件蕴含了复原以后数据集所需的最小命令汇合。 整个重写操作是相对平安的,因为 Redis 在创立新 AOF 文件的过程中,会持续将命令追加到现有的 AOF 文件外面,即便重写过程中产生停机,现有的 AOF 文件也不会失落。 而一旦新 AOF 文件创建结束,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。
- AOF 文件有序地保留了对数据库执行的所有写入操作, 这些写入操作以 Redis 协定的格局保留, 因而 AOF 文件的内容非常容易被人读懂, 对文件进行剖析(parse)也很轻松。 导出(export) AOF 文件也非常简单: 举个例子, 如果你不小心执行了 FLUSHALL 命令, 但只有 AOF 文件未被重写, 那么只有进行服务器, 移除 AOF 文件开端的 FLUSHALL 命令, 并重启 Redis , 就能够将数据集复原到 FLUSHALL 执行之前的状态。
AOF 的毛病?
- 对于雷同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积。
- 依据所应用的 fsync 策略,AOF 的速度可能会慢于 RDB 。 在个别状况下, 每秒 fsync 的性能仍然十分高, 而敞开 fsync 能够让 AOF 的速度和 RDB 一样快, 即便在高负荷之下也是如此。 不过在解决微小的写入载入时,RDB 能够提供更有保障的最大延迟时间(latency)。
AOF长久化的实现
AOF长久化性能的实现能够分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
命令追加
当 AOF 长久化性能处于关上状态时,服务器在执行完一个写命令之后,会以协定格局将被执行的写命令追加到服务器状态的 aof_buf 缓冲区的开端。
struct redisServer { // ... // AOF 缓冲区 sds aof_buf; // ..};
如果客户端向服务器发送以下命令:
> set KEY VALUEOK
那么服务器在执行这个 set 命令之后,会将以下协定内容追加到 aof_buf 缓冲区的开端;
*3\r\n$3\r\nSET\r\n$3\r\nKEY\r\n$5\r\nVALUE\r\n
AOF 文件的写入与同步
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文件外面蕴含了重建数据库状态所需的所有写命令,所以服务器只有读入并从新执行一遍AOF文件外面保留的写命令,就能够还原服务器敞开之前的数据库状态。 Redis读取AOF文件并还原数据库状态的具体步骤如下:
- 创立一个不带网络连接的伪客户端(fake client):因为Redis的命令只能在客户端上 下文中执行,而载入AOF文件时所应用的命令间接来源于AOF文件而不是网络连接,所以服 务器应用了一个没有网络连接的伪客户端来执行AOF文件保留的写命令,伪客户端执行命令 的成果和带网络连接的客户端执行命令的成果齐全一样。
- 从AOF文件中剖析并读取出一条写命令。
- 应用伪客户端执行被读出的写命令。
- 始终执行步骤2和步骤3,直到AOF文件中的所有写命令都被处理完毕为止。
当实现以上步骤之后,AOF文件所保留的数据库状态就会被残缺地还原进去,整个过程 如下图所示。
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 后盾重写
AOF 重写会执行大量的写操作,这样会影响主线程,所以redis AOF 重写放到了子过程去执行。这样能够达到两个目标:
- 子过程进行AOF重写期间,服务器过程(父过程)能够持续解决命令申请。
- 子过程带有服务器过程的数据正本,应用子过程而不是线程,能够在防止应用锁的状况 下,保证数据的安全性。
然而有一个问题,当子过程重写数据时,主过程仍然在解决新的数据,这也就会造成数据不统一状况。
为了解决这种数据不统一问题,Redis服务器设置了一个AOF重写缓冲区,这个缓冲区在 服务器创立子过程之后开始应用,当Redis服务器执行完一个写命令之后,它会同时将这个写 命令发送给AOF缓冲区和AOF重写缓冲区,如下图:
这也就是说,在子过程执行AOF重写期间,服务器过程须要执行以下三个工作:
- 执行客户端发来的命令。
- 将执行后的写命令追加到AOF缓冲区。
- 将执行后的写命令追加到AOF重写缓冲区。
这样一来能够保障:
- AOF缓冲区的内容会定期被写入和同步到AOF文件,对现有AOF文件的解决工作会如常 进行。
- 从创立子过程开始,服务器执行的所有写命令都会被记录到AOF重写缓冲区外面。
当子过程实现AOF重写工作之后,它会向父过程发送一个信号,父过程在接到该信号之 后,会调用一个信号处理函数,并执行以下工作:
- 将AOF重写缓冲区中的所有内容写入到新AOF文件中,这时新AOF文件所保留的数 据库状态将和服务器以后的数据库状态统一。
- 对新的AOF文件进行改名,原子地(atomic)笼罩现有的AOF文件,实现新旧两个 AOF文件的替换。
这个信号处理函数执行结束之后,父过程就能够持续像平常一样接受命令申请了。
在整个AOF后盾重写过程中,只有信号处理函数执行时会对服务器过程(父过程)造成 阻塞,在其余时候,AOF后盾重写都不会阻塞父过程,这将AOF重写对服务器性能造成的影 响降到了最低。
Redis 混合长久化
Redis 还能够同时应用 AOF 长久化和 RDB 长久化。 在这种状况下, 当 Redis 重启时, 它会优先应用 AOF 文件来还原数据集, 因为 AOF 文件保留的数据集通常比 RDB 文件所保留的数据集更残缺。然而 AOF 复原比较慢,Redis 4.0 推出了混合长久化。
混合长久化: 将 rdb 文件的内容和增量的 AOF 日志文件存在一起。这里的 AOF 日志不再是全量的日志,而是 自长久化开始到长久化完结 的这段时间产生的增量 AOF 日志,通常这部分 AOF 日志很小。
于是在 Redis 重启的时候,能够先加载 RDB 的内容,而后再重放增量 AOF 日志就能够齐全代替之前的 AOF 全量文件重放,重启效率因而大幅失去晋升。
感觉文章不错的话,小伙伴们麻烦点个赞、关个注、转个发一下呗~你的反对就是我写文章的能源。
更多精彩的文章请关注公众号“蘑菇睡不着”。
你越被动就会越被动,咱们下期见~