Redis高可用概述

在Redis语境中,高可用的含义:除了保证提供正常服务(如主从分离、快速容灾技术),还需要考虑数据容量的扩展、数据安全不会丢失等。

在Redis中,实现高可用的技术主要包括持久化、复制、哨兵和集群,下面分别说明它们的作用,以及解决了什么样的问题。

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

接下来会主要介绍redis的高可用,本节介绍redis的持久化部分。

Rdis持久化

一、Redis 持久化概述

redis持久化作用:将内存中的数据同步到磁盘来保证持久化,持久化可以避免因进程退出而造成数据丢失。当下次Redis重启时,利用持久化文件实现数据恢复。除此之外,为了进行灾难备份,可以将持久化文件拷贝到一个远程位置。

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

二、RDB持久化

1、RDB持久化方式

RDB持久化把当前进程数据生成快照(.rdb)文件保存到硬盘的过程,有手动触发和自动触发两种方式;

手动触发

手动触发有save和bgsave两命令

a、save命令:

阻塞当前Redis,直到RDB持久化过程完成为止,若内存实例比较大会造成长时间阻塞,线上环境不建议用它

b、bgsave命令:

redis进程执行fork操作创建子线程,由子线程完成持久化,阻塞时间很短(微秒级),是save的优化,在执行redis-cli shutdown关闭redis服务时,如果没有开启AOF持久化,自动执行bgsave;

bgsave运行流程
Redis系列(六)  高可用之持久化、复制 - 图1

  1. Redis父进程首先判断:当前是否在执行save,或bgsave/bgrewriteaof(后面会详细介绍该命令)的子进程,如果在执行则bgsave命令直接返回。bgsave/bgrewriteaof 的子进程不能同时执行,主要是基于性能方面的考虑:两个并发的子进程同时执行大量的磁盘写操作,可能引起严重的性能问题。
  2. 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的,Redis不能执行来自客户端的任何命令
  3. 父进程fork后,bgsave命令返回”Background saving started”信息并不再阻塞父进程,并可以响应其他命令
  4. 子进程创建RDB文件,根据父进程内存快照生成临时快照文件,完成后对原有文件进行原子替换
  5. 子进程发送信号给父进程表示完成,父进程更新统计信息

自动触发

a、配置save m n

自动触发最常见的情况是在配置文件中通过save m n,指定当m秒内发生n次变化时,会触发bgsave。
Redis系列(六)  高可用之持久化、复制 - 图2
其中save 900 1的含义是:当时间到900秒时,如果redis数据发生了至少1次变化,则执行bgsave;save 300 10和save 60 10000同理。当三个save条件满足任意一个时,都会引起bgsave的调用。

b、其他自动触发机制

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

  • 在主从复制场景下,如果从节点执行全量复制操作,则主节点会执行bgsave命令,并将rdb文件发送给从节点
  • 执行shutdown命令时,自动执行rdb持久化。

2、RDB文件操作

a、存储路径

RDB文件的存储路径既可以在启动前配置,也可以通过命令动态设定。
配置:dir配置指定目录,dbfilename指定文件名。默认是Redis根目录下的dump.rdb文件。
命令:可以用命令动态设置rdb文件路径和文件名。
设置路径:config set dir {newdir}

  1. config set dir /usr/local //设置rdb文件保存路径为usr/local

设置文件名:config set dbfilename {newFileName}

  1. config set dbfilename dump.rdb

b、恢复或启动时加载

将dump.rdb放到redis安装目录与redis.conf同级目录,重启redis即可。
  RDB文件的载入工作是在服务器启动时自动执行的,并没有专门的命令。但是由于AOF的优先级更高,因此当AOF开启时,Redis会优先载入AOF文件来恢复数据;只有当AOF关闭时,才会在Redis服务器启动时检测RDB文件,并自动载入。服务器载入RDB文件期间处于阻塞状态,直到载入完成为止。

3、优缺点

优点:

a、压缩后的二进制文,适用于备份、全量复制,用于灾难恢复
b、加载RDB恢复数据远快于AOF方式

缺点:

a、无法做到实时持久化,每次都要创建子进程,频繁操作成本过高
b、保存后的二进制文件,存在老版本不兼容新版本rdb文件的问题

三、AOF持久化

AOF持久化(即Append Only File持久化),是将Redis执行的每次写命令记录到单独的日志文件中(有点像MySQL的binlog);当Redis重启时再次执行AOF文件中的命令来恢复数据。与RDB相比,AOF的实时性更好,因此已成为主流的持久化方案。

1、开启AOF持久化

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

  1. appendonly yes (默认不开启,为no)

默认文件名:appendfilename “appendonly.aof”

2、AOF持久化流程

  1. 所有的写入命令(set hset)会append追加到aof_buf缓冲区中。
  2. AOF缓冲区向硬盘做sync同步。
  3. 随着AOF文件越来越大,需定期对AOF文件rewrite重写,达到压缩的目的。
  4. 当redis服务重启,可load加载AOF文件进行恢复。
    Redis系列(六)  高可用之持久化、复制 - 图3

3、关于文件重写

a、注意
  • 文件重写能减小AOF文件大小,也能减少数据恢复的时间。
  • AOF重写是把Redis进程内的数据转化为写命令,同步到新的AOF文件;不会对旧的AOF文件进行任何读取、写入操作!
  • 对于AOF持久化来说,文件重写虽然是强烈推荐的,但并不是必须的;

b、文件重写之所以能够压缩AOF文件,原因在于:
  • 过期的数据不再写入文件
  • 无效的命令不再写入文件:如有些数据被重复设值(set mykey v1, set mykey v2)、有些数据被删除了(sadd myset v1, del myset)等等
  • 多条命令可以合并为一个:如sadd myset v1, sadd myset v2, sadd myset v3可以合并为sadd myset v1 v2 v3。不过为了防止单条命令过大造成客户端缓冲区溢出,对于list、set、hash、zset类型的key,并不一定只使用一条命令;而是以某个常量为界将命令拆分为多条。这个常量在redis.h/REDIS_AOF_REWRITE_ITEMS_PER_CMD中定义,不可更改,3.0版本中值是64。

c、文件重写的流程(了解即可)

Redis系列(六)  高可用之持久化、复制 - 图4
注意:

  • 重写由父进程fork子进程进行;
  • 重写期间Redis执行的写命令,需要追加到新的AOF文件中,为此Redis引入了aof_rewrite_buf缓存。

对照上图,文件重写的流程如下:
1 ) Redis父进程首先判断当前是否存在正在执行 bgsave/bgrewriteaof的子进程,如果存在则bgrewriteaof命令直接返回,如果存在bgsave命令则等bgsave执行完成后再执行。这个主要是基于性能方面的考虑。

2 ) 父进程执行fork操作创建子进程,这个过程中父进程是阻塞的。

3.1) 父进程fork后,bgrewriteaof命令返回”Background append only file rewrite started”信息并不再阻塞父进程,并可以响应其他命令。Redis的所有写命令依然写入AOF缓冲区,并根据appendfsync策略同步到硬盘,保证原有AOF机制的正确。

3.2) 由于fork操作使用写时复制技术,子进程只能共享fork操作时的内存数据。由于父进程依然在响应命令,因此Redis使用AOF重写缓冲区(图中的aof_rewrite_buf)保存这部分数据,防止新AOF文件生成期间丢失这部分数据。也就是说,bgrewriteaof执行期间,Redis的写命令同时追加到aof_buf和aof_rewirte_buf两个缓冲区。

  1. 子进程根据内存快照,按照命令合并规则写入到新的AOF文件。

5.1) 子进程写完新的AOF文件后,向父进程发信号,父进程更新统计信息,具体可以通过info persistence查看。

5.2) 父进程把AOF重写缓冲区的数据写入到新的AOF文件,这样就保证了新AOF文件所保存的数据库状态和服务器当前状态一致。

5.3) 使用新的AOF文件替换老文件,完成AOF重写。

3、AOF如何恢复?

  1. 设置appendonly yes;
  2. 将appendonly.aof放到dir参数指定的目录;
  3. 启动Redis,Redis会自动加载appendonly.aof文件。

4、AOF常用配置总结

  1. appendonly yes //启用aof持久化方式
  2. ## appendfsync always //每收到写命令就立即强制写入磁盘,最慢的,但是保证完全的持久化,不推荐使用
  3. appendfsync everysec //每秒强制写入磁盘一次,性能和持久化方面做了折中,推荐
  4. ## appendfsync no //完全依赖os,性能最好,持久化没保证(操作系统自身的同步)
  5. no-appendfsync-on-rewrite yes //正在导出rdb快照的过程中,要不要停止同步aof
  6. auto-aof-rewrite-percentage 100 //aof文件大小比起上次重写时的大小,增长率100%时,重写
  7. auto-aof-rewrite-min-size 64mb //aof文件,至少超过64M时,重写

只有当auto-aof-rewrite-min-size和auto-aof-rewrite-percentage两个参数同时满足时,才会自动触发AOF重写,即bgrewriteaof操作(可以直接调用bgrewriteaof指令手动触发文件重写)。

5、AOF持久化的优缺点

与RDB持久化相对应,AOF的优缺点如下
优点:支持秒级持久化、兼容性好。
缺点:文件大、恢复速度慢、对性能影响大。

四、redis重启时加载AOF与RDB的顺序

Redis系列(六)  高可用之持久化、复制 - 图5

  1. 当AOF和RDB文件同时存在时,优先加载AOF
  2. 若关闭了AOF,加载RDB文件
  3. 加载AOF/RDB成功,redis重启成功
  4. AOF/RDB存在错误,启动失败打印错误信息

Redis 主从复制

一、主从复制概述

主从复制,是指将一台Redis服务器的数据,复制到其他的Redis服务器。前者称为主节点(master),后者称为从节点(slave);数据的复制是单向的,只能由主节点到从节点。

主从复制的作用

  • 数据冗余:主从复制实现了数据的热备份,是持久化之外的一种数据冗余方式。
  • 故障恢复:当主节点出现问题时,可以由从节点提供服务,实现快速的故障恢复;实际上是一种服务的冗余。
  • 负载均衡:在主从复制的基础上,配合读写分离,可以由主节点提供写服务,由从节点提供读服务(即写Redis数据时应用连接主节点,读Redis数据时应用连接从节点),分担服务器负载;尤其是在写少读多的场景下,通过多个从节点分担读负载,可以大大提高Redis服务器的并发量。
  • 高可用基石:除了上述作用以外,主从复制还是哨兵和集群能够实施的基础,因此说主从复制是Redis高可用的基础。

二、如何使用主从复制

1、建立复制

主从复制的开启,完全是在从节点发起的;不需要我们在主节点做任何事情。
从节点开启主从复制,有3种方式:

a、配置文件

在从服务器的配置文件中加入:slaveof

  1. slaveof 127.0.0.1 6379

在6379启动完后再启6380,完成配置;

b、启动命令

redis-server启动命令后加入 –slaveof

  1. ./redis-server -slaveof 127.0.0.1 6379

c、客户端命令

Redis服务器启动后,直接通过客户端执行命令:slaveof ,则该Redis实例成为从节点。

  1. slaveof 127.0.0.1 6379

2、断开复制

在slave节点,执行6380:> slaveof no one
需要注意的是,从节点断开复制后,不会删除已有的数据,只是不再接受主节点新的数据变化。

断开复制之后同样可以通过slaveof 建立复制。
可以用指令info replication 查看主从复制状态。

补充

a、读写分离:从节点建议用只读模式slave-read-only=yes, 因为从节点修改数据,主从数据不一致。并且这样读写分离可减轻主节点压力。
b、传输延迟:主从一般部署在不同机器上,复制时存在网络延时问题,redis提供repl-disable-tcp-nodelay参数决定是否关闭TCP_NODELAY(默认为关闭)
参数关闭时:无论大小都会及时发布到从节点,占带宽,适用于主从网络好的场景。
参数启用时:主节点合并所有数据成TCP包节省带宽,默认为40毫秒发一次,取决于内核,主从的同步延迟40毫秒,适用于网络环境复杂或带宽紧张,如跨机房。

三、Redis主从拓扑

1、一主一从

用于主节点故障转移从节点,当主节点的“写”命令并发高且需要持久化,可以只在从节点开启AOF(主节点不需要),这样即保证了数据的安全性,也避免持久化对主节点的影响。
Redis系列(六)  高可用之持久化、复制 - 图6

2、一主多从

针对“读”较多的场景,“读”由多个从节点来分担,但节点越多,主节点同步到多节点的次数也越多,影响带宽,也加重主节点的稳定。
Redis系列(六)  高可用之持久化、复制 - 图7

3、树状主从

一主多从的缺点是主节点推送次数多压力大,若主节点只推送一次数据到从节点1,再由从节点2推送到11,减轻主节点推送的压力。
Redis系列(六)  高可用之持久化、复制 - 图8

四、主从复制的实现原理

主从复制过程大体可以分为3个阶段:连接建立阶段(即准备阶段)、数据同步阶段、命令传播阶段;下面分别进行介绍。

1、连接建立阶段

该阶段的主要作用是在主从节点之间建立连接,为数据同步做好准备。
以下细节内容了解即可,不必细究。

步骤1:保存主节点信息

从节点服务器内部维护了两个字段,即masterhost和masterport字段,用于存储主节点的ip和port信息。
需要注意的是,slaveof是异步命令,从节点完成主节点ip和port的保存后,向发送slaveof命令的客户端直接返回OK,实际的复制操作在这之后才开始进行。

步骤2:建立socket连接

从节点每秒1次调用复制定时函数replicationCron(),如果发现了有主节点可以连接,便会根据主节点的ip和port,创建socket连接。如果连接成功,则:

从节点:为该socket建立一个专门处理复制工作的文件事件处理器,负责后续的复制工作,如接收RDB文件、接收命令传播等。

主节点:接收到从节点的socket连接后(即accept之后),为该socket创建相应的客户端状态,并将从节点看做是连接到主节点的一个客户端,后面的步骤会以从节点向主节点发送命令请求的形式来进行。

步骤3:发送ping命令

从节点成为主节点的客户端之后,发送ping命令进行首次请求,目的是:检查socket连接是否可用,以及主节点当前是否能够处理请求。

从节点发送ping命令后,可能出现3种情况:

(1)返回pong:说明socket连接正常,且主节点当前可以处理请求,复制过程继续。

(2)超时:一定时间后从节点仍未收到主节点的回复,说明socket连接不可用,则从节点断开socket连接,并重连。

(3)返回pong以外的结果:如果主节点返回其他结果,如正在处理超时运行的脚本,说明主节点当前无法处理命令,则从节点断开socket连接,并重连。

步骤4:身份验证

如果从节点中设置了masterauth选项,则从节点需要向主节点进行身份验证;没有设置该选项,则不需要验证。从节点进行身份验证是通过向主节点发送auth命令进行的,auth命令的参数即为配置文件中的masterauth的值。

如果主节点设置密码的状态,与从节点masterauth的状态一致(一致是指都存在,且密码相同,或者都不存在),则身份验证通过,复制过程继续;如果不一致,则从节点断开socket连接,并重连。

步骤5:发送从节点端口信息(可忽略)

身份验证之后,从节点会向主节点发送其监听的端口号(前述例子中为6380),主节点将该信息保存到该从节点对应的客户端的slave_listening_port字段中;该端口信息除了在主节点中执行info Replication时显示以外,没有其他作用。

2、数据同步阶段

该阶段可以理解为从节点数据的初始化。具体执行的方式是:从节点向主节点发送psync命令(Redis2.8以前是sync命令),开始同步。
这一阶段及以后,主从节点互为客户端,在数据同步阶段和后面的命令传播阶段,主节点需要主动向从节点发送请求(如推送缓冲区中的写命令),才能完成复制。

3、命令传播阶段

在这个阶段主节点将自己执行的写命令发送给从节点,从节点接收命令并执行,从而保证主从节点数据的一致性。

五、全量复制和部分复制

1、全量复制

用于初次复制或其他无法进行部分复制的情况,将主节点中的所有数据都发送给从节点,是一个非常重型的操作。

Redis通过psync命令进行全量复制的过程如下:

(1)从节点判断无法进行部分复制,向主节点发送全量复制的请求;或从节点发送部分复制的请求,但主节点判断无法进行全量复制;具体判断过程需要在讲述了部分复制原理后再介绍。
(2)主节点收到全量复制的命令后,执行bgsave,在后台生成RDB文件,并使用一个缓冲区(称为复制缓冲区)记录从现在开始执行的所有写命令。
(3)主节点的bgsave执行完成后,将RDB文件发送给从节点;从节点首先清除自己的旧数据,然后载入接收的RDB文件,将数据库状态更新至主节点执行bgsave时的数据库状态。
(4)主节点将前述复制缓冲区中的所有写命令发送给从节点,从节点执行这些写命令,将数据库状态更新至主节点的最新状态。
(5)如果从节点开启了AOF,则会触发bgrewriteaof的执行,从而保证AOF文件更新至主节点的最新状态。

2、部分复制

用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。需要注意的是,如果网络中断时间过长,导致主节点没有能够完整地保存中断期间执行的写命令,则无法进行部分复制,仍使用全量复制。

部分复制的实现,依赖于三个重要的概念:

(1)复制偏移量
主节点和从节点分别维护一个复制偏移量(offset),代表的是主节点向从节点传递的字节数;主节点每次向从节点传播N个字节数据时,主节点的offset增加N;从节点每次收到主节点传来的N个字节数据时,从节点的offset增加N。

offset用于判断主从节点的数据库状态是否一致:如果二者offset相同,则一致;如果offset不同,则不一致,此时可以根据两个offset找出从节点缺少的那部分数据。

(2)复制积压缓冲区
复制积压缓冲区是由主节点维护的、固定长度的、先进先出(FIFO)队列,默认大小1MB;当主节点开始有从节点时创建,其作用是备份主节点最近发送给从节点的数据。注意,无论主节点有一个还是多个从节点,都只需要一个复制积压缓冲区。

在命令传播阶段,主节点除了将写命令发送给从节点,还会发送一份给复制积压缓冲区,作为写命令的备份;除了存储写命令,复制积压缓冲区中还存储了其中的每个字节对应的复制偏移量(offset)。由于复制积压缓冲区定长且是先进先出,所以它保存的是主节点最近执行的写命令;时间较早的写命令会被挤出缓冲区。

由于该缓冲区长度固定且有限,因此可以备份的写命令也有限,当主从节点offset的差距过大超过缓冲区长度时,将无法执行部分复制,只能执行全量复制。

从节点将offset发送给主节点后,主节点根据offset和缓冲区大小决定能否执行部分复制:

  • 如果offset偏移量之后的数据,仍然都在复制积压缓冲区里,则执行部分复制;
  • 如果offset偏移量之后的数据已不在复制积压缓冲区中(数据已被挤出),则执行全量复制。

(3)服务器运行ID(runid)
每个Redis节点(无论主从),在启动时都会自动生成一个随机ID(每次启动都不一样),由40个随机的十六进制字符组成;runid用来唯一识别一个Redis节点。

主从节点初次复制时,主节点将自己的runid发送给从节点,从节点将这个runid保存起来;当断线重连时,从节点会将这个runid发送给主节点;主节点根据runid判断能否进行部分复制:

  • 如果从节点保存的runid与主节点现在的runid相同,说明主从节点之前同步过,主节点会继续尝试使用部分复制(到底能不能部分复制还要看offset和复制积压缓冲区的情况);
  • 如果从节点保存的runid与主节点现在的runid不同,说明从节点在断线前同步的Redis节点并不是当前的主节点,只能进行全量复制。

补充

在命令传播阶段,除了发送写命令,主从节点还维持着长连接心跳机制:PING和REPLCONF ACK。心跳机制对于主从复制的超时判断、数据安全等有作用。主节点默认每10S向从节点发ping命令,repl-ping-slave-period控制发送频率。