一、前言
由于疫情的原因,学校还没有开学,这也就让我有了很多的时间。趁着时间比较多,我终于可以开始学习那些之前一直想学的技术了。最近这几天开始学习Redis
,买了本《Redis实战》
,看到了第四章,前三章都是讲一些Redis
的基本使用以及命令,第四章才开始涉及到原理相关的内容。《Redis实战》
的第四章涉及到了Redis
的持久化、主从复制以及事务等内容,我个人认为这些应该属于Redis
中比较重要的部分,也是面试的常考内容。这篇博客就来记录一下Redis
的持久化机制。
二、正文
2.1 为什么需要持久化
学习过Redis
的应该都知道,Redis
与MySQL
等关系型数据库不同,它的数据不是存储在硬盘中,而是存放在内存,所以Redis
的速度非常快。而这也就会造成一个问题:电脑如果宕机,或者由于某些原因需要重启,此时内存中的数据就会丢失。Redis
既然把数据存放在内存,自然也就无法避免这个问题。所以,为了在电脑重启后,能够恢复原来的数据,Redis
就需要提供持久化的机制,将Redis
数据库存储在内存中的数据,在磁盘中进行备份,也就是持久化。而当Redis
重启后,去磁盘中将持久化的数据重新读取到内存,便能避免数据的丢失。
2.2 Redis的持久化方式
Redis
提供了两种持久化的方式,分别是:
- 快照持久化;
- AOF持久化;
下面我就来详细地介绍这两种持久化的方式。
2.3 快照持久化(RDB)
快照持久化也就做RDB
持久化。快照持久化的实现方式简单来说就是:Redis将当前内存中存储的数据写入到一个文件中,将这个文件作为Redis当前的一个快照,保存在磁盘中。当Redis重启时,将这个快照文件中存储的内容加载进内存,即可恢复Redis之前的状态。默认情况下,Redis
将快照保存在一个叫做dump.rdb
的文件中,我们也可以在配置文件中,通过dbfilename
选项来设置快照文件的名称;默认情况下快照文件就保存在Redis
的安装目录下,我们可以在配置文件中,通过dir
选项来配置快照文件的存储路径(其实也是AOF的路径)。
2.4 执行快照持久化的方式
执行快照持久化有两种方式:
2.4.1 使用配置文件
第一种方式就是在Redis
的配置文件中(windows
下这个文件叫redis.windows-service.conf
)加上save
配置项,比如像下面这样:
save 60 100:在配置文件中加上这一条的意思是,Redis会每60秒检查一次,若在这60秒中,Redis数据库执行了100次以上的写操作,那Redis就会生成一个快照文件,替换原来的快照文件;若不满足这个条件,则不生成快照,继续等待60秒;
我们可以根据自己的需求,调整每次等待的时间,以及对写操作次数的要求。而且,我们可以在配置文件中,添加多个save
选项,比如一个save 60 100
,一个save 5 10
,则Redis
每5
秒以及每100
秒都会判断一次。需要注意的是,我们不应该让生成快照太过频繁,因为这是一个比较消耗资源的工作,会降低Redis
的响应速度。
2.4.2 使用指令
执行快照持久化的第二个方法就是使用Redis
指令,Redis
提供了两个指令来请求服务器进行快照持久化,这两个指令分别是SAVE和BGSAVE。
(a)BGSAVE指令
BGSAVE
的执行流程如下:
-
Redis
调用系统的fork()
,创建出一个子进程; - 子进程将当前
Redis
中的数据,写入到一个临时文件中;同时父进程不受影响,继续执行客户端的请求; - 子进程将所有的数据写入到了临时文件后,于是使用这个文件替换原来的快照文件(默认是
dump.rdb
);
值得一提的是,通过配置文件执行快照持久化的方式,实际上就是Redis
在判断满足条件时,调用BGSAVE
指令来实现的。
(b)SAVE指令
SAVE
指令生成快照的方式与BGSAVE
不同,Redis
执行SAVE
指令时,不会创建一个子进程,异步的生成快照文件,而是直接使用Redis
当前进程。执行SAVE
指令在创建快照的过程中,Redis
服务器会阻塞所有的Redis
客户端,直到快照生成完毕,并更新到磁盘之后,才会继续执行客户端发来的增删改查的指令。
当然,这个指令一般很少使用,因为会阻塞客户端,造成停顿。但是实际上,这个指令的执行效率一般比BGSAVE
更高,因为不需要创建子进程,而且在这个过程中,其他操作被阻塞,Redis
服务器一心一意地生成快照。在《Redis实战》
中,作者提到了使用SAVE
指令的一个案例:
当前服务器的Redis数据库中保存了大量数据,使用BGSAVE指令生成快照会非常的耗时 ,而且由于所剩内存不多,甚至无法创建子进程,于是作者编写了一个shell脚本,这个脚本的内容就是让服务器每天凌晨3点,执行SAVE命令,生成快照,这样就不需要创建子进程,而且由于是凌晨三点,用户较少,SAVE的阻塞机制也不会有太大的影响。
2.5 快照持久化的优缺点
(1)优点:
快照的
rdb
文件是一个经过压缩的紧凑文件,它保存了Redis
在某个时间点上的数据集,这个文件非常适合用来备份。我们可以存储Redis
服务器在不同时间点上的rbd
文件,比如说一小时存储一次,一个月存储一次,这样就可以在遇到问题或有特殊需求时,将Redis
恢复到某一个时间点;RDB非常适用于灾难恢复(disaster recovery):它只有一个文件,并且内容都非常紧凑,可以(在加密后)将它传送到别的服务器上;
RDB 可以最大化 Redis 的性能:父进程在保存
RDB
文件时唯一要做的就是fork
出一个子进程,然后这个子进程就会处理接下来的所有保存工作,父进程无须执行任何磁盘I/O
操作,所以不会影响父进程处理客户端的请求;RDB
在恢复大数据集时的速度比AOF
的恢复速度要快。
(2)缺点:
- 当我们的服务器发生异常,导致停机时,那我们将会丢失最后一次创建快照后,所作的所有写操作,因为这些操作发生在内存中,还没来得及同步到磁盘。比如我们在配置文件中配置每
5
分钟生成一次快照,那当系统发生故障导致宕机时,我们将会丢失好几分钟内的操作; - 每次执行
BGSAVE
创建快照,都需要先创建出一个子进程,再由子进程执行后续操作,当内存中数据较大时,创建一个子进程将会非常耗时,造成服务器等待数毫秒,甚至达到一秒,影响用户体验。而且从这一点也可以说明,我们不能通过提高生成快照的频率,来解决第一个缺点;
2.6 AOF持久化
AOF
全称为只追加文件(append-only file),它的实现方式简单来说就是:AOF持久化机制,会将Redis执行的所有写指令,追加到到AOF的末尾,当服务器发生宕机,或者由于某些原因需要重启时,在重启后,读取AOF文件,重新执行其中记录的写操作,以此达到恢复数据的目的。
AOF
持久化机制默认是关闭的,我们可以在配置文件中,配置appendonly yes来开启。同时我们也可以通过配置appendfsync,控制写操作追加到AOF
中的频率,它有如下三种选项:
-
always:
Redis
每次执行写指令,都会立即将这个写指令同步到AOF
中;使用这个选项时,Redis
发生异常,则最多只会丢失一次写操作(也就是在同步的过程中宕机,没同步完成),但是这也会导致Redis
的响应速度变慢,因为此选项会造成频繁的IO
操作; -
everysec(默认):每秒同步一次,使用此选项,速度足够快,而且发生宕机时也只会丢失
1s
内的写操作; - no:让操作系统来决定什么时候进行同步,这个选项速度更快,但是不安全,宕机时丢失数据的多少是不确定的;
推荐(也是默认),使用第二个选项everysec
,每秒进行一次同步,因为这个选项兼顾了速度与安全性,而第一个选项太慢,第三个选项无法保证安全性。
2.7 AOF的重写机制
AOF
持久化的执行机制就是,不断地将写指令追加到AOF
的末尾,这样就会导致AOF
越来越大。为了解决AOF
越来越大的问题,Redis
实现了一种优化机制——AOF重写。当Redis
检查到AOF
已经很大时,就会触发重写机制,优化其中的内容,将它优化为能够得到相同结果的最小的指令集合。
比如说,我们在Redis
中使用SET
指令创建了一个String
类型的数据,最后使用DEL
指令将它删除了。如果开启了AOF
持久化,那么则AOF
中,将会记录这条SET
和DEL
指令。但是,在执行重写的过程中,这个String最后被删除了,那么Redis
就不会将这两条指令加入新的AOF
中,因为已经被删除的数据,不需要恢复。再比如说,我们使用Redis
维护了一个计数器cnt
,我们使用了100
次INCR
指令,让cnt
自增到了100
,而Redis
重写AOF
时,可以将这100
条incr
修改为一条SET
指令,直接将cnt
设置为100
,而不是保留100
条INCR
。
经过了重写后,AOF
的大小将会大大减小,而且也去除了不必要的操作,优化了恢复数据的指令集。而AOF
重写的过程与生成一个快照文件类似,如下:
-
Redis
执行fock()
,创建一个子进程用来执行后续操作,而父进程继续处理发送到Redis
的执行请求; - 子进程重写旧
AOF
文件,将重写后的内容写入到一个临时文件; - 如果这个过程中,有新的写指令到达,那么
Redis
会将这些写指令依旧追加到旧的AOF
中,同时也会将这些指令加入到内存的一个缓冲区中。这样做的目的是,如果服务器发生异常,AOF
重写失败,这些指令依然能够保存在旧AOF
,不会丢失; - 当子进程完成重写工作时,它给父进程发送一个信号,父进程在接收到信号之后,将内存缓存中的所有写指令追加到新
AOF
文件的末尾; - 使用新
AOF
替换旧的AOF
,这之后执行的所有写指令都将追加到新的AOF
中;
2.8 AOF的错误处理
AOF
文件是有可能发生错误的,比如上面提过,如果当前Redis
正在向AOF
中追加一个写指令,但是此时服务器宕机,那么这个存入AOF
中的这个写指令就是不完整的,也就是AOF
出现了错误。如果停机造成了 AOF
文件出错(corrupt), 那么 Redis
在重启时会拒绝载入这个 AOF
文件, 从而确保数据的一致性不会被破坏。
那么,当AOF
发生了错误,应该如何处理呢?我们可以使用如下命令:
redis-check-aof --fix:redis-check-aof用来检测AOF是否存在错误,如果指定了--fix参数,那么Redis在检测到AOF的错误后,会对AOF进行修复。
redis-check-aof
修复AOF
的方式非常简单:扫描AOF文件,寻找其中不正确或不完整的指令,当发现第一个出错的指令后,就将这个指令以及这之后的所有指令删除。为什么需要删除出错指令之后的所有指令呢?因为当一条指令出错,很有可能影响到后续的操作,导致后续操作的都是脏数据,而Redis
无法检测哪些指令是受到影响的,所以为了保险起见,就将后续指令全部删除。不过不用担心,因为在大多数情况下,出错的都是AOF
最末尾的指令。
2.9 AOF的优缺点
(1)优点:
- 使用
AOF
持久化会让Redis
变得非常耐久,意思就是说,当发生异常导致需要重启服务器时,只会丢失很少的一部分数据,因为AOF
持久化默认1s
同步一次,也就是说,Redis
最多只会丢失1s
中所做的修改; -
Redis
可以在AOF
文件体积变得过大时,自动地在后台对AOF
进行重写: 重写后的新AOF
文件包含了恢复当前数据集所需的最小命令集合。 -
AOF
文件有序地保存了对数据库执行的所有写入操作, 这些写入操作以Redis
协议的格式保存, 因此AOF
文件的内容非常容易被人读懂, 对文件进行分析(parse
)也很轻松。 -
AOF
文件是一个只进行追加操作的日志文件(append only log
), 因此对AOF
文件的写入不需要进行seek
, 即使日志因为某些原因而包含了未写入完整的命令(比如写入时磁盘已满,写入中途停机,等等),redis-check-aof
工具也可以轻易地修复这种问题。
(2)缺点:
- 对于相同的数据集来说,
AOF
文件的体积通常要大于快照RDB
文件的体积大,因为RDB
只存储数据,而AOF
中的写指令,既包含指令,也包含数据; - 如果
AOF
体积太大,那么恢复数据将要花费较长的时间,因为需要重做指令;
2.10 选择快照还是AOF?
说到这里,可能就有人要问了,在实际生产中,我们应该使用快照持久化还是AOF
持久化呢?
1、如果希望自己的数据库有很高的安全性,则应该两者同时使用,AOF
用作精确记录,而快照用作数据备份,前面也说过,快照非常适合用来做数据备份,因为它只存储数据库中的数据,并且经过了压缩,而且它恢复Redis
数据的速度一般要快于AOF
;
2、如果可以容忍一段时间内的数据丢失,则可以考虑只使用快照持久化,减小服务器的开销;
值得一提的是,如果我们同时开启了快照持久化和AOF
持久化,Redis
在重启后,会优先选择AOF
来恢复数据,因为一般情况下,AOF
能够更加完整地恢复数据。
三、总结
快照持久化和AOF
持久化各有优劣,在实际生产环境中,我们一般是两者配合使用,快照持久化消耗较低,而且适合用于备份,但是会丢失一段时间的数据;AOF
持久化更加地耐久,可靠性更高,但是开销可能相对较高。以上就对Redis
的持久化机制做了一个比较详细的介绍,相信看完只后,对Redis
会有一个更加深入的理解。
四、参考
- 《Redis实战》
- Redis官方文档——持久化