Redis 的持久化与过期键
简介
Redis 是使用非常广泛的 Key-Value 内存数据库。因为数据都存放在内存中,所以存取速度非常快。不过,很多情况下我们需要将 Redis 中的数据保存到硬盘中以便做备份。Redis 提供了两种数据持久化方式,分别是 RDB 和 AOP,本文分析这两种方式的使用以及过期键对持久化的影响。
RDB
RDB 指的是将 Redis 数据库在某个时间点的快照保存到磁盘,所生成的 RDB 文件是一个经过压缩的二进制文件,通过这个文件可以还原出 Redis 的数据状态。
创建快照的方式有以下几种:
- 客户端向 Redis 发送
BGSAVE
命令,Redis 会调用 fork 创建一个子进程,然后子进程负责将快照写入硬盘,而父进程继续处理命令请求。 - 客户端向 Redis 发送
SAVE
命令,此时 Redis 将开始创建快照,并且在完成之前不再响应其它命令。 - 用户设置 save 配置选项,比如
save 60 10000
,那么从 Redis 最近一次创建快照算起,当 “60 秒内有 10000 次写入” 这个条件被满足时, Redis 就会自动触发BGSAVE
命令。如果用户设置了多个 save 配置选项,那么当任意一个 save 配置满足时,Redis 就会触发一次BGSAVE
命令。save 配置的格式如下所示:
save 60 10000
stop-writes-on-bgsave-error no
rdbcompression yes // 使用压缩
dbfilename dump.rdb // RBD 文件的名字
dir ./
- 当 Redis 通过
SHUTDOWN
命令接收到关闭服务器的请求时,或者接收到 TERM命令时,会执行一个SAVE
命令,并且阻塞所有的客户端,不再执行任何请求。在SAVE
命令执行结束后关闭服务器。 - 当一个 Redis 服务器连接另一个 Redis 服务器,并向对方发送
SYNC
命令来开始一次复制操作的时候,如果主服务器没有在执行BGSAVE
操作,或者主服务器并非刚执行完BGSAVE
,那么主服务器会执行BGSAVE
命令。
RDB 的主要问题是,如果系统发生崩溃,那么最近一次执行完快照后修改的数据将被丢失。因此,RDB 适合用于即使丢失一部分数据也不会造成影响的应用程序。
AOF
AOF 指的是将所有执行的写命令写到 AOF 文件的末尾,以此来记录数据发生的变化。如果 Redis 想要恢复 AOF 中的数据,只要重新执行一次 AOF 文件中所包含的写命令就可以。
AOF 的配置如下所示:
appendonly yes // 打开 aof
appendfsync everysec // aof 同步的频率
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100 // 文件大小增长比超过这个值开始自动重写 aof
auto-aof-rewrite-min-size 64mb // 文件大小超过这个值才可以有可能自动重写 aof
上面的配置中,appendfsync everysec
设置的是同步的频率。应用程序在向硬盘写入数据的时候有 3 个步骤:
- 调用
file.write()
向文件写入,此时需要写入的内容被存储到了缓冲区,并不是真正写到硬盘上了。 - 操作系统在某个时候将缓冲区的内容写入硬盘,这时数据才真正被持久化了。
操作系统使用以上的文件写入方式是为了提高性能,毕竟硬盘 I/O 操作是比较耗时的。但是,这种方式的缺点在于如果机器崩溃了那么缓冲区的内容将丢失。程序可以使用 file.flush()
来请求操作系统尽快地将缓冲区的内容刷新到硬盘上,不过何时开始执行仍然由操作系统决定。程序也可以命令操作系统将文件同步 ( sync ) 到硬盘,同步操作会阻塞应用程序直到数据被写入硬盘。当同步操作完成后,即使系统出现故障,也不会对被同步的文件造成影响。
对于 Redis 来讲,可以指定 appendfsync
以何种方式让数据完全同步到硬盘,这个配置有 3 个选项:
- always: 每个 Redis 写命令都立即同步到硬盘,这是比较消耗性能的
- everysec: 每秒执行一次同步,兼顾性能与数据安全,是比较常用的选项
- no: 让操作系统决定何时进行同步
always
可以使得在 Redis 发生崩溃时丢失的数据最少,但是也是最消耗性能的,导致 Redis 的处理速度变慢。ererysec
是一种兼顾性能与数据安全的方式,在这种情况下,如果系统崩溃,用户最多会丢失一秒内的数据。no
选项完全将同步交给操作系统被决定,性能也不比 everysec
高多少,是不推荐的方式。
AOF 的缺点是随着 Redis 的不断运行,AOF 文件可能会非常大,甚至用完硬盘的空间。解决这个问题的办法是 AOF 重写。
重写
客户端可以发送 BGREWRITEAOF
命令让 Redis 重写 AOF 文件,Redis 会移除冗余的 AOF 命令进行重写,使得 AOF 文件的体积尽可能地小。
除了客户端主动发送 BGREWRITEAOF
命令,也可以使用配置让 Redis 在满足一定条件地情况下自动开始重写 AOF 文件。例如上一小节设置了 auto-aof-rewrite-percentage 100
和 auto-aof-rewrite-min-size 64mb
。这两个配置的含义是,如果 AOF 文件大于 64MB 并且比上一次重写之后的大小增加了一倍的时候,Redis 将执行 BGREWERITEAOF
命令。
过期键删除
用户往往为 Redis 中的键设定过期时间,因此需要一定的策略来删除过期键,可以有三种策略:
- 定时删除,即通过定时器在过期时间到达的时候删除过期的键。这种方式的优点是节省内存,不会因为大量的过期键占用内存资源,而缺点则是消耗 CPU 资源,尤其是过期键数量较多的时候,删除操作消耗太长时间,降低了 Redis 的响应时间。
- 惰性删除,即在每次获取某个键的时候判断是否过期,如果未过期,则正常返回其值,否则删除这个键,返回空。这种方式的优点是节省 CPU 资源,但是消耗了内存。尤其是过期键数量较多的时候,大量内存被无效的键占用,相当于内存泄露。
- 定期删除,即每隔一段时间周期对数据库中的键进行扫描,但是只扫描其中一部分,力求在内存和 CPU 之间达到一个平衡。
从上面 3 种策略可以看出,单用第一个肯定是不行的,Redis 的响应时间至关重要。第二个则是比较好的方式,在获取键的时候判断是否过期并决定是否删除,它的缺点是很多键无法及时删除。如果一个过期键再也没有被访问,那么它将永远留在内存中,而第三种方式正好可以弥补。
Redis 中过期键的删除策略正是惰性删除与定期删除的结合。
过期键与持久化
了解了过期键的删除策略后,下面看下键的过期时间对持久化的影响。
在生成 RDB 文件的过程中,如果一个键已经过期,那么其不会被保存到 RDB 文件中。在载入 RDB 的时候,要分两种情况:
- 如果 Redis 以主服务器的模式运行,那么会对 RDB 中的键进行时间检查,过期的键不会被恢复到 Redis 中。
- 如果 Redis 以从服务器的模式运行,那么 RDB 中所有的键都会被载入,忽略时间检查。在从服务器与主服务器进行数据同步的时候,从服务器的数据会先被清空,所以载入过期键不会有问题。
对于 AOF 来说,如果一个键过期了,那么不会立刻对 AOF 文件造成影响。因为 Redis 使用的是惰性删除和定期删除,只有这个键被删除了,才会往 AOF 文件中追加一条 DEL 命令。在重写 AOF 的过程中,程序会检查数据库中的键,已经过期的键不会被保存到 AOF 文件中。
在运行过程中,对于主从复制的 Redis,主服务器和从服务器对于过期键的处理也不相同:
- 对于主服务器,一个过期的键被删除了后,会向从服务器发送 DEL 命令,通知从服务器删除对应的键
- 从服务器接收到读取一个键的命令时,即使这个键已经过期,也不会删除,而是照常处理这个命令。
- 从服务器接收到主服务器的 DEL 命令后,才会删除对应的过期键。
这么做的主要目的是保证数据一致性,所以当一个过期键存在于主服务器时,也必然存在于从服务器。
总结
本文对 Redis 的两种持久化方式进行了简要的梳理,分析了 Redis 删除过期键的策略以及对持久化的影响。理解了这部分内容不仅可以让我们对 Redis 的使用更加得心应手,对于学习 Redis 的其它内容如复制的过程也会很有帮助。
参考
- 《Redis 实战》
- 《Redis 设计与实现》
如果我的文章对您有帮助,不妨点个赞支持一下(^_^)