什么是 Redis 持久化
Redis 是什么这里就不多介绍了,作为一款 NoSQL 数据库来说,Redis 的相关的所有数据操作都是基于内存的,如下图:
基于内存有一个最大的问题就是,由于各种原因造成服务器的重启,那么内存中的数据就会消失。为了要解决这个问题,Redis 提供了对持久化的支持,我们可以通过 Redis 提供的2种不同的方式(RDB和AOF)将内存中的数据保存到硬盘中,以此实现数据的持久化,如下图:
如何持久化
Redis 为我们提供了2种持久化的方式,下面我来分别介绍下。
一、RDB
RDB 属于快照存储,就是在某一时刻将 Redis 当下的内存数据保存到硬盘的文件当中,默认该文件名为 dump.rdb。当 Redis 服务器重启时,会重新将 dump.rdb 文件中的数据加载到内存当中。
触发机制
有三种方式触发 Redis 的 RDB 持久化操作,如下:
-
save 指令
通过客户端向服务器发送 save 指令,通知服务器执行 RDB 持久化,当服务器接收到指令后,服务器会阻塞 save 指令之后的所有客户端请求,直到数据同步完成,如下图:
如上图,Redis 服务器接收到客户端 alpha 的 save 指令,则客户端 beta、gamma 的指令都将被阻塞。很明显,如果快照的数据量比较大,那 Redis 服务器将会有很长时间无法正常接收、响应客户端的新指令,所以,最好不要在生产环境中执行该操作。
-
bgsave 指令
与 save 指令不同,bgsave 指令对应的是一个异步的操作,当服务器接收到 bgsave 指令后,会启动一个子进程来持久化当前的内存快照,当内存快照保存到 rdb 文件之后,子进程才会退出。在这段时间内,主进程依然可以正常接收其他客户端的请求,如下图:
由于是异步操作,Redis 主进程不会被 RDB 影响,依然可以接收客户端的写指令,但是此时的写指令见不会同步到 Redis 主进程的主内存中,而是会写到一个临时的内存区域作为一个副本,等到 Redis 主进程接收到 RDB 子进程已完成的消息后,再将内存副本中的数据同步到主内存中。 -
服务器配置自动触发
以上介绍的 save、bgsave 指令都是需要通过客户端向 Redis 服务器发送相应的指令来实现 Redis持久化的。我们还可以通过设置 Redis 服务器的配置(redis.conf),来指定自动触发 RDB 持久化的条件,比如“多少秒内至少达到多少次写操作”就自动开启 RDB 数据同步,如下面 redis.conf 中的代码片段:
# 900s内至少达到一条写命令
save 900 1
# 300s内至少达至10条写命令
save 300 10
# 60s内至少达到10000条写命令
save 60 10000
RDB 的过程
- 执行 bgsave 指令的时候,Redis 主进程会检查是否有正在执行RDB/AOF持久化任务的子进程,如果有的话,直接返回。
- Redis 主进程会 fork 一个子进程来执行执行 RDB 操作,fork 操作会对主进程造成阻塞(影响Redis的读写),fork 操作完成后会通知主进程,使主进程不再阻塞。
- RDB 子进程会根据 Redis 主进程的内存生成临时的快照文件,RDB 完成后会使用临时快照文件替换掉原来的RDB文件。
二、AOF
AOF 全称为 Append Only File,与 RDB 存储内存内的数据快照不同,AOF 是记录客户端对服务器发送的写操作指令,类似于数据库的审计功能,所有的写操作指令会被追加保存到一个以 aof 为后缀的文件中。当 Redis 服务器重启时,会加载并运行 aof 文件的写操作指令,以达到恢复数据的目的,如下图:
触发机制
默认情况下,Redis 不会开启 AOF,我们可以通过修改配置文件(redis.conf)来开启AOF并对其进行更加详细的设置,如下:
# 开启aof机制
appendonly yes
# aof文件名
appendfilename "appendonly.aof"
# 写入策略,从“always、everysec、no”中选择一个合适的策略
appendfsync always
# 默认不重写aof文件
no-appendfsync-on-rewrite no
# 保存目录
dir ~/redis/
Redis 提供三种方式向 aof 文件写入信息(写操作指令),如下:
- always
客户端的每一次写操作都会立即保存到 aof 文件中,该策略很安全,但是每个写请求都有 IO 操作的消耗,所以会比较影响速度。 - everysec
每秒写一次 aof 文件,也就是将1秒内发生的所有写操作指令定时写入 aof 文件。 - no
Redis 服务器不负责写入 aof,该任务(什么时候写入 aof 文件)交给操作系统来处理。(注:更快,但也是最不安全的选择,不推荐使用)
AOF 的过程
- 所有的写指令都会追加到 AOF 缓冲区中。
- 使用既定的策略将 AOF 缓冲区中的指令写到 AOF 文件中。
- 随着 AOF 文件的越来越大,会对 AOF 文件进行重写。
- 当服务器重启的时候,会加载AOF文件并执行AOF文件中的命令用于恢复数据。
AOF 重写
AOF 作为类似于数据库审计功能的存在,会记录客户端所有的操作,例如:对一个 key 多次执行 incr 命令,这时候,aof 也会保存每一次指定到 aof 文件中,这样就会造成 aof 文件非常大,如下:
incr num 1
incr num 2
incr num 3
...
incr num 100000
aof 文件过大,必然会影响加载速度,也就是说当 Redis 服务器重启的时候,会因为 aof 文件很大而造成数据恢复非常慢。为了解决这个问题,Redis 提供了 AOF重写。
所谓「AOF重写」,就是将多条指令合并为一条指令,最终生成一个能够恢复当前数据的最少指令集
比如上面的例子中那么多条指令,可以重写为:
incr num 100000
这么看来定期的进行 aof 文件重写,在持久化这个大前提下,还是很有必要的。但是,默认情况下,Redis 是没有开启重写的。那么,我们怎样才能触发重写呢?!
- 通过修改配置文件(redis.conf)
我们可以通过修改 Redis 服务器的 redis.conf 配置文件中 no-appendfsync-on-rewrite 选项的值来指定是否开启 aof 文件重写(如下),这里需要注意的一点是这种方式会在每次 fsync 时都重写,比较影响服务器的性能,因此默认值为 no,不推荐使用。
# 默认不重写aof文件
no-appendfsync-on-rewrite yes
auto-aof-rewrite-min-size 64MB
auto-aof-rewrite-min-percenrage 100
- 发送 bgrewriteaof 指令
由客户端向服务器发送 bgrewriteaof 指令,可以让服务器进行 AOF 重写。
上边说了一下如何开启 AOF 重写,下面再说一下整个 AOF 重写的过程:
- 执行 bgrewriteaof 指令的时候,如果当前存在正在执行 AOF 重写的子进程,那么直接返回;如果有正在执行 bgsave 的进程,那么需要等待 bgsave 执行完毕后再执行 AOF 重写。
- Redis 主进程会 fork 一个子进程执行 AOF 重写。
- AOF 重写过程中,不影响 Redis 原有的 AOF 过程,包括写消息到 AOF 缓存以及同步 AOF 缓存中的数据到硬盘。
- AOF 重写过程中,主进程收到的写操作指令的时候,不但会把该指令写到 AOF 缓冲区内,同时还会将该命令写到 AOF 重写缓冲区内。这里需要注意,AOF 缓冲区 和 AOF 重写缓冲区 是两块缓冲区。
- 由于 AOF 重写过程中原 AOF 文件还在陆续写入数据,所以 AOF 重写子进程只会拿到 fork 子进程时的 AOF 文件进行重写。
- AOF 重写子进程将原 AOF 文件中的数据写到一个临时的 AOF 文件中。
- 子进程完成 AOF 重写后会通知主进程,主进程得到通知后会将 AOF 重写缓冲区中的数据写入 AOF 缓冲区,然后用新的 AOF 文件替换旧的 AOF 文件。
在整个 AOF 重写过程当中,有三个地方会使 Redis 主进程阻塞:
- 主进程 fork 子进程的时候
- 主进程把 AOF 重写缓冲区中的数据写到 AOF 缓冲区的时候
- 使用新的 AOF 文件替换掉旧的 AOF 文件的时候