1. 如何保证 Redis 挂掉之后再重启数据可以恢复?(Redis 持久化)

1.1 快照持久化 RDB(Redis DataBase)

快照持久化是 Redis 默认采用的持久化方式,RDB 是 Redis 的二进制快照文件,优点是文件紧凑,占用空间小,恢复速度比较快。同时,由于是子进程 Fork 的模式,对 Redis 本身读写性能的影响很小。

Redis 创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。

开启自动持久化后,Redis 会将某一时刻的数据以二进制形式写入到磁盘里,数据会存储到默认名为dump.rdb的文件中。当 Redis 服务器重启时,检测到dump.rdb文件后,会自动加载进行数据恢复。

1.1.1 save

save命令是一个同步操作。当客户端向服务器发送save命令请求进行持久化时,服务器会阻塞save命令之后的其他客户端的请求,直到数据同步完成。如下图所示:
Redis 系统容灾 - 图1
但如果数据量太大,同步数据会执行很久,而这期间Redis服务器也无法接收其他请求,所以,最好不要在生产环境使用save命令。

9.1.2 bgsave

save命令不同,bgsave命令是一个异步操作。当客户端发服务发出bgsave命令时,Redis服务器主进程会forks一个子进程来数据同步问题,在将数据保存到RDB文件之后,子进程会退出。如下图所示:
Redis 系统容灾 - 图2
所以,与save命令相比,Redis 服务器在处理bgsave采用子线程进行 IO 写入,而主进程仍然可以接收其他请求,但forks子进程这个过程是同步的,所以forks子进程时,一样不能接收其他请求,这意味着,如果forks一个子进程花费的时间太久(一般是很快的),bgsave命令仍然有阻塞其他客户的请求的情况发生。

除了通过客户端发送命令外,还有一种方式是在 Redis 配置文件进行配置,Redis 会自动触发bgsave命令创建快照。比如在redis.conf中默认有以下配置:

  1. save 900 1 #在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发bgsave命令创建快照
  2. save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,Redis就会自动触发bgsave命令创建快照
  3. save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,Redis就会自动触发bgsave命令创建快照

9.1.3 写时复制

执行 bgsave 时,Redis 依然可以继续处理操作命令,也就是数据仍然是可以被修改的。这里关键的技术就在于写时复制。执行 bgsave 命令的时候,会通过 fork() 创建子进程,此时子进程和父进程是共享同一片内存数据的,因为创建子进程的时候,会复制父进程的页表,但是页表指向的物理内存还是一个。
image.png
只有在发生修改内存数据的情况时,物理内存才会被复制一份。
image.png
这样的目的是为了减少创建子进程时的性能损耗,从而加快创建子进程的速度,毕竟创建子进程的过程中,是会阻塞主线程的。

9.1.4 总结

无论是由主进程生成还是子进程来生成,其过程如下:

  1. 生成临时 rdb 文件,并写入数据。
  2. 完成数据写入,用临时文件代替代正式 rdb 文件。
  3. 删除原来的 rdb 文件。

还有一些其他的常见配置:


rdbcompression yes          #是否压缩rdb文件
dbfilename redis-6379.rdb   #在单机下启动多个redis服务器进程时,可以通过端口号配置不同的rdb名称
dir ~/redis/                #rdb文件保存目录

优点:

  1. 文件体积小。RDB 的文件内容是二进制格式,因此体积比实例内存小。
  2. 恢复速度快。当 Redis 实例恢复时,加载 RDB 文件速度很快,能在很短时间内迅速恢复数据。

缺点:

  1. 数据缺失。无法保存最近一次快照之后的数据,当 Redis 实例某一时刻异常时,会导致数据丢失。
  2. 消耗资源。RDB 文件的生成会消耗大量的 CPU 和内存资源,有一定代价。

    1.2 AOF 持久化(Append-Only File)

    另一种方式是 AOF,AOF 中记录了 Redis 的操作命令,可以重放请求恢复现场,AOF 的文件会比 RDB 大很多。与快照持久化相比,AOF的优点是故障情况下,丢失的数据会比RDB更少。如果是执行命令后立马刷入,AOF会拖累执行速度,所以一般都是配置为每秒定期刷入,这样对性能影响不会很大。AOF持久化方式会记录客户端对服务器的每一次写操作命令,并将这些写操作按照 Redis 协议追加保存到以后缀为aof文件末尾,在 Redis 服务器重启时,会加载并运行aof文件的命令,以达到恢复数据的目的。如下图所示:
    Redis 系统容灾 - 图5

    aof 文件是一个二进制文件,并不是直接保存每个命令,而使用 Redis 自己的格式。

1.2.1 开启 AOF 持久化

想要使用 AOF 持久化方式,需要启用配置文件中的 appendonly 参数。如果服务器开启了 AOF 持久化功能,会优先使用 AOF 文件来进行恢复。我们可以在redis.conf中配置如下参数:

appendonly yes                      #开启aof机制  
appendfilename "appendonly.aof"     #aof文件名
appendfsync everysec                #写入策略,always表示每个写操作都保存到aof文件中,也可以是everysec或no
no-appendfsync-on-rewrite no        #默认不重写aof文件
auto-aof-rewrite-percentage 100     #aof文件距离上次文件增长超过多少百分比
auto-aof-rewrite-min-size 64mb      #aof文件体积最小多大以上触发
dir ~/redis/                        #保存目录

1.2.2 AOF 的三种写入策略

redis.conf,可以通过配置appendfsync使用 AOF 的三种写入策略:

  1. always:客户端的每一个写操作都会保存到 aof 文件,这种策略很安全,但是每个写请注都有 IO 操作,所以也很慢。
  2. everysec:是 appendfsync 的默认写入策略,每秒写入一次 aof 文件,因此,最多可能会丢失 1s 的数据。
  3. no:Redis 服务器不负责写入 aof,而是交由操作系统来处理什么时候写入 aof 文件。更快,但也是最不安全的选择,不推荐使用。

    1.2.3 如何防止 AOF 文件过大?(AOF 文件重写)

    AOF 将客户端的每一个写操作都追加到aof文件末尾,随着命令的不断写入,AOF 文件会变得越来越大。aof 文件太大,加载 aof 文件恢复数据时,就会非常慢。为了解决这个问题,Redis 支持 aof 文件重写,通过重写 aof,可以生成一个恢复当前数据的最少命令集,比如 10000 条incr num 1命令可以重写为set num 100000。说白了,就是针对相同Key的操作,进行合并。

通过在redis.conf配置文件中的选项no-appendfsync-on-rewrite可以设置是否开启重写,这种方式会在每次fsync时都重写,影响服务器性能,因此默认值为 no,不推荐使用。除此之外,客户端向服务器发送bgrewriteaof命令,也可以让服务器进行 AOF 重写。

AOF 重写方式也是异步操作,即如果要写入 aof 文件,则 Redis 主进程会forks一个子进程来处理,主要流程如下所示:

  1. 调用 fork(),创建一个子进程。
  2. 子进程直接把当前数据生成对应命令,不依赖于原来的 AOF,把新的 AOF 写到一个临时文件里。
  3. 主进程继续工作,同时获取子进程重写 AOF 的完成信号,用新的 AOF 文件替换老的 AOF 文件。

在重写过程中,Redis 不但将新的操作记录在原有的 AOF 缓冲区,而且还会记录在 AOF 重写缓冲区。一旦新 AOF 文件创建完毕,Redis 就会将重写缓冲区内容,追加到新的 AOF 文件,再用新 AOF 文件替换原来的 AOF 文件。
image.png

1.2.4 AOF 文件恢复

同样地,我们也需要对 AOF 文件进行恢复。和 RDB 不同的是,Redis 中是通过创建一个不带网络连接的伪客户端来进行实现的。为什么要创建伪客户端呢?因为 AOF 文件中的数据格式,都是由命令组成的。通过客户端直接执行每条命令就可以将数据进行恢复。

1.2.5 总结

优点:

  1. 数据更完整。AOF 中是及时写入的方式,数据保存更完整。恢复时降低数据的损失率
  2. 易读性强。AOF 中保存的数据格式是客户端的写入命令,可读性性强。

缺点:

  1. 文件体积大。AOF 中存储客户端所有的写命令,未经压缩,随着命令的写入,文件会越来越大。
  2. 增加磁盘IO。AOF 文件刷盘如果采用每秒刷一次的方式会导致磁盘IO升高,影响性能。

    1.3 Redis 4.0 的 RDB-AOF 混合持久化

    1.3.1 混合持久化

    尽管 RDB 比 AOF 的数据恢复速度快,但是快照的频率不好把握:
  • 如果频率太低,两次快照间一旦服务器发生宕机,就可能会比较多的数据丢失;
  • 如果频率太高,频繁写入磁盘和创建子进程会带来额外的性能开销。

为了兼备 RDB 恢复速度快AOF 丢失数据少的优点,Redis 4.0 提出了混合持久化。如果想要开启混合持久化功能,可以在 Redis 配置文件将下面这个配置项设置成 yes:

aof-use-rdb-preamble yes

当开启了混合持久化时,在 AOF 重写日志时,fork 出来的重写子进程会先将与主线程共享的内存数据以 RDB 方式写入到 AOF 文件,然后主线程处理的操作命令会被记录在重写缓冲区里,重写缓冲区里的增量命令会以 AOF 方式写入到 AOF 文件,写入完成后通知主进程将新的含有 RDB 格式和 AOF 格式的 AOF 文件替换旧的的 AOF 文件。也就是说,使用了混合持久化,AOF 文件的前半部分是 RDB 格式的全量数据,后半部分是 AOF 格式的增量数据

这样的好处在于,重启 Redis 加载数据的时候,由于前半部分是 RDB 内容,这样加载的时候速度会很快。加载完 RDB 的内容后,才会加载后半部分的 AOF 内容,这里的内容是 Redis 后台子进程重写 AOF 期间,主线程处理的操作命令,可以使得数据更少的丢失

1.3.2 文件恢复

那么混合持久化中是如何来进行数据恢复的呢?在 Redis 重启时,先加载 RDB 的内容,然后再重放增量 AOF 格式命令。这样就避免了 AOF 持久化时的全量加载,从而使加载速率得到大幅提升。

1.4 RDB 和 AOF 的区别

在应用时,要根据自己的实际需求,选择 RDB 持久化或者 AOF 持久化。其实,如果想要数据足够安全,可以两种方式都开启,但两种持久化方式同时进行 IO 操作,会严重影响服务器性能,因此有时候不得不做出选择。
Redis 系统容灾 - 图7
当 RDB 与 AOF 两种方式都开启时,Redis 会优先使用 AOF 日志来恢复数据,因为 AOF 保存的文件比 RDB 文件更完整。

2. Redis 哨兵机制

TODO

参考

  1. 彻底理解 Redis 的持久化和主从复制
  2. 我是面试官,Redis面试攻略第一弹