为什么要持久化
Redis
是内存数据库,宕机后数据会消失,Redis
重启后快速恢复数据,要提供持久化机制。Redis
持久化不保证数据的完整性,有可能会丢数据。当下次Redis
重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。
RDB(默认)
RDB
方式是通过快照完成的。它保存的是某一时刻的数据并不关注过程。RDB
保存redis
某一时刻的数据的快照。
触发条件
- 执行
save
命令 : 会在主进程执行RDB,导致其他命令阻塞 - 执行
bgsave
命令 :会开启独立进程异步执行RDB,主进程可以持续处理请求 shutdown
指令:redis
通过shutdown
指令接受到关闭服务器的请求时,会触发一次SAVE
命令,阻塞所有的客户端- 执行主从复制操作 (第一次)。
- 符合自定义配置的快照规则;
执行流程
执行流程
Redis
父进程首先判断:当前是否在执行save或bgsave/bgrewriteaof的子进程,如果在执行则bgsave
命令直接返回。bgsave/bgrewriteaof
的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题;- 父进程执行
fork
操作创建子进程,这个过程中父进程是阻塞的,Redis
不能 执行来自客户端的任何命令; - 父进程
fork
后,bgsave
命令不再阻塞父进程,并可以响应其他命令; - 子进程创建
RDB
文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换; - 子进程发送信号给父进程表示完成,父进程更新统计信息。
写时复制机制(cow机制)
原因:在服务执行请求的同时,Redis
还需要进行快照**(文件IO操作)**,所以文件IO
操作不能进行多路复用。单线程同时在服务线上的请求还要进行文件 IO
操作,文件 IO
操作会严重拖垮服务器请求的性能。
原理:Redis
在持久化时fork 产生一个子进程,快照持久化完全交给子进程来处理,子进程刚刚产生时,它和父进程共享内存里面的代码段和数据段(上图共享内存)。这是 Linux 操作系统的机制,为了节约内存资源,所以尽可能让它们共享起来。在进程分离的一瞬间,内存的增长几乎没有明显变化。这时父进程持续服务客户端请求,然后对内存数据结构进行不间断的修改。这个时候就会使用操作系统的 COW
机制来进行数据段页面的分离(对数据页进行复制,子进程读取不变)。父进程修改操作的持续进行,越来越多的共享页面被分离出来,内存就会持续增长。但是也不会超过原有数据内存的 2 倍大小。
save与bgsave对比:
命令 | save | bgsave |
---|---|---|
IO类型 | 同步 | 异步 |
是否阻塞redis其它命令 | 是 | 否(在生成子进程执行调用fork函数时会有短暂阻塞) |
复杂度 | O(n) | O(n) |
优点 | 不会消耗额外内存 | 不阻塞客户端命令 |
缺点 | 阻塞客户端命令 | 需要fork子进程,消耗内存 |
在执行BGSAVE命令时,服务器BGSAVE ,SAVAE ,BGREWRITEAOF 异同。(了解)
1.BGSAVE
执行期间,客户端发送SAVE
命令全部拒绝。避免父子进程同时调用rdbSave
,防止竞争。
2.BGSAVE
执行期间,客户端发送BGSAVE
命令全部拒绝,同理。
3.BGSAVE
和BGWRITEAOF
不能同时执行。如果执行BGSAVE
期间,客户端发送BGWRITEAOF
指令,延迟到BGSAVE
指令完成之后执行。如果BGWRITEAOF
正在执行,客户端发送BGSAVE
会被拒绝。两个指令都是子进程完成,出于性能考虑不能同时执行,并且两个指令进行大量写入磁盘的操作。
RDB优缺点
优点:RDB文件紧凑,体积小,网络传输快,适合全量复制;恢复速度比AOF快很多。当然,与AOF相比,RDB最重要的优点之一是对性能的影响相对较小。
缺点:RDB文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此AOF持久化成为主流。此外,RDB文件需要满足特定格式,兼容性差(如老版本的Redis不兼容新版本的RDB文件)。
AOF
AOF
默认情况下是不开启的。开启AOF
持久化后Redis
将所有对数据库进行过写入的命令记录到 AOF
文件, 这样当Redis重启后只要按顺序回放这些命令就会恢复到原始状态了。AOF会记录过程,RDB只管结果。
通过修改配置文件来打开 AOF 功能:
# appendonly yes
可以配置Redis
多久才将数据 fsync
到磁盘一次。
有三个选项:
appendfsync always:每次有新命令追加到 AOF 文件时就执行一次 fsync ,非常慢,也非常安全。
appendfsync everysec:每秒 fsync 一次,足够快,并且在故障时只会丢失 1 秒钟的数据。
appendfsync no:从不 fsync ,将数据交给操作系统来处理。更快,也更不安全的选择。
推荐(并且也是默认)的措施为每秒fsync
一次, 这种fsync
策略可以兼顾速度和安全性。
AOF的执行流程
- 命令追加(append):Redis的所有写命令依然写入
AOF缓冲区(aof_buf)
。(主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。) - 文件写入(write)和文件同步(sync):根据不同的同步策略将
aof_buf
中的内容同步到硬盘;操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失;因此系统同时提供了fsync、fdatasync(appendfsync控制)等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。 - 文件重写(rewrite):定期重写
AOF
文件,达到压缩的目的。
具体执行流程
1.Redis
父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof
的子进程,如果存在则bgrewriteaof
命令直接返回,如果存在bgsave
命令则等bgsave
执行完成后再执行。前面曾介绍过,这个主要是基于性能方面的考虑。
2. 父进程执行fork
操作创建子进程,这个过程中父进程是阻塞的。
3.1:父进程fork
后,bgrewriteaof
命令返回并不再阻塞父进程,并可以响应其他命令。Redis
的所有写命令依然写入AOF缓冲区(aof_buf)。
3.2. 由于fork
操作使用写时复制技术,子进程只能共享fork
操作时的内存数据。由于父进程依然在响应命令,因此Redis
使用AOF
重写缓冲区(图中的aof_rewrite_buf)
保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof
执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。(aof_buf
定时写入aof文件,aof_rewirte_buf
写入新的aof文件)
4. 子进程根据内存快照,按照命令合并规则写入到新的AOF
文件。
5.1.子进程写完新的AOF
文件后,向父进程发信号。
5.2.父进程把AOF
重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。
5.3.使用新的AOF
文件替换老文件,完成AOF重写。
AOF重写
如下两个配置可以控制AOF自动重写频率
1 # auto‐aof‐rewrite‐min‐size 64mb //aof文件至少要达到64M才会自动重写,文件太小恢复速度本来就
很快,重写的意义不大
2 # auto‐aof‐rewrite‐percentage 100 //aof文件自上一次重写后文件大小增长了100%则再次触发重写
当然AOF还可以手动重写,进入redis客户端执行命令bgrewriteaof重写AOF。
注意:AOF
重写redis
会fork
出一个子进程去做(与bgsave命令类似),不会对redis
正常命令处理有太多影响。
Redis
可以在 AOF
体积变得过大时,自动地在后台(Fork子进程)对 AOF进行重写。重写后的新 AOF
文件包含了恢复当前数据集所需的最小命令集合。 AOF 重写并不需要对原有的 AOF 文件进行任何写入和读取, 它针对的是数据库中键的当前值。
Redis
不希望AOF
重写造成服务器无法处理请求, 所以Redis
决定将 AOF 重写程序放到(后台)子进程里执行, 这样处理的最大好处是:
- 子进程进行
AOF
重写期间,主进程可以继续处理命令请求。 - 子进程带有主进程的数据副本,使用子进程而不是线程,可以在避免锁的情况下,保证数据的安全性。
使用子进程也有一个问题需要解决: 因为子进程在进行 AOF 重写期间, 主进程还需要继续处理命令, 而新的命令可能对现有的数据进行修改, 这会让当前数据库的数据和重写后的 AOF 文件中的数据不一致。 为了解决这个问题,Redis
增加了一个 AOF
重写缓存, 这个缓存在 fork
出子进程之后开始启用,Redis
主进程在接到新的写命令之后, 除了会将这个写命令的协议内容追加到现有的AOF
文件之外,还会追加到这个缓存
中。
当子进程在执行 AOF
重写时, 主进程需要执行以下三个工作:
1.处理命令请求;
2.将写命令追加到现有的 AOF
文件中;
3.将写命令追加到AOF
重写缓存中。
当子进程完成AOF
重写之后, 它会向父进程发送一个完成信号, 父进程在接到完成信号之后, 会调用一个信号处理函数, 并完成以下工作:
1、 将 AOF 重写缓存中的内容全部写入到新 AOF
文件中;
2、 对新的 AOF 文件进行改名,覆盖原有的 AOF
文件;
Redis数据库里的+AOF重写过程中的命令------->新的AOF文件---->覆盖老的AOF文件
这个信号处理函数执行完毕之后, 主进程就可以继续像往常一样接受命令请求了。 在整个AOF
后台重写过程中, 只有最后的写入缓存和改名操作会造成主进程阻塞, 在其他时候,AOF
后台重写都不会对主进程造成阻塞, 这将 AOF 重写对性能造成的影响降到了最低。
AOF持久化优缺点
优点
-
AOF 的默认策略为每秒钟
fsync
一次,在这种配置下,Redis
仍然可以保持良好的性能,并且就算发生故障停机,也最多只会丢失一秒钟的数据,也可以根据实际情况设置fsync的策略。 -
AOF
文件是一个只进行追加操作的日志文件, 即使日志因为某些原因而包含了未写入完整的命令, redis-check-aof 工具也可以轻易地修复这种问题。 -
Redis
可以在AOF
文件体积变得过大时,自动地在后台对 AOF 进行重写: 重写后的新AOF
文件包含了恢复当前数据集所需的最小命令集合。 整个重写操作是绝对安全的,因为Redis
在创建新AOF
文件的过程中,会继续将命令追加到现有的 AOF 文件里面,即使重写过程中发生停机,现有的 AOF 文件也不会丢失。 而一旦新 AOF 文件创建完毕,Redis 就会从旧 AOF 文件切换到新 AOF 文件,并开始对新 AOF 文件进行追加操作。 -
AOF 文件有序地保存了对数据库执行的所有写入操作。
缺点
- 对于具有相同数据的的 Redis,AOF 文件通常会比 RDF 文件体积更大。
- 虽然 AOF 提供了多种同步的频率,默认情况下,每秒同步一次的频率也具有较高的性能。但在 Redis 的负载较高时,RDB 比 AOF 具好更好的性能保证。
RDB和AOF如何选择
在介绍持久化策略之前,首先要明白无论是RDB
还是AOF
,持久化的开启都是要付出性能方面代价的。对于RDB
持久化,一方面是bgsave
在进行fork
操作时Redis
主进程会阻塞,另一方面,子进程向硬盘写数据也会带来IO
压力;对于AOF
持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO
压力更大,甚至可能造成AOF
追加阻塞问题,此外,AOF文件的重写
与RDB的bgsave
类似,会有fork时的阻塞和子进程的IO压力问题。相对来说,由于AOF向硬盘中写数据的频率更高,因此对Redis主进程性能的影响会更大。
- 如果
Redis
中的数据完全丢弃也没有关系,那么无论是单机,还是主从架构,都可以不进行任何持久化。 - 在单机环境下(对于个人开发者,这种情况可能比较常见),如果可以接受十几分钟或更多的数据丢失,选择
RDB
对Redis的性能更加有利;如果只能接受秒级别的数据丢失,应该选择AOF
。 - 但在多数情况下,我们都会配置主从环境,slave的存在既可以实现数据的热备,也可以进行读写分离分担
Redis
读请求,以及在master宕掉后继续提供服务。
在主从环境情况下,一种可行的做法是:
- master:完全关闭持久化(包括RDB和AOF),这样可以让master的性能达到最好;
- slave:开启
AOF
(如果对数据安全要求不高,开启RDB关闭AOF也可以),并定时对持久化文件进行备份
(如备份到其他文件夹,并标记好备份的时间);然后关闭AOF
的自动重写,然后添加定时任务,在每天Redis
闲时(如凌晨12点)调用bgrewriteaof
。
为什么开启了主从复制,可以实现数据的热备份,还需要设置持久化呢?
因为在一些特殊情况下,主从复制仍然不足以保证数据的安全,例如:
master和slave
进程同时停止。考虑这样一种场景,如果master
和slave
在同一栋大楼或同一个机房,则一次停电事故就可能导致master和slave机器同时关机,如果没有持久化,则面临的是数据的完全丢失。- master误重启:
master
服务因为故障宕掉,由于没有持久化文件,那么master
重启后数据是空的,slave
同步数据也变成了空的。 - 异地灾备:如火灾地震,就需要进行异地灾备。例如对于单机的情形,可以定时将
RDB
文件或重写后的AOF
文件,拷贝到远程机器;对于主从的情形,可以定时在master上执行bgsave,然后将RDB文件拷贝到远程机器,或者在slave上执行bgrewriteaof重写AOF文件后,将AOF文件拷贝到远程机器上。一般来说,由于RDB
文件文件小、恢复快,因此灾难恢复常用RDB文件;异地备份的频率根据数据安全性的需要及其他条件来确定,但最好不要低于一天一次。
其他问题
fork阻塞:CPU的阻塞
众多因素限制了Redis
单机的内存不能过大,例如:当面对请求的暴增,需要从库扩容时,Redis内存过大会导致扩容时间太长;当主机宕机时,切换主机后需要挂载从库,Redis内存过大导致挂载速度过慢;以及持久化过程中的fork操作,下面详细说明。
首先说明一下fork操作:父进程通过fork操作可以创建子进程;子进程创建后,父子进程共享代码段,不共享进程的数据空间,但是子进程会获得父进程的数据空间的副本。在操作系统fork的实际实现中,基本都采用了写时复制技术,即在父/子进程试图修改数据空间之前,父子进程实际上共享数据空间;但是当父/子进程的任何一个试图修改数据空间时,操作系统会为修改的那一部分(内存的一页)制作一个副本。
虽然fork时,子进程不会复制父进程的数据空间,但是会复制内存页表(页表相当于内存的索引、目录);父进程的数据空间越大,内存页表越大,fork时复制耗时也会越多。
在Redis中,无论是RDB持久化的bgsave,还是AOF重写的bgrewriteaof,都需要fork出子进程来进行操作。如果Redis内存过大,会导致fork操作时复制内存页表耗时过多;而Redis主进程在进行fork时,是完全阻塞的,也就意味着无法响应客户端的请求,会造成请求延迟过大。
AOF追加阻塞:硬盘的阻塞
在AOF
中,如果AOF缓冲区
的文件同步策略为everysec
,则:在主线程中,命令写入aof_buf
后调用系统write
操作,write
完成后主线程返回;fsync同步文件操作由专门的文件同步线程每秒调用一次。
这种做法的问题在于,如果硬盘负载过高,那么fsync操作可能会超过1s;如果Redis主线程持续高速向aof_buf写入命令,硬盘的负载可能会越来越大,IO资源消耗更快;如果此时Redis进程异常退出,丢失的数据也会越来越多,可能远超过1s。
为此,Redis的处理策略是这样的:主线程每次进行AOF会对比上次fsync成功的时间;如果距上次不到2s,主线程直接返回;如果超过2s,则主线程阻塞直到fsync同步完成。因此,如果系统硬盘负载过大导致fsync速度太慢,会导致Redis主线程的阻塞;此外,使用everysec配置,AOF最多可能丢失2s的数据,而不是1s。
混合持久化
配置可以开启混合持久化(必须先开启aof):
# aof‐use‐rdb‐preamble yes
AOF
在重写时,不再是单纯将内存数据转换为RESP
命令写入AOF
文件,而是将重写这一刻之前的内存做RDB
快照处理,并且将RDB快照内容和增量的AOF
修改内存数据的命令存在一起,都写入新的AOF
文件,新的文件一开始不叫appendonly.aof,等到重写完新的AOF
文件才会进行改名,覆盖原有的AOF
文件,完成新旧两个AOF
文件的替换。
于是在 Redis
重启的时候,可以先加载RDB
的内容,然后再重放增量 AOF 日志就可以完全替代之前的
AOF` 全量文件重放,因此重启效率大幅得到提升。
混合持久化AOF文件结构如下
Redis数据备份策略
- 写crontab定时调度脚本,每小时都copy一份rdb或aof的备份到一个目录中去,仅仅保留最近48
小时的备份; - 每天都保留一份当日的数据备份到一个目录中去,可以保留最近1个月的备份;
- 每次copy备份的时候,都把太旧的备份给删了;
- 每天晚上将当前机器上的备份复制一份到其他机器上,以防机器损坏。