Redis高级进阶之数据持久化

448 阅读10分钟

开篇

在使用Redis之前,我们一定要搞明白我们为什么选择Redis?在进行技术选型时,我们要明白Redis的优缺点,Redis最大优点的就是响应速度快,缺点就是数据可靠性较差。Redis在设计之初,放弃了一些其他特性,以此来保证其快速的特点。而我们在使用Redis时,通常用其来做缓存,减少数据库的压力,这个时候,我们为了追求速度,对数据可靠性有一定的容忍度,Redis的优势就发挥出来了。

fork()的原理

在讲RDB之前,我们看一种场景:

现在有一个需求,我们需要在每天18点,对数据库的数据进行备份,要求备份的数据为18点时刻。第一种解决方案,18点时,Redis停止对外服务,开始备份数据,数据备份完毕,恢复对外服务。这是一种阻塞的解决方案,能够保证备份的数据是18点时刻的数据,但是无法对外提供服务,显然是无法容忍的。


第二种解决思路,Redis开启一个进程,主进程对外提供服务,子进程进行数据备份,这样就可以解决第一种方案里的问题,但是又会带来另一个问题。我们假设子进程数据备份需要10分钟,当子进程备份到第8分钟时,有客户端修改了原来第9分钟备份的数据,当Redis备份到第9分钟时,备份的已经不是18点时刻的数据,显然这也不是我们要的结果。


那么,实际中Redis是怎么解决这两种方案的问题?Redis是采用fork()解决这个问题的,但什么是fork()?在这之前我们需要明白在linux系统中,主进程与子进程的数据是相互隔离互不感扰的,主进程可以让子进程看到自己的数据,但是子进程修改同一个数据不会影响主进程,主进程修改数据也不会影响子进程。但是如果创建子进程时完全拷贝主进程的数据,有可能会超过Redis的最大内存。在linux内存中,每个应用都有虚拟地址空间,通过内核将虚拟地址空间映射到真实内存空间,那么我们此时只要子进程拷贝主进程的虚拟地址空间就行了,在通过内核映射到物理内存,这样就大大的节省了空间,简单来说就是将主进程的数据指针复制给子进程,但是此时又有一个问题,主进程如果修改了数据,子进程指向的数据又发生了改变,在fork()中,主子进程任何一个要修改数据都会触发copy on write机制,比如主进程修改一个数据时,不会改变原有的数据,而是重新开辟空间,并将主进程的指针指向新的内存空间,这样就保证了主进程修改了数据,子进程的数据没有改变。


Redis在进行数据持久化时,采用的就是fork()机制,保证在对外提供服务时,又可以高效的进行数据备份。

RDB(rewrite database)

什么是RDB?我们来看一下官网的描述:

  • RDB是一个非常紧凑的文件,它保存了某个时间点得数据集,非常适用于数据集的备份,比如你可以在每个小时报保存一下过去24小时内的数据,同时每天保存过去30天的数据,这样即使出了问题你也可以根据需求恢复到不同版本的数据集.
  • RDB是一个紧凑的单一文件,很方便传送到另一个远端数据中心或者亚马逊的S3(可能加密),非常适用于灾难恢复.
  • RDB在保存RDB文件时父进程唯一需要做的就是fork出一个子进程,接下来的工作全部由子进程来做,父进程不需要再做其他IO操作,所以RDB持久化方式可以最大化redis的性能.
  • 与AOF相比,在恢复大的数据集的时候,RDB方式会更快一些.

RDB可以采取两种方式开启,一种是在客户端使用save命令手动的开启,我们一般是在关机维护的时候使用save,另一种是bgsave即通过配置文件自动的开启,有趣的是在配置文件中bgsave开启竟然使用的是save标识,我们来看一下配置文件:



那么RDB有缺点吗?答案是肯定是有的,如果redis意外停止工作(例如电源中断),虽然我们可以配置不同的save时间点(例如每隔5分钟并且对数据集有100个写的操作),但是Redis要完整的保存整个数据集是一个比较繁重的工作,万一在Redis意外宕机,我们可能会丢失几分钟的数据。RDB 需要经常fork子进程来保存数据集到硬盘上,当数据集比较大的时候,fork的过程是非常耗时的,可能会导致Redis在一些毫秒级内不能响应客户端的请求.如果数据集巨大并且CPU性能不是很好的情况下,这种情况会持续1秒。RDB虽然能够保存某个时刻的全量数据,但是不支持数据拉链,不能恢复到以前版本的数据,而且频繁触发RDB会增加IO成本。那么有没有一种只添加修改的数据,不去重新备份全量数据的机制呢?Redis也为我们想到了这个,他就是AOF。

AOF(append only file)

什么是AOF?AOF类似与日志,每当Redis发生写操作时,Redis都会向AOF文件中追加一条记录。那么问题来了,如果我们的Redis运行了20年,我们的AOF文件有20T的大小,当Redis宕机后,我们要用AOF恢复数据,我们就要面临一是我们的服务器内存不够大,二是数据恢复的时间过长,这都是我们无法容忍的。

Redis怎么解决这个问题,在4.0版本以前,Redis会对AOF文件进行重写,删除抵消到多余的命令,比如一个客户端连续多次创建删除一个Key,那么Redis会对多余的指令进行删除抵消。在4.0版本以后,AOF重写时,会copy一份RDB文件,将增量的数据添加到AOF文件,AOF变成了混合文件,利用了RDB恢复数据快和日志数据全的特点。我们开一下Redis的配置文件:


我们可以配置 Redis 多久才将数据 fsync 到磁盘一次。有三种方式:

  • 每次有新命令追加到 AOF 文件时就执行一次 fsync :非常慢,也非常安全
  • 每秒 fsync 一次:足够快(和使用 RDB 持久化差不多),并且在故障时只会丢失 1 秒钟的数据。
  • 从不 fsync :将数据交给操作系统来处理。更快,也更不安全的选择。
  • 推荐(并且也是默认)的措施为每秒 fsync 一次, 这种 fsync 策略可以兼顾速度和安全性

服务器可能在程序正在对 AOF 文件进行写入时停机, 如果停机造成了 AOF 文件出错(corrupt), 那么 Redis 在重启时会拒绝载入这个 AOF 文件, 从而确保数据的一致性不会被破坏。当发生这种情况时, 可以用以下方法来修复出错的 AOF 文件:

  • 为现有的 AOF 文件创建一个备份。
  • 使用 Redis 附带的 redis-check-aof 程序,对原来的 AOF 文件进行修复:

    $ redis-check-aof –fix

  • (可选)使用 diff -u 对比修复后的 AOF 文件和原始 AOF 文件的备份,查看两个文件之间的不同之处。
  • 重启 Redis 服务器,等待服务器载入修复后的 AOF 文件,并进行数据恢复。

AOF只会在Redis发生写操作是进行记录,这就意味着它丢失的数据较少,在Redis中如果我们同时开启了RDB和AOF,在我们恢复数据的时候,Redis只会用AOF恢复数据,因为它恢复数据较为完整。

怎样从RDB方式切换为AOF方式

在 Redis 2.2 或以上版本,可以在不重启的情况下,从 RDB 切换到 AOF :

  • 为最新的 dump.rdb 文件创建一个备份。
  • 将备份放到一个安全的地方。
  • 执行以下两条命令:
  • redis-cli config set appendonly yes
  • redis-cli config set save “”
  • 确保写命令会被正确地追加到 AOF 文件的末尾。
  • 执行的第一条命令开启了 AOF 功能: Redis 会阻塞直到初始 AOF 文件创建完成为止, 之后 Redis 会继续处理命令请求, 并开始将写入命令追加到 AOF 文件末尾。

执行的第二条命令用于关闭 RDB 功能。 这一步是可选的, 如果你愿意的话, 也可以同时使用 RDB 和 AOF 这两种持久化功能。

重要:别忘了在 redis.conf 中打开 AOF 功能! 否则的话, 服务器重启之后, 之前通过 CONFIG SET 设置的配置就会被遗忘, 程序会按原来的配置来启动服务器。

AOF和RDB之间的相互作用

在版本号大于等于 2.4 的 Redis 中, BGSAVE 执行的过程中, 不可以执行 BGREWRITEAOF 。 反过来说, 在 BGREWRITEAOF 执行的过程中, 也不可以执行 BGSAVE。这可以防止两个 Redis 后台进程同时对磁盘进行大量的 I/O 操作。

如果 BGSAVE 正在执行, 并且用户显示地调用 BGREWRITEAOF 命令, 那么服务器将向用户回复一个 OK 状态, 并告知用户, BGREWRITEAOF 已经被预定执行: 一旦 BGSAVE 执行完毕, BGREWRITEAOF 就会正式开始。 当 Redis 启动时, 如果 RDB 持久化和 AOF 持久化都被打开了, 那么程序会优先使用 AOF 文件来恢复数据集, 因为 AOF 文件所保存的数据通常是最完整的。

备份redis数据

Redis 对于数据备份是非常友好的, 因为你可以在服务器运行的时候对 RDB 文件进行复制: RDB 文件一旦被创建, 就不会进行任何修改。 当服务器要创建一个新的 RDB 文件时, 它先将文件的内容保存在一个临时文件里面, 当临时文件写入完毕时, 程序才使用 rename(2) 原子地用临时文件替换原来的 RDB 文件。

这也就是说, 无论何时, 复制 RDB 文件都是绝对安全的。

  • 创建一个定期任务(cron job), 每小时将一个 RDB 文件备份到一个文件夹, 并且每天将一个 RDB 文件备份到另一个文件夹。
  • 确保快照的备份都带有相应的日期和时间信息, 每次执行定期任务脚本时, 使用 find 命令来删除过期的快照: 比如说, 你可以保留最近 48 小时内的每小时快照, 还可以保留最近一两个月的每日快照。
  • 至少每天一次, 将 RDB 备份到你的数据中心之外, 或者至少是备份到你运行 Redis 服务器的物理机器之外。