概述
Redis 是一个基于内存的数据库,它的数据是存放在内存中,内存有个问题就是关闭服务或者断电会丢失。
Redis 的数据也支持写到硬盘中,这个过程就叫做持久化。
Redis 提供了 2 种不同形式的持久化方式:
- RDB(Redis DataBase)
- AOF(Append Of File)
RDB
在指定的时间间隔内将内存中的数据集快照写入磁盘,也就是行话讲的 Snapshot 快照,它恢复时是键快 照文件直接读到内存里。
这种方式可以将某一时刻的所有数据都写入硬盘中,当然这也是redis 的默认开启持久化方式 ,保存的文件是以 .rdb
后缀名结尾的文件。
RDB 持久化触发策略
RDB 持久化提供了两种触发策略,一种是手动出发,另一种是手动触发。
手动触发策略
手动出发是通过 SAVE
命令或者 BGSAVE
命令将内存数据保存到磁盘文件中。示例:
1 | 127.0.0.1:6379> SAVE |
既然这两个命令都能持久化,那这两者有什么区别?
SAVE
命令会阻塞 Redis 服务器进程,直到 dump.rdb 文件创建完毕为止,在这个过程中,服务器不能处理任何的命令请求。
BGSAVE
命令是非阻塞式的,所谓非阻塞式,指的是在该命令执行的过程中,并不影响 Redis 服务器处理客户端的其他请求。
BGSAVE 为何会是非阻塞的?
为什么 BGSAVE 会是非阻塞的呢?
这是因为 Redis 服务器会 fork() 一个子进程来进行持久化操作(比如创建新的 dunp.rdb 文件),而父进程则继续处理客户端请求。当子进程处理完后会向父进程发送一个信号,通知它已经处理完毕。此时,父进程会用新的 dump.rdb 文件覆盖掉原来的旧文件。
fork() 是什么操作?
fork()是 unix 和 linux 这种操作系统的一个 api,fork()用于创建一个子进程,注意是子进程,不是子线程。fork()出来的进程共享其父类的内存数据。仅仅是共享 fork()出子进程的那一刻的内存数据,后期主进程修改数据对子进程不可见,同理,子进程修改的数据对主进程也不可见。
比如:A 进程 fork()了一个子进程 B,那么 A 进程就称之为主进程,这时候主进程子进程所指向的内存空间是同一个,所以他们的数据一致。但是 A 修改了内存上的一条数据,这时候 B 是看不到的,A 新增一条数据,删除一条数据,B 都是看不到的。
而且子进程 B 出问题了,对我主进程 A 完全没影响,我依然可以对外提供服务,但是主进程挂了,子进程也必须跟随一起挂。这一点有点像守护线程的概念。Redis 正是巧妙的运用了 fork()这个牛逼的 api 来完成 RDB 的持久化操作。
那 Redis 又是如何运用 fork()的呢?
当 BGSAVE 执行时,Redis 主进程会判断当前是否有 fork()出来的子进程,若有则忽略,若没有则会 fork()出一个子进程来执行 rdb 文件持久化的工作,子进程与 Redis 主进程共享同一份内存空间,所以子进程可以搞他的 rdb 文件持久化工作,主进程又能继续他的对外提供服务,二者互不影响。
主进程和子进程之后的修改内存数据对彼此不可见,但是明明指向的都是同一块内存空间,这是咋搞得?
肯定不可能是 fork()出来子进程后顺带复制了一份数据出来,如果是这样的话比如我有 4g 内存,那么其实最大有限空间是 2g,我要给 rdb 留出一半空间来,扯淡一样!那他咋做的?采取了 copyonwrite 技术。
copyonwrite 技术原理
主进程 fork()子进程之后,内核把主进程中所有的内存页的权限都设为 read-only,然后子进程的地址空间指向主进程。这也就是共享了主进程的内存,当其中某个进程写内存时(这里肯定是主进程写,因为子进程只负责 rdb 文件持久化工作,不参与客户端的请求),CPU 硬件检测到内存页是 read-only 的,于是触发页异常中断(page-fault),陷入内核的一个中断例程。
中断例程中,内核就会把触发的异常的页复制一份(这里仅仅复制异常页,也就是所修改的那个数据页,而不是内存中的全部数据),于是主子进程各自持有独立的一份。
这样就实现了写时拷贝,而不会全量复制。
示例
主进程收到了 set k 1 请求(之前 k 的值是 2),然后这同时又有子进程在 rdb 持久化,那么主进程就会把 k 这个 key 的数据页拷贝一份,并且主进程中 k 这个指针指向新拷贝出来的数据页地址上,然后进行更改值为 1 的操作,这个主进程 k 元素地址引用的新拷贝出来的地址,而子进程引用的内存数据 k 还是修改之前的。
总结
copyonwrite 技术将 fork()出来的子进程来共享主进程(这里的主进程指的是 Redis 进程)的物理空间,当主进程有内存写入操作时,因为主进程的内存空间已经被设置成了 read-only 无法写入,会发生中断,这时主进程会将触发异常的内存页复制一份,将写入部分的指针指向新拷贝出来的数据页地址上,其余的页还是共享主进程的。
因为 SAVE
命令无需创建子进程,所以执行速度要略快于 BGSAVE
命令,但是 SAVE
命令是阻塞式的,因此其可用性欠佳,如果在数据量较少的情况下,基本上体会不到两个命令的差别,不过仍然建议使用 BGSAVE
命令。
自动触发策略
自动触发策略,是指 Redis 在指定的时间内,数据发生了多少次变化时,会自动执行 BGSAVE
命令。自动触发的条件包含在了 Redis 的配置文件中,如下图:
语法格式:
1 | save 时间间隔(秒) 写操作次数 |
含义是在时间间隔时间范围内,如果 Redis 数据至少发生了指定写操作次数次变化,那么就自动执行 BGSAVE
命令。上图配置策略说明如下:
- save 3600 1 表示在 3600 秒内,至少更新了 1 条数据,Redis 自动触发 BGSAVE 命令,将数据保存到硬盘。
- save 300 100 表示在 300 秒内,至少更新了 100 条数据,Redis 自动触 BGSAVE 命令,将数据保存到硬盘。
- save 60 10000 表示 60 秒内,至少更新了 10000 条数据,Redis 自动触发 BGSAVE 命令,将数据保存到硬盘。
只要上述三个条件任意满足一个,服务器就会自动执行 BGSAVE
命令。当然也可以根据实际情况自己调整触发策略。
RDB 的相关配置
指定备份文件的名称
在 redis.conf 文件中,可以修改 rdb 备份文件的名称,配置项是 dbfilename
,默认为 dump.rdb,可以将其修改:
指定备份文件存放的目录
在 redis.conf 中,rdb 文件的保存的目录是可以修改的,默认为 Redis 启动命令所在的目录,如下:
RDB 的优劣势
优势:
- 适合大规模数据恢复
- 对数据完整性和一致性要求不高更适合使用
- 节省磁盘空间
- 恢复速度快
劣势:
- 虽然 Redis 在 fork 的时候使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能
- 在备份周期在一定间隔时间做一次备份,所以如果 Redis 意外 down 的话,就会丢失最后一次快照后所有修改
RDB 数据持久化适合于大规模的数据恢复,并且还原速度快,如果对数据的完整性不是特别敏感(可能存在最后一次丢失的情况),那么 RDB 持久化方式非常合适。
AOF
以日志的形式来记录每个写操作(增量保存),将 redis 执行过的所有写指令记录下来(读操作不记录),只允追加文件但不可改写文件,redis 启动之初会读取该文件重新构造数据。
换言之,redis 重启的话就根据日志文件的内容将写指令从前到后执行一次以完成数据的恢复工作。
这种方式可以将所有客户端执行的写命令记录到日志文件中,AOF 持久化会将被执行的写命令写到 AOF 的文件末尾,以此来记录数据发生的变化。因此只要 redis 从头到尾执行一次 AOF 文件所包含的所有写命令,就可以恢复 AOF 文件的记录的数据集。
redis 默认处于未关闭状态,可以通过修改 redis 配置文件来开启 AOF:
1 | vim /etc/redis/redis.conf |
持久化机制
每当有一个修改数据库的命令被执行时,服务器会将该命令写入到内存缓冲区中;内存缓冲区会根据 AOF 的持久化策略将操作同步到磁盘的 AOF 文件中。
如果 AOF 文件大小超过重写策略或者执行命令手动重写时,会对 AOF 文件进行重写(rewrite),压缩 AOF 文件容量。
redis 服务在重启时,会重新夹在 AOF 文件中的写操作以达到数据回复的目的。
AOF 的策略配置
这里的策略分为 2 重,一种是持久化策略,另一种重写策略。
持久化策略
redis 服务器在遇到宕机前,缓存内的数据未能写入到磁盘中,那么数据仍然有丢失的风险。服务器宕机时,丢失命令的数量取决月命令被写入磁盘的时间:越早地把命令写入到磁盘中,发生意外时丢失的数据就会越少,否则就会越多。
在 redis 中,提供 AOF 的持久化策略选项是 appendfsync
,该选项提供了 3 个选项:always、everysec、no
配置项 | 写回时机 | 优点 | 缺点 |
---|---|---|---|
always | 同步写回 | 可靠性高 | 每个写命令都要落盘,性能影响较大 |
everysec | 每秒写回 | 性能适中 | 宕机时丢失1秒内的数据 |
no | 操作系统控制的写回 | 性能好 | 宕机时丢失数据较多 |
always(默认)
始终同步,每次 redis 的写入都会立刻记入日志;服务器每写入一个命令,将缓冲区里面的命令写入到硬盘。这种模式下,服务器出现故障,也不会丢失任何已经成功执行的命令数据,但是其执行速度较慢;
everysec
每秒同步,每秒记录日志一次,如果宕机,本秒数据可能丢失;更新的命令会放在内存 AOF 缓冲区内,每秒将缓冲区的命令追加到 AOF 文件。
这种模式下,服务器出现故障,最多只丢失一秒钟内的执行的命令数据。
no
redis 不主动进行同步,由操作系统决定何时将缓冲区里面的命令写入到硬盘。这种模式下,服务器遭遇意外停机时,丢失命令的数量是不确定的,所以这种策略,不确定性较大,不安全。
到这里,我们就可以根据系统对高性能和高可靠的要求,来选择使用哪种策略了。总结一下就是:想要获得高性能,就选择 no 策略;如果想要得到高可靠性保证,就选择 always 策略;如果允许数据有一点丢失,又希望性能别受太大影响的话,那么就选择 everysec 策略。
重写策略
Redis 在长期运行的过程中,aof 文件会越变越长。如果机器宕机重启,“重演”整个 aof 文件会非常耗时,导致长时间 Redis 无法对外提供服务。因此就需要对 aof 文件做一下“瘦身”运动。
为了让 aof 文件的大小控制在合理的范围内,Redis 提供了 AOF 重写机制策略。
重写流程
- 手动执行 bgrewriteaof 命令触发重写,判断是否当前有 bgfsave 或 bgrewriteaof 在运行,如果 有,则等待该命令结束后再继续执行
- 主进程 fork 出子进程执行重写操作,保证主进程不会阻塞
- 子进程遍历 redis 内存中的数据到临时文件,客户端的写请求同时写入 aof_buf 缓冲区和 aof_rewrite_buf 重写缓冲区保证原 AOF 文件完整性以及新 AOF 文件生成期间的新的数据修改动作不会丢失
- 子进程写完新的 AOF 文件后,向主进程发送信号,父进程更新统计信息
- 主进程把 aof_rewrite_buf 中的数据写入到新的 AOF 文件
- 使用新的 AOF 文件覆盖旧的 AOF 文件,完成 AOF 重写
流程图如下:
重写策略又分为手动重写和自动重写两种。
手动重写
手动重写指的是手动执行 BGREWRITEAOF
命令,如下所示:
1 | 127.0.0.1:6379> BGREWRITEAOF |
通过上述操作后,服务器会生成一个新的 aof 文件,该文件具有以下特点:
- 新的 aof 文件记录的数据库数据和原 aof 文件记录的数据库数据完全一致;
- 新的 aof 文件会使用尽可能少的命令来记录数据库数据,因此新的 aof 文件的体积会小很多;
- AOF 重写期间,服务器不会被阻塞,它可以正常处理客户端发送的命令。
自动重写
设置自动重写的话需要修改配置文件,让服务器自动执行 BGREWRITEAOF
命令。
1 | #默认配置项 |
该配置项表示:触发重写所需要的 aof 文件体积百分比,只有当 aof 文件的增量大于 100% 时才进行重写,也就是大一倍。比如,第一次重写时文件大小为 64M,那么第二次触发重写的体积为 128M,第三次重写为 256M,以此类推。如果将百分比值设置为 0 就表示关闭 AOF 自动重写功能。
AOF 的优劣势
优势:
- 备份机制更稳健,丢失数据概率更低
- 可读的日志文本,通过操作 AOF 文件,可以处理误操作
劣势:
- 比 RDB 占用更多的磁盘空间
- 恢复备份速度慢
- 每次读写都同步的花,有一定的性能压力
AOF 文件是一个只进行追加的日志文件,Redis 可以在 AOF 文件体积变得过大时,自动地在后台对 AOF 文件进行重写。
AOF 文件有序地保存了对数据库执行的所有写入操作,这些写入操作以 redis 协议的格式保存,因此 AOF 文件的内容非常容易被人读懂,对文件进行分析也很轻松。
对于相同的数据集来说,AOF 文件的体积通常要大于 RDB 文件的体积,根据所使用的 fsync 策略,AOF 的速度可能会慢于 RDB。
RDB 和 AOF 选哪一个
官方推荐 2 个都启用。
如果对数据不敏感,可以单独用 RDB。如果只是做纯内存缓存,可以都不用。