Redis 高可用的概述

在 redis中,实现高可用的技术主要包括持久化,复制,哨兵和集群;

  • 持久化:持久化是最简单的高可用方法,主要作用是数据备份,即将数据存储在硬盘中,保证数据不会因进程退出而丢失;
  • 复制: 复制是高可用 Redis的基础,哨兵和集群都是在复制的基础上实现的高可用,复制主要实现了数据的多机备份,以及对于读操作的负载均衡和简单的故障恢复,缺陷:故障恢复无法自动化,写操作无法负载均衡;存储能力受单机的限制;
  • 哨兵: 在复制的基础上,哨兵实现了自动化的故障恢复,缺陷:写操作无法负载均衡;存储能力受单机的限制;
  • 集群: 通过集群,Redis解决了写操作无法负载均衡,以及存储能力受到单机限制的问题,实现了较为完善的高可用方案;

Redis持久化概述

​ 持久化的功能:Redis是内存数据库,数据都是存储在内存中,为了避免进程退出导致数据的永久丢失,需要定期将 Redis 中的数据以某种形式从内存保存到硬盘;当下次 Redis 重启时,利用持久化文件实现数据恢复,除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置.

Redis持久化分为 RDB持久化和 AOF 持久化: 前者将当前数据保存到硬盘,后者则将每次执行的写命令保存到硬盘;由于 AOF持久化的实时性更好,即当进程意外退出时丢失的数据更少,因此 AOF 是目前主流的持久化方式,不过 RDB 持久化仍然有其用武之地;

RDB 持久化

​ RDB 持久化是将当前进程中的数据生成快照保存到硬盘,保存的文件后缀是 rdb;当 Redis重新启动时,可以读取快照文件恢复数据;

触发条件

RDB持久化的触发分为 2 种,手动触发,自动触发

1) 手动触发:

save 命令和 bgsave命令都可以生成 RDB文件;

save命令会阻塞 Redis 服务进程,直到 RDB 文件创建完成为止,在 Redis服务器阻塞期间,服务器不能处理任何命令请求;

而 bgsave 命令创建一个子进程,由子进程来负责创建 RDB 文件,父进程(Redis主进程)则继续处理请求,此外,在自动触发 RDB 持久化时,Redis 也会选择 bgsave而不是 save 来进行持久化;

2) 自动触发

save m n

自动触发最常见的情况是在配置文件中通过 save m n ,指定当 m 秒内发生 n 次变化时,会触发 bgsave;

例如: 查看 redis 的默认配置文件(Linux下为 redis 跟目录下的 redis.conf)

save 900 1    #在900秒(15分钟)之后,如果至少有1个key发生变化,则dump内存快照。          

save 300 10 #在300秒(5分钟)之后,如果至少有10个key发生变化,则dump内存快照。

save 60 10000 #在60秒(1分钟)之后,如果至少有10000个key发生变化,则dump内存快照。

其中save 900 1的含义是:当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave;save 300 10和save 60 10000同理。当三个save条件满足任意一个时,都会引起bgsave的调用。

save m n的实现原理

除了save m n 以外,还有一些其他情况会触发bgsave:

  • 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点
启动时加载

RDB 文件的载入工作是在服务器启动时自动执行的,并没有专门的命令,但是由于 AOF 的优先级更高,因为当 AOF开启时,Redis 会优先载入 AOF 文件来恢复数据,只有当 AOF 关闭时,才会在 Redis服务器启动时检测 RDB文件,并自动载入,服务器载入 RDB 文件期间处于阻塞状态,直到载入完成为止;

Redis载入 RDB文件时,会对 RDB 文件进行效验,如果文件损坏,则日志会打印错误,Redis启动失败;

RDB常用配置总结

下面是RDB常用的配置项,以及默认值;前面介绍过的这里不再详细介绍。

  • save m n:bgsave自动触发的条件;如果没有save m n配置,相当于自动的RDB持久化关闭,不过此时仍可以通过其他方式触发
  • stop-writes-on-bgsave-error yes:当bgsave出现错误时,Redis是否停止执行写命令;设置为yes,则当硬盘出现问题时,可以及时发现,避免数据的大量丢失;设置为no,则Redis无视bgsave的错误继续执行写命令,当对Redis服务器的系统(尤其是硬盘)使用了监控时,该选项考虑设置为no
  • rdbcompression yes:是否开启RDB文件压缩
  • rdbchecksum yes:是否开启RDB文件的校验,在写入文件和读取文件时都起作用;关闭checksum在写入文件和启动文件时大约能带来10%的性能提升,但是数据损坏时无法发现
  • dbfilename dump.rdb:RDB文件名
  • dir ./:RDB文件和AOF文件所在目录

AOF持久化

RDB 持久化是将进程数据写入到文件,而 AOF 持久化则是将 Redis 执行的每次写命令记录到单独的日志文件中;当 Redis 重启时再次执行 AOF 文件中的命令来恢复数据;

与 RDB相比,AOF的实时性更好,因此成为主流的持久化方案;

开启 AOF

Redis 服务器默认开启 RDB,关闭 AOF;要开启 AOF,需要配置文件配置;

appendonly yes

执行流程

由于需要记录 Redis的每条命令,因此 AOF不需要触发;

AOF的执行流程包括:

  • 命令追加(append):将 Redis的命令追加到缓冲区 aof_buf;

  • 文件写入(write)和文件同步(sync):根据不同的同步策略将 aof_buf中的内容同步到硬盘;

  • 文件重写(rewirte):定期重写 AOF文件,达到压缩的目的;

命令追加(append)

Redis先将写命令追加到缓冲区,而不是直接写入文件,主要是为了避免每次有写命令都直接写入硬盘,导致硬盘IO成为Redis负载的瓶颈。

命令追加的格式是Redis命令请求的协议格式,它是一种纯文本格式,具有兼容性好、可读性强、容易处理、操作简单避免二次开销等优点;具体格式略。在AOF文件中,除了用于指定数据库的select命令(如select 0 为选中0号数据库)是由Redis添加的,其他都是客户端发送来的写命令。

2) 文件写入和文件同步

为了提高文件写入效率,在现代操作系统中,当用户调用write函数将数据写入文件时,操作系统通常会将数据暂存到一个内存缓冲区里,当缓冲区被填满或超过了指定时限后,才真正将缓冲区的数据写入到硬盘里。

这样的操作虽然提高了效率,但也带来了安全问题:如果计算机停机,内存缓冲区中的数据会丢失;因此系统同时提供了fsync、fdatasync等同步函数,可以强制操作系统立刻将缓冲区中的数据写入到硬盘里,从而确保数据的安全性。

AOF缓存区的同步文件策略由参数appendfsync控制,各个值的含义如下

  • always: 命令写入 aof_buf后立即调用系统 fsync操作同步到 AOF文件,fsync 完成后线程返回;这种情况下,每次有写命令都要同步到AOF文件,硬盘IO成为性能瓶颈,Redis只能支持大约几百TPS写入,严重降低了Redis的性能;即便是使用固态硬盘(SSD),每秒大约也只能处理几万个命令,而且会大大降低SSD的寿命。
  • no:命令写入aof_buf后调用系统write操作,不对AOF文件做fsync同步;同步由操作系统负责,通常同步周期为30秒。这种情况下,文件同步的时间不可控,且缓冲区中堆积的数据会很多,数据安全性无法保证。
  • everysec:命令写入aof_buf后调用系统write操作,write完成后线程返回;fsync同步文件操作由专门的线程每秒调用一次。everysec是前述两种策略的折中,是性能和数据安全性的平衡,因此是Redis的默认配置,也是我们推荐的配置。

3)文件重写

随着时间流逝,Redis服务器执行的写命令越来越多,AOF文件也会越来越大;过大的AOF文件不仅会影响服务器的正常运行,也会导致数据恢复需要的时间过长。

文件重写是指定期重写AOF文件,减小AOF文件的体积。需要注意的是,AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作!

文件重写之所以能够压缩AOF文件,原因在于:

  • 过期的数据不再写入文件
  • 无效的命令不再写入文件:如有些数据被重复设值(set mykey v1, set mykey v2)、有些数据被删除了(sadd myset v1, del myset)等等

文件重写的触发

手动和自动

手动:

直接调用 bgrewirteaof命令,该命令的执行与 bgsave有些类似: 都是 fork 子进程进行具体的工作且都只有在 fork时阻塞;

自动:

根据auto-aof-rewrite-min-size和auto-aof-rewrite-percentage参数,以及aof_current_size和aof_base_size状态确定触发时机。

  • auto-aof-rewrite-min-size:执行AOF重写时,文件的最小体积,默认值为64MB。
  • auto-aof-rewrite-percentage:执行AOF重写时,当前AOF大小(即aof_current_size)和上一次重写时AOF大小(aof_base_size)的比值。

AOF文件大小比值来决定什么时机触发;

AOF常用配置总结

下面是AOF常用的配置项,以及默认值;前面介绍过的这里不再详细介绍。

  • appendonly no:是否开启AOF
  • appendfilename “appendonly.aof”:AOF文件名
  • dir ./:RDB文件和AOF文件所在目录
  • appendfsync everysec:fsync持久化策略
  • no-appendfsync-on-rewrite no:AOF重写期间是否禁止fsync;如果开启该选项,可以减轻文件重写时CPU和硬盘的负载(尤其是硬盘),但是可能会丢失AOF重写期间的数据;需要在负载和安全性之间进行平衡
  • auto-aof-rewrite-percentage 100:文件重写触发条件之一
  • auto-aof-rewrite-min-size 64mb:文件重写触发提交之一
  • aof-load-truncated yes:如果AOF文件结尾损坏,Redis启动时是否仍载入AOF文件

方案选择与常见问题

前面介绍了RDB和AOF两种持久化方案的细节,下面介绍RDB和AOF的特点、如何选择持久化方案,以及在持久化过程中常遇到的问题等。

RDB和 AOF的优缺点
RDB 持久化

优点: RDB文件紧凑,体积小,网络传输快,适合全量复制,恢复速度比 AOF 快很多,当然,与 AOF 相比,RDB 最重要的优点之一是对性能的影响相对较小

缺点: RDB 文件的致命缺点在于其数据快照的持久化方式决定了必然做不到实时持久化,而在数据越来越重要的今天,数据的大量丢失很多时候是无法接受的,因此 AOF 持久化成为主流,此外,RDB 文件需要满足特定格式,兼容性差;

AOF持久化

与 RDB持久化相比,AOF 的优点在于支持秒级持久化,兼容性好,缺点是文件大,恢复速度慢,对性能影响大;

持久化策略选择

在介绍持久化策略之前,首先要明白无论是 RDB还是 AOF,持久化的开启都是要扶持性能方面的代价的

对于RDB持久化,一方面是 bgsave在进行 fork操作时 Redis主进程会阻塞,另一方面,子进程向硬盘写数据也会带来 IO 压力;

对于 AOF 持久化,向硬盘写数据的频率大大提高(everysec策略下为秒级),IO 压力更大,甚至可能造成 AOF 追加阻塞问题;此外,AOF 文件的重写与RDB的 bgsave类似,会有 fork 时的阻塞和子进程的 IO 压力问题;

相对来说,由于 AOF 向硬盘中写数据的频率更高,因此对 Redis主进程性能的影响会更大,

在实际生产环境中,根据数据量,应用对数据的安全要求,预算限制等不同情况,会有各种各样的持久化策略;

此外,持久化的选择必须与Redis的主从策略一起考虑,因为主从复制与持久化同样具有数据备份的功能,而且主机master和从机slave可以独立的选择持久化方案。

下面分场景来讨论持久化策略的选择,下面的讨论也只是作为参考,实际方案可能更复杂更具多样性。

  • 如果Redis中的数据完全丢弃也没有关系(如Redis完全用作DB层数据的cache),那么无论是单机,还是主从架构,都可以不进行任何持久化。
  • 在单机环境下(对于个人开发者,这种情况可能比较常见),如果可以接受十几分钟或更多的数据丢失,选择RDB对Redis的性能更加有利;如果只能接受秒级别的数据丢失,应该选择AOF。
  • 但在多数情况下,我们都会配置主从环境,slave的存在既可以实现数据的热备,也可以进行读写分离分担Redis读请求,以及在master宕掉后继续提供服务。

在这种情况下,一种可行的做法是:

master:完全关闭持久化(包括RDB和AOF),这样可以让master的性能达到最好。

slave:关闭RDB,开启AOF(如果对数据安全要求不高,开启RDB关闭AOF也可以),并定时对持久化文件进行备份(如备份到其他文件夹,并标记好备份的时间);然后关闭AOF的自动重写,然后添加定时任务,在每天Redis闲时(如凌晨12点)调用bgrewriteaof。

这里需要解释一下,为什么开启了主从复制,可以实现数据的热备份,还需要设置持久化呢?因为在一些特殊情况下,主从复制仍然不足以保证数据的安全,例如:

  • master和slave进程同时停止:考虑这样一种场景,如果master和slave在同一栋大楼或同一个机房,则一次停电事故就可能导致master和slave机器同时关机,Redis进程停止;如果没有持久化,则面临的是数据的完全丢失。
  • master误重启:考虑这样一种场景,master服务因为故障宕掉了,如果系统中有自动拉起机制(即检测到服务停止后重启该服务)将master自动重启,由于没有持久化文件,那么master重启后数据是空的,slave同步数据也变成了空的;如果master和slave都没有持久化,同样会面临数据的完全丢失。需要注意的是,即便是使用了哨兵(关于哨兵后面会有文章介绍)进行自动的主从切换,也有可能在哨兵轮询到master之前,便被自动拉起机制重启了。因此,应尽量避免“自动拉起机制”和“不做持久化”同时出现。