一、为什么要持久化
- Redis是内存数据库,宕机后数据会消失;
- Redis重启后快速恢复数据,要提供持久化机制;
- Redis持久化是为了快速的恢复数据而不是为了存储数据;
- Redis有两种持久化方式:RDB和AOF;
注意:Redis持久化不保证数据的完整性。
当Redis用作DB时,DB数据要完整,所以一定要有一个完整的数据源(文件或者mysql),在系统启动时,从这个完整的数据源中将数据load到redis中;
通过info命令可以查看关于持久化的信息:Persistence
# Persistence loading:0 rdb_changes_since_last_save:0 rdb_bgsave_in_progress:0 rdb_last_save_time:1614661770 rdb_last_bgsave_status:ok rdb_last_bgsave_time_sec:-1 rdb_current_bgsave_time_sec:-1 aof_enabled:0 aof_rewrite_in_progress:0 aof_rewrite_scheduled:0 aof_last_rewrite_time_sec:-1 aof_current_rewrite_time_sec:-1 aof_last_bgrewrite_status:ok aof_last_write_status:ok
持久化流程
既然redis的数据可以保存在磁盘上,那么这个流程是什么样的呢?
要有下面五个过程:
(1)客户端向服务端发送写操作(数据在客户端的内存中)。
(2)数据库服务端接收到写请求的数据(数据在服务端的内存中)。
(3)服务端调用write这个系统调用,将数据往磁盘上写(数据在系统内存的缓冲区中)。
(4)操作系统将缓冲区中的数据转移到磁盘控制器上(数据在磁盘缓存中)。
(5)磁盘控制器将数据写到磁盘的物理介质中(数据真正落到磁盘上)。
这5个过程是在理想条件下一个正常的保存流程,但是在大多数情况下,我们的机器等等都会有各种各样的故障,这里划分了两种情况:
(1)Redis数据库发生故障,只要在上面的第三步执行完毕,那么就可以持久化保存,剩下的两步由操作系统替我们完成。
(2)操作系统发生故障,必须上面5步都完成才可以。
在这里只考虑了保存的过程可能发生的故障,其实保存的数据也有可能发生损坏,需要一定的恢复机制,不过在这里就不再延伸了。现在主要考虑的是redis如何来实现上面5个保存磁盘的步骤。它提供了两种策略机制,也就是RDB和AOF。
二、RDB
RDB(Redis DataBase),是redis默认的存储方式,RDB其实就是把数据以快照的形式保存在磁盘上。什么是快照呢,你可以理解成把当前时刻的数据拍成一张照片保存下来。关注的是这一刻的数据 不关注过程。
RDB持久化是指在指定的时间间隔内将内存中的数据集快照写入磁盘。也是默认的持久化方式,这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。
触发快照的方式
- 执行save或者bgsave命令
- 符合自定义配置的快照规则
- 执行flushall命令
- 执行主从复制操作 (第一次)
(1)save命令触发方式
在客户端输入save命令
127.0.0.1:6379> save OK (0.62s)
该命令会阻塞当前Redis服务器,执行save命令期间,Redis不能处理其他命令,直到RDB过程完成为止。
执行完成时候如果存在老的RDB文件,就把新的替代掉旧的。我们的客户端可能都是几万或者是几十万,这种方式显然不可取。
save命令具体流程如下:
(2)bgsave命令触发方式:RDB执行流程(原理)
在客户端输入bgsave命令
127.0.0.1:6379> bgsave Background saving started
执行该命令时,Redis会在后台异步进行快照操作,快照同时还可以响应客户端请求。
bgsave命令具体流程如下:RDB执行流程(原理)
流程:
- Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof(aof文件重写命令)的子进程,如果在执行则bgsave命令直接返回。
- 父进程执行fork(调用OS函数复制主进程)操作创建子进程,这个复制过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令。
- 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令。
- 子进程创建RDB文件,根据父进程内存快照先生成临时快照文件,完成后对原有文件进行原子替换。(RDB始终完整)
- 子进程发送信号给父进程表示完成,父进程更新统计信息。
- 父进程fork子进程后,继续工作。
注意:
- 复制的时候父进程阻塞(如果数据太多就会redis阻塞影响性能)
- fork:调用操作系统的fork函数,主进程复制自己产生一个子进程,子进程包括主进程这一刻的操作和数据;
- 父进程先生成临时快照文件再去原子替换:为了保证RDB文件一直是完整的;
(3)配置参数定期执行
自动触发是由我们的配置文件来完成的。
在redis.conf中配置:save <seconds> <changes> 多少秒内 数据变了多少
save "" # 不使用RDB存储 不能主从 save 900 1 # 表示15分钟(900秒钟)内至少1个键被更改则进行快照。 save 300 10 # 表示5分钟(300秒)内至少10个键被更改则进行快照。 save 60 10000 # 表示1分钟内至少10000个键被更改则进行快照。
save:这里是用来配置触发 Redis的 RDB 持久化条件,也就是什么时候将内存中的数据保存到硬盘。比如“save m n”,表示m秒内数据集存在n次修改时,自动触发bgsave。如果不需要持久化,那么你可以注释掉所有的 save 行来停用保存功能。
其他参数配置:
- stop-writes-on-bgsave-error :默认值为yes。当启用了RDB且最后一次后台保存数据失败,Redis是否停止接收数据。这会让用户意识到数据没有正确持久化到磁盘上,否则没有人会注意到灾难(disaster)发生了。如果Redis重启了,那么又可以重新开始接收数据了
- rdbcompression ;默认值是yes。对于存储到磁盘中的快照,可以设置是否进行压缩存储。
- rdbchecksum :默认值是yes。在存储快照后,我们还可以让redis使用CRC64算法来进行数据校验,但是这样做会增加大约10%的性能消耗,如果希望获取到最大的性能提升,可以关闭此功能。
- dbfilename :设置快照的文件名,默认是 dump.rdb
- dir:设置快照文件的存放路径,这个配置项一定是个目录,而不能是文件名。
我们可以修改这些配置来实现我们想要的效果。
因为第三种方式是配置的,所以我们对前两种进行一个对比:
RDB文件的结构
1、头部5字节固定为“REDIS”字符串
2、4字节“RDB”版本号(不是Redis版本号),当前为9,填充后为0009
3、辅助字段,以key-value的形式
4、存储数据库号码
5、字典大小
6、过期key
7、主要数据,以key-value的形式存储
8、结束标志
9、校验和,就是看文件是否损坏,或者是否被修改。
可以用winhex打开dump.rdb文件查看:
RDB的优缺点
优点:
- RDB是二进制压缩文件,占用空间小,便于传输(传给slaver) ,全量备份,非常适合用于进行备份和灾难恢复。
- 主进程fork子进程(异步),可以最大化Redis性能,主进程不能太大(redis的数据不能太大),复制过程中主进程阻塞。
- RDB 在恢复大数据集时的速度比 AOF 的恢复速度要快。
缺点:不保证数据完整性,会丢失最后一次快照以后更改的所有数据。(解决:AOF进行辅助)
注意:复制的过程中主进程阻塞(fork的时候),故redis的数据不能太大,不然会影响性能;
三、AOF
AOF(append only file)是Redis的另一种持久化方式。Redis默认情况下是不开启的。开启AOF持久化后。
Redis 将所有对数据库进行过写入的命令(及其参数)(RESP)记录到 AOF 文件, 以此达到记录数据库状态的目的,这样当Redis重启后只要按顺序回放这些命令就会恢复到原始状态了。
AOF会记录过程,RDB只管结果(只管这一刻的结果)
AOF持久化实现
配置redis.conf
# 可以通过修改redis.conf配置文件中的appendonly参数开启 appendonly yes # AOF文件的保存位置和RDB文件的位置相同,都是通过dir参数设置的 dir ./ # 默认的文件名是appendonly.aof,可以通过appendfilename参数修改 appendfilename appendonly.aof # AOF保存模式 appendfsync no // 不保存 appendfsync everysec // 每一秒钟保存一次(默认) appendfsync always // 没执行一个命令保存一次(不推荐)
AOF原理
AOF文件中存储的是redis的命令,同步命令到 AOF 文件的整个过程可以分为三个阶段:
- 命令传播:Redis 将执行完的命令、命令的参数、命令的参数个数等信息发送到 AOF 程序中。
- 缓存追加:AOF 程序根据接收到的命令数据,将命令转换为网络通讯协议的格式,然后将协议内容追加 到服务器的 AOF 缓存中。
- 文件写入和保存:AOF 缓存中的内容被写入到 AOF 文件末尾,如果设定的 AOF 保存条件被满足的话, fsync 函数或者 fdatasync 函数会被调用,将写入的内容真正地保存到磁盘中。
第一阶段:命令传播
当一个 Redis 客户端需要执行命令时, 它通过网络连接, 将协议文本发送给 Redis 服务器。服务器在 接到客户端的请求之后, 它会根据协议文本的内容, 选择适当的命令函数, 并将各个参数从字符串文 本转换为 Redis 字符串对象( StringObject )。每当命令函数成功执行之后, 命令参数都会被传播到 AOF 程序。
第二阶段:缓存追加
当命令被传播到 AOF 程序之后, 程序会根据命令以及命令的参数, 将命令从字符串对象转换回原来的 协议文本。协议文本生成之后, 它会被追加到 redis.h/redisServer 结构的 aof_buf 末尾。
redisServer 结构维持着 Redis 服务器的状态, aof_buf 域则保存着所有等待写入到 AOF 文件的协议文本(RESP)。
第三阶段:文件写入和保存
每当服务器常规任务函数被执行或者事件处理器被执行时, aof.c/flushAppendOnlyFile 函数都会被调用, 这个函数执行以下两个工作:
- WRITE:根据条件,将 aof_buf 中的缓存写入到 AOF 文件。
- SAVE:根据条件,调用 fsync 或 fdatasync 函数(OS函数),将 AOF 文件保存到磁盘中。(下面三种AOF的保存模式)
AOF保存模式
Redis 目前支持三种 AOF 保存模式,它们分别是:
AOF_FSYNC_NO :不保存。 AOF_FSYNC_EVERYSEC :每一秒钟保存一次。(默认) AOF_FSYNC_ALWAYS :每执行一个命令保存一次。(不推荐)
(1)不保存
在这种模式下, 每次调用 flushAppendOnlyFile 函数, WRITE 都会被执行, 但 SAVE 会被略过。
在这种模式下, SAVE 只会在以下任意一种情况中被执行:
- Redis 被关闭
- AOF 功能被关闭
- 系统的写缓存被刷新(可能是缓存已经被写满,或者定期保存操作被执行)
这三种情况下的 SAVE 操作都会引起 Redis 主进程阻塞。
(2)每一秒钟保存一次
在这种模式中, SAVE 原则上每隔一秒钟就会执行一次, 因为 SAVE 操作是由后台子线程(fork)调用 的, 所以它不会引起服务器主进程阻塞。
(3)每执行一个命令保存一次
在这种模式下,每次执行完一个命令之后, WRITE 和 SAVE 都会被执行。
另外,因为 SAVE 是由 Redis 主进程执行的,所以在 SAVE 执行期间,主进程会被阻塞,不能接受命令请求。
AOF 保存模式对性能和安全性的影响
对于三种 AOF 保存模式, 它们对服务器主进程的阻塞情况如下:
AOF重写
AOF记录数据的变化过程,越来越大,需要重写“瘦身”。
Redis可以在AOF体积变得过大时,自动地在后台(Fork子进程)对AOF进行重写。重写后的新AOF文件包含了恢复当前数据集所需的最小命令集合。 所谓的“重写”其实是一个有歧义的词语, 实际上,AOF 重写并不需要对原有的AOF 文件进行任何写入和读取, 它针对的是数据库中键的当前值。
举例一: set s1 11 set s1 22 set s1 33 -------------------> 上面命令执行结果 s1 33 AOF重写优化之后这三句变为一句: set s1 33 举例二: lpush list1 1 2 3 lpush list1 4 5 6 -------------------> 上面命令执行结果 list1 1 2 3 4 5 6 AOF重写优化之后的语句: lpush list1 1 2 3 4 5 6
Redis不希望AOF 重写造成服务器无法处理请求, 所以Redis决定将 AOF重写程序放到(后台)子进程里执行。
这样处理的最大好处是:
- 子进程进行 AOF 重写期间,主进程可以继续处理命令请求。
- 子进程带有主进程的数据副本,使用子进程而不是线程,可以在避免锁的情况下,保证数据的安全性。
不过, 使用子进程也有一个问题需要解决:因为子进程在进行 AOF 重写期间, 主进程还需要继续处理命令, 而新的命令可能对现有的数据进行修改,这会让当前数据库的数据和重写后的AOF 文件中的数据不一致。
为了解决这个问题, Redis 增加了一个AOF 重写缓存, 这个缓存在 fork 出子进程之后开始启用, Redis 主进程在接到新的写命令之后, 除了会将这个写命令的协议内容追加到现有的 AOF 文件之外, 还会追加到这个缓存中。
重写过程分析(整个重写操作是绝对安全的):
Redis 在创建新 AOF 文件的过程中,会继续将命令追加到现有的 AOF文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到 新 AOF 文件,并开始对新 AOF 文件进行追加操作。
当子进程在执行 AOF 重写时, 主进程需要执行以下三个工作:
- 处理命令请求。
- 将写命令追加到现有的 AOF 文件中。
- 将写命令追加到 AOF 重写缓存中。
这样可以保证现有的 AOF 功能会继续执行,即使在 AOF 重写期间发生停机,也不会有任何数据丢失。 所有对数据库进行修改的命令都会被记录到 AOF 重写缓存中。当子进程完成 AOF 重写之后, 它会向父进程发送一个完成信号, 父进程在接到完成信号之后, 会调用 一个信号处理函数, 并完成以下工作:
- 将 AOF 重写缓存中的内容全部写入到新 AOF 文件中。 这个时候新的AOF文件所保存的数据库状态和服务器当前的数据库状态一致;
- 对新的 AOF 文件进行改名,原子的覆盖原有的AOF文件;完成新旧两个AOF文件的替换。
这个信号处理函数执行完毕之后, 主进程就可以继续像往常一样接受命令请求了。 在整个 AOF 后台重 写过程中, 只有最后的“主进程写入命令到AOF缓存”和“对新的AOF文件进行改名,覆盖原有的AOF文件。”这两个步骤(信号处理函数执行期间)会造成主进程阻塞, 在其他时候, AOF 后台重写都不会对 主进程造成阻塞, 这将 AOF 重写对性能造成的影响降到了最低。
以上就是 AOF 后台重写, 也即是 BGREWRITEAOF 命令(AOF重写)的工作原理。
AOF重写触发方式
(1)配置触发 在redis.conf中配置
# 表示当前aof文件大小超过上一次aof文件大小的百分之多少的时候会进行重写。如果之前没有重写过,以启动时aof文件大小为准 auto-aof-rewrite-percentage 100 # 限制允许重写最小aof文件大小,也就是文件大小小于64mb的时候,不需要进行优化 auto-aof-rewrite-min-size 64mb
服务器在AOF功能开启的情况下,会维持以下三个变量:
aof_current_size。# 记录当前AOF文件大小的变量 aof_rewrite_base_size。# 记录最后一次AOF重写之后,AOF文件大小的变量 aof_rewrite_perc。 # 增长百分比变量
每次当serverCron(服务器周期性操作函数)函数执行时,它会检查以下条件是否全部满足,如果全部满足的话,就触发自动的AOF重写操作:
- 没有BGSAVE命令(RDB持久化)/AOF持久化在执行;
- 没有BGREWRITEAOF在进行;
- 当前AOF文件大小要大于server.aof_rewrite_min_size(默认为1MB),或者在redis.conf配置了auto-aof-rewrite-min-size大小;
- 当前AOF文件大小和最后一次重写后的大小之间的比率等于或者等于指定的增长百分比(在配置文件设置了auto-aof-rewrite-percentage参数,不设置默认为100%)如果前面三个条件都满足,并且当前AOF文件大小比最后一次AOF重写时的大小要大于指定的百分比,那么触发自动AOF重写。
(2)执行bgrewriteaof命令
127.0.0.1:6379> bgrewriteaof Background append only file rewriting started
混合持久化(RDB,AOF)
RDB和AOF各有优缺点,Redis 4.0 开始支持 rdb 和 aof 的混合持久化。
如果把混合持久化打开,aof rewrite 的时候就直接把 rdb 的内容写到 aof 文件开头。
RDB的头 + AOF的身体 ---> appendonly.aof
开启混合持久化 aof-use-rdb-preamble yes
RDB是压缩的二进制的数据快照文件;容易丢数据;
AOF是记录命令执行过程的文本文件;记录的很清楚,但是很大;
混合持久化的appendonly.aof文件内容:
新的AOF文件是rdb文件的头和aof格式的内容,在加载时,首先会识别AOF文件是否以 REDIS字符串开头,如果是就按RDB格式加载,加载完RDB后继续按AOF格式加载剩余部分。
AOF文件的载入与数据还原
因为AOF文件里面包含了重建数据库状态所需的所有写命令,所以服务器只要读入并重新执行一遍AOF 文件里面保存的写命令,就可以还原服务器关闭之前的数据库状态 Redis读取AOF文件并还原数据库状态的详细步骤如下:
- 创建一个不带网络连接的伪客户端(fake client,服务启动产生的):因为Redis的命令只能在客户端上下文中执行, 而载入AOF文件时所使用的命令直接来源于AOF文件而不是网络连接,所以服务器使用了一个没有网络连接的伪客户端来执行AOF文件保存的写命令,伪客户端执行命令的效果和带网络连接的客户端执行命令的效果完全一样;
- 从AOF文件中分析并读取出一条写命令;
- 使用伪客户端执行被读出的写命令;
- 一直执行步骤2和步骤3,直到AOF文件中的所有写命令都被处理完毕为止 当完成以上步骤之后,AOF文件所保存的数据库状态就会被完整地还原出来,整个过程如下图所示:
RDB与AOF对比 和 选型
RDB和AOF的对标:
- RDB存某个时刻的数据快照,采用二进制压缩存储,AOF存操作命令,采用文本存储(混合);
- RDB性能高、AOF性能较低;
- RDB在配置触发状态会丢失最后一次快照以后更改的所有数据,AOF设置为每秒保存一次,则最多丢2秒的数据;
- Redis以主服务器模式运行,RDB不会保存过期键值对数据,Redis以从服务器模式运行,RDB会保存过期键值对,当主服务器向从服务器同步时,再清空过期键值对。AOF写入文件时,对过期的key会追加一条del命令,当执行AOF重写时,会忽略过期key和del命令。
数据完整性:AOF > RDB
性能:RDB > AOF
存储大小:RDB > AOF
Redis默认开启RDB,关闭AOF的;
RDB和AOF的选型:
应用场景:
内存数据库: rdb + aof 数据不容易丢;如果有原始数据源,每次启动时都初始化就可以了,不需要开持久化了(rdb aof)!
缓存服务器:rdb 一般性能比较高;(注意:数据非常大的时候,在fork的时候可能会阻塞主线程导致性能下降)
在数据还原时:
有rdb + aof,则还原aof,因为rdb会造成文件的丢失,aof相对数据要完整;
只有rdb,则还原rdb;
拉钩公司内部的配置:
追求高性能:都不开 redis宕机 从数据源恢复;
字典库:不驱逐,保证数据完整性;不开持久化,每次重启的时候从数据源恢复;
不开持久化,redis不能主从了!
用作DB数据完整性:不需要开启持久化(不能主从),重启从数据源恢复;
做缓存要较高性能:开rdb;如果Redis数据量存储过大,性能急剧下降,因为fork时间过长阻塞了主进程,这时把rdb关闭则只开启AOF;
来源:拉钩大数据高薪视频课程