简介
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既可以手动触发也可以自动触发。
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)自动触发
自动触发的几种方式如下:
1、redis.conf中配置save m n,即在m秒内有n次修改时,自动触发bgsave生成rdb文件;
2、主从复制时,从节点要从主节点进行全量复制时也会触发bgsave操作,生成当时的快照发送到从节点;
3、执行debug reload命令重新加载redis时也会触发bgsave操作;
4、执行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个好处:
1、如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。
2、不会阻塞当前的写操作 。
但这种方式存在潜在风险,如果命令执行完成,但在写日志之前宕机了,会丢失数据。
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重写
1)什么是AOF重写
随着 Redis 不断运行,AOF 的体积也会不断增长,这将导致两个问题:
AOF 耗尽磁盘可用空间
Redis 重启后需要执行 AOF 文件记录的所有写命令来还原数据集,如果 AOF 过大,则还原操作执行的时间就会非常长
为了解决 AOF 体积膨胀问题,Redis 提供了 AOF 重写功能,来对 AOF 文件进行压缩。
AOF文件存储的是Redis中所执行过的指令,就比如一个列表经过不断的添加、删除后会产生n条操作记录。但是我们最后恢复数据的时候,必然只要恢复数据的最后状态就可以了,无需关注一个数据过往的状态,所以AOF重写实际上就是合并这些冗余的指令,将他们合并为一条指令,通过这条指令就可以直接获得Redis中数据最后的状态。这样做既可以实现数据的备份,又可以实现AOF文件的压缩。
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重写缓冲区中的数据追加进去即可。
三、补充
1、RBD与AOF的比较
RBD保存的是内存的快照,且使用了LZF压缩算法,因此文件大小不会超过内存的大小,而AOF文件因为会一直追加操作指令,因此会比较大。
RBD存储的即为内存中的数据,而AOF存储的是指令,因此使用RBD恢复数据会比AOF更快,AOF文件需要通过指令一条条改动记录,可能出现一个列表多次改动才会变为最终状态。
RBD文件使用二进制流的方式存储,不具备可读性,AOF文件了解其结构的情况下可以手动进行数据修复。
RDB方式必然产生fork,这种重量级操作会导致主线程的阻塞,且内存复制需要时间,因此无法实现秒级的持久化,而AOF可以。
2、RDB与AOF混用
由上一节我们可以看出,RBD与AOF各有优劣,于是4.0之后允许我们将他们混用。其方式为RBD以一定的频率执行,在两次快照之间,使用 AOF 日志记录这期间的所有命令操作。
这样一来,快照不用很频繁地执行,这就避免了频繁 fork 对主线程的影响。而且,AOF 日志也只用记录两次快照间的操作,也就是说,不需要记录所有操作了,因此,就不会出现文件过大的情况了,也可以避免重写开销。
3、数据如何恢复
Redis重启时会自动读取持久化文件,过程如下:
1、Redis重启时判断是否开启AOF,如果开启了AOF,那么就优先加载AOF文件(这是由于AOF最多损失1s的数据,因此持久化文件更加完整)。
2、如果AOF存在,那么就去加载AOF文件,加载成功的话Redis重启成功,如果AOF文件加载失败,那么会打印日志表示启动失败,此时可以去修复AOF文件后重新启动。
3、若AOF文件不存在,那么Redis就会转而去加载RDB文件,如果RDB文件不存在,Redis直接启动成功。
4、如果RDB文件存在就会去加载RDB文件恢复数据,如加载失败则打印日志提示启动失败,如加载成功,那么Redis重启成功,且使用RDB文件恢复数据。
4、使用建议
redis String类型的数据类型,是二进制安全的,那么我们如何理解这个二进制安全呢?
如果Redis中的数据并不是特别敏感或者可以通过其它方式重写生成数据,可以关闭持久化,如果丢失数据可以通过其它途径补回。
自己制定策略定期检查Redis的情况,然后可以手动触发备份、重写数据。
单机如果部署多个实例,要防止多个机器同时运行持久化、重写操作,防止出现内存、CPU、IO资源竞争,让持久化变为串行。
可以加入主从机器,利用一台从机器进行备份处理,其它机器正常响应客户端的命令。
RDB持久化与AOF持久化可以同时存在,配合使用。