参考资料:
写在开头:本文为学习后的总结,可能有不到位的地方,错误的地方,欢迎各位指正。
目录
一、简介
Redis 是内存型数据库,为了保证数据在断电后不会丢失,需要将内存中的数据持久化到硬盘上,以防止突然宕机造成的数据丢失。
在Redis的官网中,明确说明的持久化方案有2种(传送门):
RDB: 即快照模式,将内存中某一时刻的所有数据都写入到硬盘中。
AOF :追加文件模式,存储的是Redis中所执行的命令。
这两种模式在以前只能单独使用,而4.0版本后可以混合使用。
二、RDB
1、RDB简介
RDB 就是 Redis DataBase 的缩写,即快照方式,RDB持久化是指将某个时间点的所有 Redis 数据保存到一个经过压缩的二进制文件(RDB 文件)中。
创建 RDB 后,用户可以对 RDB 进行备份,可以将 RDB 复制到其他服务器从而创建具有相同数据的服务器副本,还可以在重启服务器时使用。
RDB 既可以手动执行,也可以根据服务器配置选项定期执行。该功能可以将某个时间点的数据库状态保存到一个 RDB 文件中。
2、RDB 的触发
RDB既可以手动触发也可以自动触发。
2.1、手动触发
Redis 提供save和bgsave这两个命令用于生成 RDB 文件。
- SAVE 命令会阻塞 Redis 服务器进程,直到 RDB 创建完成为止,在阻塞期间,服务器不能响应任何命令请求。
- BGSAVE 命令会派生出(fork)一个子进程,然后由子进程负责创建 RDB 文件,服务器进程(父进程)继续处理命令请求。
bgsave流程总结如下:
1、redis客户端执行bgsave命令或者自动触发bgsave命令。
2、主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回。
3、如果不存在正在执行的子进程,那么就fork一个新的子进程进行持久化数据,fork过程是阻塞的,fork操作完成后主进程即可执行其他操作。
4、子进程先将数据写入到临时的rdb文件中,待快照数据写入完成后再原子替换旧的rdb文件。
5、同时发送信号给主进程,通知主进程rdb持久化完成。
上文中第2点提到,“主进程判断当前是否已经存在正在执行的子进程,如果存在,那么主进程直接返回”,这是因为 BGSAVE命令执行期间,SAVE、BGSAVE、BGREWRITEAOF 这三个命令会与当前的 BGSAVE 操作产生竞态条件,降低性能,因此会被拒绝。
另外注意,快照文件只有1个,每次进行RBD持久化都会先写入一个临时文件,在写入完成后再替换旧的快照文件。当然,如果想保存多份快照文件,我们可以创建一个定期任务(cron job),每小时将一个 RDB 文件备份到一个文件夹,以此来实现我们的目的。
2.2、自动触发
自动触发的几种方式如下:
- redis.conf中配置save m n,即在m秒内有n次修改时,自动触发bgsave生成rdb文件;
- 主从复制时,从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点;
- 执行debug reload命令重新加载redis时也会触发bgsave操作;
- 执行shutdown命令时,如果没有开启aof持久化,那么也会触发bgsave操作;
这里我们只介绍save配置的方式,在 redis.conf中进行如下配置,当满足如下条件之一时,就会自动执行bgsave。
save 900 1 -- 900 秒内,至少对数据库进行了 1 次修改
save 300 10 -- 300 秒内,至少对数据库进行了 10 次修改
save 60 10000 -- 60 秒内,至少对数据库进行了 10000 次修改
3、RDB相关配置
# 文件名称
dbfilename dump.rdb
# 文件保存路径
dir /home/work/app/redis/data/
# 如果持久化出错,主进程是否停止写入
stop-writes-on-bgsave-error yes
# 是否压缩
rdbcompression yes
dbfilename:RDB文件在磁盘上的名称。
dir:RDB文件的存储路径。默认设置为“./”,也就是Redis服务的主目录。
stop-writes-on-bgsave-error:当配置为yes时,如果快照操作出现异常(例如操作系统用户权限不够、磁盘空间写满等等)时,Redis就会禁止写操作。这个特性的主要目的是使运维人员在第一时间就发现Redis的运行错误,并进行解决。
rdbcompression:该属性将在字符串类型的数据被快照到磁盘文件时,启用LZF压缩算法。Redis官方的建议是请保持该选项设置为yes,因为“it’s almost always a win”。
4、持久化期间的数据同步
经过前文介绍我们了解到的主线程只有在fork子进程时才会阻塞,所以在持久化期间依然会提供服务,这就产生了一个问题,RBD持久化要将整个Redis中的数据都拷贝一份进行保存,这个操作必然不是短时间内能够完成的,如果在这个过程中出现了数据的修改,该如何保证数据的一致性?
当bgsave子进程执行时,如果主线程要修改一块数据(例如图中的键值对 C),那么,这块数据就会被复制一份,生成该数据的副本,主线程的修改就会发生在这个副本上,而原内存中的值不变。然后,bgsave 子进程则会把这个副本数据写入 RDB 文件,同时快照写完后这个副本内的数据还会再同步回原来的内存块中,以此来保证内存与RBD快照中的数据一致性。
二、AOF
1、AOF简介
AOF是以 文本日志形式 将 所有写命令以 Redis 命令请求协议格式追加到 AOF 文件的末尾,以此来记录数据的变化。当服务器重启时,会重新载入和执行 AOF 文件中的命令,就可以恢复原始的数据。
需要注意的是,AOF先写内存,后写日志。这么有2个好处:
- 如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。
- 不会阻塞当前的写操作 。
但这种方式存在潜在风险,如果命令执行完成,但在写日志之前宕机了,会丢失数据。
2、AOF 的创建
AOF记录的是Redis中执行的命令,AOF默认是不开启的,要开启的话需要在redis.conf中配置appendonly yes。AOF创建的过程被分为了2步,Redis 命令请求会先保存到内存中的 AOF 缓冲区,再定期写入并同步到 AOF 文件。这两步的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤。
- 命令追加 :当 Redis 服务器开启 AOF 功能时,服务器在执行完一个写命令后,会以 Redis 命令协议格式将被执行的写命令追加到 AOF 缓冲区的末尾。
- 文件写入和文件同步 :Redis 的服务器根据 appendfsync 选项来判断 AOF 缓冲区内容是否需要写入和同步到 AOF 文件中。
appendfsync 提供了三种写回策略:
- Always,同步写回:每个写命令执行完,立马同步地将日志写回磁盘;
- Everysec,每秒写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,每隔一秒把缓冲区中的内容写入磁盘;
- No,操作系统控制的写回:每个写命令执行完,只是先把日志写到AOF文件的内存缓冲区,由操作系统决定何时将缓冲区内容写回磁盘。
3、AOF相关配置
# appendonly参数开启AOF持久化
appendonly no
# AOF持久化的文件名,默认是appendonly.aof
appendfilename "appendonly.aof"
# AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的
dir ./
# 同步策略
# appendfsync always
appendfsync everysec
# appendfsync no
# aof重写期间是否同步
no-appendfsync-on-rewrite no
# 重写触发配置
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
appendfsync:AOF写回与同步的策略
no-appendfsync-on-rewrite:AOF 重写时不支持追加命令
auto-aof-rewrite-percentage:AOF 重写百分比
auto-aof-rewrite-min-size:AOF 重写文件的最小大小
4、持久化期间的数据同步
在上文介绍RDB的内容中,有介绍过当快照创建过程中数据发生修改时的处理方案,同样的问题AOF也会遇到,这里AOF与RBD的处理方法类似,但也有些区别,下面我们简单解释下。
当fork子进程时,子进程时会拷贝父进程的页表,即虚实映射关系(虚拟内存和物理内存的映射索引表),而不会拷贝物理内存。
5、AOF重写
5.1、什么是AOF重写
随着 Redis 不断运行,AOF 的体积也会不断增长,这将导致两个问题:
- AOF 耗尽磁盘可用空间
- Redis 重启后需要执行 AOF 文件记录的所有写命令来还原数据集,如果 AOF 过大,则还原操作执行的时间就会非常长
为了解决 AOF 体积膨胀问题,Redis 提供了 AOF 重写功能,来对 AOF 文件进行压缩。
AOF文件存储的是Redis中所执行过的指令,就比如一个列表经过不断的添加、删除后会产生n条操作记录。但是我们最后恢复数据的时候,必然只要恢复数据的最后状态就可以了,无需关注一个数据过往的状态,所以AOF重写实际上就是合并这些冗余的指令,将他们合并为一条指令,通过这条指令就可以直接获得Redis中数据最后的状态。这样做既可以实现数据的备份,又可以实现AOF文件的压缩。
5.2、AOF重写过程
首先,我们在redis.conf中配置auto-aof-rewrite-percentage、auto-aof-rewrite-min-size来设定AOF重写的时机,例如auto-aof-rewrite-percentage设为100,则表示 AOF文件的体积比上一次重写后的体积大了至少 100% 时则重写,auto-aof-rewrite-min-size则表示当AOF文件多大时开始重写(单位为M)。
在重写时,Redis会fork出一个bgrewriteaof子进程(和bgsave一样,这个操作会造成主线程的阻塞),并把主线程的内存拷贝一份给这个bgrewriteaof子进程。然后,bgrewriteaof子进程就可以在不影响主线程的情况下,逐一把拷贝的数据写成操作,记入重写日志。在重写完成后,再将原来的AOF文件给替换掉。
当然这里有一个问题就是数据的追加,当我们在重写日志的时候Redis并没有阻塞,因此还是会继续接收修改操作,那么这个操作如何追加到正在重写的AOF日志中呢?这里Redis会为重写进程创建一个AOF重写缓冲区,当有操作产生时,会同时记录到AOF缓冲区与AOF重写缓冲区,等重写日志完成后,再将AOF重写缓冲区中的数据追加进去即可。
三、补充
RBD与AOF的比较
RBD保存的是内存的快照,且使用了LZF压缩算法,因此文件大小不会超过内存的大小,而AOF文件因为会一直追加操作指令,因此会比较大。
RBD存储的即为内存中的数据,而AOF存储的是指令,因此使用RBD恢复数据会比AOF更快,AOF文件需要通过指令一条条改动记录,可能出现一个列表多次改动才会变为最终状态。
RBD文件使用二进制流的方式存储,不具备可读性,AOF文件了解其结构的情况下可以手动进行数据修复。
RDB方式必然产生fork,这种重量级操作会导致主线程的阻塞,且内存复制需要时间,因此无法实现秒级的持久化,而AOF可以。
RDB与AOF混用
由上一节我们可以看出,RBD与AOF各有优劣,于是4.0之后允许我们将他们混用。其方式为RBD以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
数据如何恢复
Redis重启时会自动读取持久化文件,过程如下:
- Redis重启时判断是否开启AOF,如果开启了AOF,那么就优先加载AOF文件(这是由于AOF最多损失1s的数据,因此持久化文件更加完整)。
- 如果AOF存在,那么就去加载AOF文件,加载成功的话Redis重启成功,如果AOF文件加载失败,那么会打印日志表示启动失败,此时可以去修复AOF文件后重新启动。
- 若AOF文件不存在,那么Redis就会转而去加载RDB文件,如果RDB文件不存在,Redis直接启动成功。
- 如果RDB文件存在就会去加载RDB文件恢复数据,如加载失败则打印日志提示启动失败,如加载成功,那么Redis重启成功,且使用RDB文件恢复数据。
使用建议
- 如果Redis中的数据并不是特别敏感或者可以通过其它方式重写生成数据,可以关闭持久化,如果丢失数据可以通过其它途径补回。
- 自己制定策略定期检查Redis的情况,然后可以手动触发备份、重写数据。
- 单机如果部署多个实例,要防止多个机器同时运行持久化、重写操作,防止出现内存、CPU、IO资源竞争,让持久化变为串行。
- 可以加入主从机器,利用一台从机器进行备份处理,其它机器正常响应客户端的命令。
- RDB持久化与AOF持久化可以同时存在,配合使用。