一杯茶的功夫,上手 Redis 持久化机制
开篇
Redis 作为最常用的内存数据库,通常来说数据存储在内存中,为了避免 Redis 服务器进程退出导致内存中的数据消失。Redis 提出了持久化机制,也就是把内存中的数据保存到磁盘中,从而提高数据存储的可靠性。为此主流数据库会提供两类持久化方案,它们是“快照”存储和“日志”存储。相应地 Redis 提供了 RDB 持久化和 AOF 持久化与之对应。其中 RDB 是以快照的方式存储内存数据到磁盘上,而 AOF 是以日志追加的方式进行存储。下面就围绕这两种持久化方式展开如下内容:
RDB 文件结构
RDB 触发机制以及流程
AOF 持久化流程
AOF 缓冲区同步文件策略
AOF 重写
RDB 和 AOF 的比较
RDB 持久化
RDB 是 Redis Database 的缩写,其作用是在某一个时间点,将 Redis 存储在内存中的数据生成快照并存储到磁盘等介质上,存在这个磁盘介质上的文件就是 RDB 文件。“快照”顾名思义就是好像照相一样保存当时的数据,这里的 RDB 文件是一个二进制的文件,并且是经过压缩的。因为 RDB 文件是保存在硬盘中的,即使 Redis 服务器进程退出,甚至运行 Redis 服务器的计算机宕机,但只要 RDB 文件仍然存在,Redis 服务器就可以用它来还原数据库状态。如图 1 所示,可以想象 Redis 数据库在时间轴上有位于不同时间点的时候都有一个数据库状态,可以把它们想象成一个个切片。图上标注出两个时间点的两个数据库切片,RDB 持久化做的事情就是顺着绿色箭头的方向将数据库状态的“切片”以 RDB 文件的形式保存到磁盘中。
图 1 将数据库状态保存为 RDB 文件
RDB 文件结构
虽然说 RDB 是一个压缩过的二进制文件,但是它的文件结构也需要有基本的了解,这样有助于我们理解其发挥的作用。如表格 1 所示,RDB 文件由 5 个部分组成,按照从左到右的顺序依次是:
文件最开头是“REDIS”部分,其长度为 5 个字节,保存着“REDIS”五个字符。通过这五个字符,程序可以在载入文件时可以判断所载入的文件是否是 RDB 文件。
接下来是“db_version”长度为 4 字节,是一个字符串表示的整数,它记录了 RDB 文件的版本号,例如:"0006"就代表 RDB 文件的版本为第六版。
“databases”中可以包含着零个或任意多个数据库。
“EOF”常量的长度为 1 字节,是 RDB 文件正文结束的标识,当载入程序读取到个值的时,就意味着数据库的所有键值对都已经加载完毕了。
“checksum”是一个 8 字节长的无符号整数,保存着一个校验和。这个校验和是通过对“REDIS”、“dbversion”、“databases”、以及“EOF”四个部分的内容进行计算得出的。Redis 服务器在载入 RDB 文件时,会将载入数据所计算出的校验和与 check_sum 所记录的校验和进行对比,以此来判断 RDB 文件是否损坏。
表格 1 RDB 的文件结构
如表格 2 所示,其表示一个 databases 部分为空的 RDB 文件:文件以"REDIS"开头,表示这是一个 RDB 文件,之后的"0006"表示数据库版本是第六版。因为 databases 为空所以这里没有数据库的信息,所以版本号之后直接跟着“EOF”常量,最后的 6265312314761934562 是文件的校验和。
表格 2 RDB 文件例子
RDB 触发机制以及流程
在认识了 RDB 文件的结构以后,再来看看 Redis 如何触发 RDB 持久化操作,以及其具体流程是如何的。这里包括三部分的内容,分别是 save 同步方式触发、bgsave 异步方式触发以及自动配置化的方式触发。
save 同步方式触发 RDB 持久化
如图 2 所示,描述了 save 同步方式持久化 RDB 的过程:
Redis Client 端通过向 Redis Server 发起 save 命令请求 RDB 持久化操作。
Redis Server 接受到命令以后,将当前数据库快照保存到 RDB 文件中。
由于 save 命令是同步操作,因此如果此时有其他 Redis Client 也向 Redis Server 发起 save 操作,会被阻塞直到第一个 Redis Client 完成 save 命令为止。
图 2 save 同步方式触发 RDB 持久化
bgsave 同步方式触发 RDB 持久化
如图 3 所示,异步方式过程如下:
依旧是 Redis Client 发起命令,不过命令改成了 bgsave(background save 有后台运行的意思),照旧请求 Redis Server。
Redis Server 接受到请求以后会 fork 出一个 Redis 子进程。
这个子进程用来创建 RDB 文件。由于这个过程是异步的,因此 Redis Server 在启动子进程以后还可以接受其他请求。
Redis 子进程创建 RDB 文件以后会把成功的消息返回给 Redis Server。
由于 bgsave 命令是异步操作,如果此时有其他 Redis Client 同时请求 Redis Server 并不会被阻塞。Redis Server 会响应请求,同样也会 fork 出对应的子进程进行 RDB 文件的创建。
图 3 bgsave 异步方式触发 RDB 持久化
自动配置化的方式触发
如图 4 所示,这种方式可以理解为读取配置文件的方式。看下面的三个步骤:
Redis Server 直接读取 Redis 配置文件的内容,获取 RDB 持久化的信息。
在 Redis 配置文件中配置了对应的 save 命令用来代替 Redis Client 请求的命令。其配置内容包括 save 命令、秒和修改次数。按照图中的例子来说,“save 500 3” 的意思是,在 500 秒的时间内如果 Redis 数据库有 3 次修改就进行 save 请求,也就是请求 RDB 的持久化操作。
一旦满足配置文件中的条件,Redis Server 就会执行对应的 save 操作进行持久化。
图 4 读取配置文件进行 RDB 持久化
一般而言 Redis 的配置信息会放到 redis.conf 配置文件中进行存储,其包含很多内容,这里我们就 RDB 持久化的部分给大家做简单介绍。redis.conf 文件中找到“SNAPSHOTTING”(快照)的部分。看如下几个配置项:
关于 RDB 持久化恢复 Redis 数据方面也比较简单,将 RDB 持久化文件 (例如:dump.rdb) 移动到 Redis 安装目录并启动 Redis 服务就可以了。可以通过 Redis 中的“CONFIG GET dir”命令获取 Redis 的安装目录。
由于 RDB 是一个压缩的二进制文件,其代表 Redis 在某一个时间点上的快照。其适合数据库备份和全量复制的场景。比如定期给数据库进行备份,把 RDB 文件拷贝到其他的服务器上,以及用于灾备。同样是因为压缩的原因,RDB 的加载速度比 AOF 也要快。
AOF 持久化
上面介绍了 RDB 的执行方式和流程,这种方式没有办法做到实时持久化的要求。因为无论是 save 还是 bgsave 每次运行都要消耗大量的资源(CPU、内存、磁盘)。随着数据库本身容量的增加每次备份的数据量也随之增加。同时 RDB 是二进制保存,当 Redis 版本演进过程中有多个格式的 RDB 版本,会存在老版本 RDB 与新版本格式兼容的问题。正式因为 RDB 的这些问题,Redis 提出了 AOF 的持久化方式。AOF(append only file),是以日志的方式记录每次写入的命令,在 Redis Server 启动的时候会重新执行 AOF 文件中的命令,从而达到恢复数据的目的。AOF 可以解决数据持久化的实时性问题,也是当前 Redis 主流的持久化方式。
AOF 持久化流程
上面提到了 AOF 持久化的过程就是日志不断追加的过程,这里通过图 5 给大家介绍具体流程:
Redis Client 作为命令的来源,会有多个源头以及源源不断的请求命令。
在这些命令到达 Redis Server 以后,并不是直接写入 AOF 文件,会将其这些命令先放入 AOF 缓存中进行保存。这里的 AOF 缓冲区实际上是内存中的一片区域,存在的目的是当这些命令达到一定量以后再写入磁盘,避免频繁的磁盘 IO 操作。
AOF 缓冲会根据对应的策略将命令写入磁盘上的 AOF 文件。
AOF 文件随着写入文件内容的增加,会根据规则进行命令的合并,这里叫做 AOF 重写,从而起到 AOF 文件压缩的目的。
当 Redis Server 服务器重启的时候会从 AOF 文件载入数据。
图 5 AOF 处理流程图
AOF 缓冲区同步文件策略
上面提到了 Redis 会将命令先写入到 AOF 缓冲区,再写入 AOF 文件。这里介绍一下 AOF 缓冲区同步文件的三个策略。
always 策略:命令写入 AOF 缓冲区以后会调用系统 fsync 操作同步到 AOF 文件,fsync 完成后线程返回。这里的 fsync 是针对单个文件的操作,在进行磁盘同步的时候会阻塞直到写入磁盘完成以后返回,从而保证数据持久化的完成。
everysec 策略:命令写入 AOF 缓冲区以后调用 write 操作,write 完成后线程返回。此操作会有专门线程执行每秒执行一次。这里的 write 操作会触发延迟写(delayed write)机制,Linux 内核提供页缓冲区来提高硬盘 IO 性能。也就是说 write 操作写入系统缓冲区以后就返回了,同步硬盘依赖于操作系统调度机制完成。(Redis 默认配置)
no 策略:此种刷新策略是根据操作系统来决定的,也就是由操作系统来决定什么时候将缓冲区的数据写入到磁盘中。由于是操作系统来决定持久化,所以这种方式是不可控的。
AOF 重写
AOF 缓冲区会将 Redis Client 请求的命令源源不断地同步到 AOF 文件中,同时 AOF 文件会不断增大,这里就需要 AOF 重写。AOF 重写就是把 Redis 进程内的数据转化为写命令同步到新的 AOF 文件的过程。其目的就是使重写后的 AOF 文件变得更小:
进程内已经超时的数据不会再写入 AOF 文件中。
旧 AOF 文件含有的无效命令,可以通过进程内的数据直接生成,新的 AOF 文件只保留最终的数据写入命令。例如就文件中存在三条命令,它们依次是“set hello A”、 “set hello B”和“set hello C”,对同一个 key 进行负值只有最后一句“set hello C”是起效的,所以这三条命令会被“set hello C”一条命令替换,并且保存到新的 AOF 文件中。
另外,多条写命令可以合并成一个。例如依次存在三个命令:“lpush list A”、 “lpush list B”和“lpush list C”,这里就可以合并为一条命令“lpush list A B C”。
AOF 重写不仅降低了文件的占用空间,同时更小的 AOF 也可以更快地被 Redis 加载。
说完了 AOF 重写的定义以后,下面来看看 AOF 重写的流程。一般而言有两种方式可以执行重写操作,分别是:bgrewriteaof 命令和 AOF 重写配置。
bgrewriteaof 命令重写
如图 6 所示,整个执行过程由三步组成:
Redis Client 发起 bgrewriteaof 命令,这个命令是一个异步命令。由于 Redis Server 在接受 bgrewriteaof 命令的同时,还可以接受其他 Redis Client 的命令,因此后面的第二步和第三步实际上是并行进行的。第二步:进行 AOF 重写,在同时第三步:还会接受其他非重写的命令请求。
Redis Server 接受到这个命令以后,会启动一个 Redis 的子进程用来执行 AOF 重写操作。这个重写过程实际上是针对 Redis 内存中的数据进行回溯,也就是下方红色区域的“AOF 重写缓冲区”。
正如在第一步中提到的,在进行第二步的同时第三步还在接受客户端的请求并且通过“AOF 缓冲区”保存到“旧 AOF 文件”中。
最终,完成 AOF 重写操作以后将“新 AOF 文件”写入到“旧 AOF 文件”中完成 AOF 重写。
图 6 AOF 重写流程图
AOF 配置重写
实际上是通过 AOF 的配置文件中的配置值来确定重写的时机。配置如下:
通过上面的配置可以得到 AOF 重写的机制如下:
当 AOF 文件当前尺寸大于 AOF 重写的最小尺寸的时候就触发重写机制。通过上面配置来形成表达式就是:aof-current-size> auto-aof-rewrite-min-size
当 AOF 文件当前尺寸减去 AOF 文件本身尺寸的值除以 AOF 文件本身的尺寸得到的结果大于 AOF 文件重写比率的时候就需要出发重写机制。
表达式就是:aof-current-size- aof-base-size/ aof-base-size > auto-aof-rewrite-percentage
由于 Redis 的配置文件中 RDB 是默认配置。AOF 需要手动开启。需要到 Redis 的配置文件 redis.conf 中进行如下设置。
与 RDB 持久化文件恢复数据一样,只需要将 AOF 文件 移动到 Redis 安装目录,并启动 Redis 服务就可以在 Redis 启动的时候加载 AOF 文件恢复数据了。
通过上面对 AOF 的描述可以看出,AOF 具有数据完整,安全性高(秒级数据丢失)等优点。同时 AOF 文件以追加日志文件的形式存在,且写入操作是以 Redis 协议格式保存的,因此其内容是可读性强,适合误删时的紧急恢复。不过,相对于 RDB 而言其文件尺寸较大,会占用更多 Redis 启动加载数据的时间。
RDB 和 AOF 的比较
前面介绍了 RDB 和 AOF 两种机制,这里将它们做一个简单对比。
启动优先级:假设 Redis 同时打开了 RDB 和 AOF 持久化功能,当 Redis 重启的时候会优先加载 AOF,因为 AOF 数据更新的频率更高,会保存更新的数据。
体积大小/恢复速度:RDB 是使用二进制压缩的模式保存,因此体积会比较小,在 Redis 恢复的时候加载的速度也会更快。相反 AOF 写入的是日志的形式,因此体积会较大,恢复速度也会慢些。
数据安全性:RDB 是以快照的模式保存数据,对数据的保存不是实时性的,会有丢失数据的可能性。而在这方面 AOF 的日志方式数据丢失的几率会比 RDB 好很多(例如:everysec)。
资源消耗:RDB 显示需要消耗的资源会更大,因为每次将全量的数据保存到磁盘中。而 AOF 每次可以保存增量的 Redis 数据。
总结
本文从为什么需要数据库持久化作为切入点,谈到 Redis 中的两类数据库持久化机制:RDB 和 AOF。其中针对 RDB 持久化通过介绍 RDB 文件结构、触发持久化的机制和流程进行了阐述。其包括:save 同步方式、bgsave 同步方式和自动配置方式。针对 AOF 持久化,通过 AOF 持久化流程、缓冲区同步文件策略以及 AOF 重写机制进行了介绍。其中缓冲区同步策略包括:always 策略、everysec 策略和 no 策略。
把本文学习笔记的思维导图分享如下。
版权声明: 本文为 InfoQ 作者【崔皓】的原创文章。
原文链接:【http://xie.infoq.cn/article/2ff9dc17dca3a7c7913cfe934】。文章转载请联系作者。
评论