> 文章列表 > Redis高级功能

Redis高级功能

Redis高级功能

目录

1.RDB 持久化

1.1生成RDB文件命令

1.2RDB 文件结构

1.3RDB 文件结构 - database 部分

2.AOF 持久化

2.主从复制

2.1重同步 - 完整重同步 

2.2重同步 - 部分重同步

2.2.1重同步 - 部分重同步的实现 - PSYNC的实现原理 

3.复制的具体过程

3.Sentinel 哨兵模式

3.1启动并初始化Sentinel

3.2 获取主服务器信息

3.3获取从服务器信息

3.4 向主服务器和从服务器发送信息

3.5 接收来自主服务器和从服务器的频道信息 

3.6 接收来自主服务器和从服务器的频道信息 

3.7检测主、客观下线状态

3.8选举领头Sentinel 

3.9故障转移


1.RDB 持久化

  • Redis是内存数据库,它将自己的数据库状态储存在内存里面,所以如果不想办法将储存在内存中的数据库状态保存到磁盘里面,那么一旦服务器进程退出,服务器中的数据库状态也会消失不见。 为了解决这个问题,Redis提供了RDB持久化功能,这个功能可以将Redis在内存中的数据库状态保存到磁盘里面,避免数据意外丢失。
  • RDB持久化既可以手动执行,也可以根据服务器配置选项定期执行,该功能可以将某个时间点上的数据库状态保存到一个RDB文件中。RDB持久化功能所生成的RDB文件是一个经过压缩的二进制文件,通过该文件可以还原生成RDB文件时的数据库状态。和使用SAVE命令或者BGSAVE命令创建RDB文件不同,RDB文件的载入工作是在服务器启动时自动执行的,所以Redis并没有专门用于载入RDB文件的命令,只要Redis服务器在启动时检测到RDB文件存在,它就会自动载入RDB文件。因为RDB文件是保存在硬盘里面的,所以即使Redis服务器进程退出,甚至运行Redis服务器的计算机停机,但只要RDB文件仍然存在,Redis服务器就可以用它来还原数据库状态。

1.1生成RDB文件的命令

有两个Redis命令可以用于生成RDB文件,一个是SAVE,另一个是BGSAVE

SAVE命令会阻塞Redis服务器进程,直到RDB文件创建完毕为止,在服务器进程阻塞期间,服务器不能处理任何命令请求(SAVE命令,大家只有在测试、开发环境手动使用就行了);和SAVE命令直接阻塞服务器进程的做法不同,BGSAVE命令会派生出一个子进程,然后由子进程负责创建RDB文件,服务器进程(父进程)继续处理命令请求(redis的主从复制的时候)。

BGSAVE命令的保存工作是由子进程执行的,所以在子进程创建RDB文件的过程中,Redis服务器仍然可以继续处理客户端的命令请求,但是,在BGSAVE命令执行期间,服务器处理SAVE、BGSAVE、BGREWRITEAOF三个命令的方式会和平时有所不同。

首先,在BGSAVE命令执行期间,客户端发送的SAVE命令会被服务器拒绝,防止产生竞争条件。 其次,在BGSAVE命令执行期间,客户端发送的BGSAVE命令会被服务器拒绝,因为同时执行两个BGSAVE命令也会产生竞争条件。

最后,出于性能方面的考虑,BGREWRITEAOF和BGSAVE两个命令不能同时执行: ·如果BGSAVE命令正在执行,那么客户端发送的BGREWRITEAOF命令会被延迟到BGSAVE命令执行完毕之后执行(BGSVAE 不排斥BGREWRITEAOF,排队效应)。 ·如果BGREWRITEAOF命令正在执行,那么客户端发送的BGSAVE命令会被服务器拒绝。

1.2RDB 文件结构

下图展示了一个完整RDB文件所包含的各个部分:

  •  RDB文件的最开头是REDIS部分,这个部分的长度为5字节,保存着“REDIS”五个字符。通过这五个字符,程序可以在载入文件时,快速检查所载入的文件是否RDB文件。
  • db_version长度为4字节,它的值是一个字符串表示的整数,这个整数记录了RDB文件的版本号,比如"0006"就代表RDB文件的版本为第六版。
  • databases部分包含着零个或任意多个数据库,以及各个数据库中的键值对数据。如果服务器的数据库状态为空),那么这个部分也为空,长度为0字节。
  • EOF常量的长度为1字节,这个常量标志着RDB文件正文内容的结束。
  • check_sum是一个8字节长的无符号整数,保存着一个校验和,这个校验和是程序通过对REDIS、db_version、databases、EOF四个部分的内容进行计算得出的。服务器在载入RDB文件时,会将载入数据所计算出的校验和与check_sum所记录的校验和进行对比,以此来检查RDB文件是否有出错或者损坏的情况出现。

1.3RDB 文件结构 - database 部分

每个非空数据库在RDB文件中都可以保存为SELECTDB、db_number、key_value_pairs三个部分:

  1. SELECTDB常量的长度为1字节,当读入程序遇到这个值的时候,它知道接下来要读入的将是一个数据库号码。
  2. db_number保存着一个数据库号码,根据号码的大小不同,这个部分的长度可以是1字节、2字节或者5字节。当程序读入db_number部分之后,服务器会调用SELECT命令,根据读入的数据库号码进行数据库切换,使得之后读入的键值对可以载入到正确的数据库中。
  3. key_value_pairs部分保存了数据库中的所有键值对数据,如果键值对带有过期时间,那么过期时间也会和键值对保存在一起。根据键值对的数量、类型、内容以及是否有过期时间等条件的不同,key_value_pairs部分的长度也会有所不同。 

2.AOF 持久化

除了RDB持久化功能之外,Redis还提供了AOF(Append Only File)持久化功能。与RDB持久化通过保存数据库中的键值对来记录数据库状态不同,AOF持久化是通过保存Redis服务器所执行的写命令来记录数据库状态的。

AOF持久化功能的实现可以分为命令追加(append)、文件写入、文件同步(sync)三个步骤

命令追加:当AOF持久化功能处于打开状态时,服务器在执行完一个写命令之后,会以协议格式将被执行的写命令追加到服务器状态的aof_buf缓冲区的末尾:

struct redisServer {// ...// AOF缓冲区sds aof_buf;// ...
};

写入与同步

服务器每次结束一个事件循环之前,它都会调用flushAppendOnlyFile函数进行文件写入。flushAppendOnlyFile函数的行为由服务器配置的appendfsync选项的值来决定,各个不同值产生的行为如表

如果用户没有主动为appendfsync选项设置值,那么appendfsync选项的默认值为everysec。服务器配置appendfsync选项的值直接决定AOF持久化功能的效率和安全性。

  • 当appendfsync的值为always时,服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,所以always的效率是最慢的;但always也是最安全的,因为即使出现故障,只会丢失一个事件循环中所产生的命令数据。(并发量不高,并且对数据要求百分百(redis保证99%,剩余的1%由我们的程序保证,每进行一个redis操作,从程序层面也要进行一次记录)不丢失的情况下,用 always)
  • 当appendfsync的值为everysec时,服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,并且每隔一秒就要在子线程中对AOF文件进行一次同步。everysec模式足够快,并且就算出现故障停机,也只丢失一秒钟的命令数据。
  • 当appendfsync的值为no时,服务器在每个事件循环都要将aof_buf缓冲区中的所有内容写入到AOF文件,至于何时对AOF文件进行同步,则由操作系统控制。

2.主从复制

  • 在Redis中,用户可以通过执行SLAVEOF命令或者设置slaveof选项,让一个服务器去复制(replicate)另一个服务器,我们称呼被复制的服务器为主服务器(master),而对主服务器进行复制的服务器则被称为从服务器(slave)。
  • 当客户端向从服务器发送SLAVEOF命令,要求从服务器复制主服务器时,从服务器首先需要执行同步操作,也即是,将从服务器的数据库状态更新至主服务器当前所处的数据库状态。

Redis的复制功能分为同步(sync)和命令传播(command propagate)两个操作:

  1.  同步。是第一次进行主从服务器连接的时候,一定会进行同步操作(主服务器-RDB文件传输-从服务器);从服务器断线重连主服务器的时候,有可能触发同步操作(如果从服务器短线期间,新的命令都存在主服务器的缓冲区里,那么就直接发送这部分命令(命令传播)给从服务器就可以了;如果这些新的命令,不全部存在于主服务器的缓冲区中,则触发同步操作(RDB 文件))。
  2.  命令传播。第一次链接完我们的主服务器后,通过RDB文件同步之后的所有的命令,都是采用命令传播的形式。 同理,断线重连之后的所有主从数据的同步都采用命令传播。

2.1重同步 - 完整重同步 

RDB文件同步(1. 第一次连接;2 断线重连有可能触发RDB文件同步)

PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)两种模式:

其中完整重同步用于处理初次复制情况:完整重同步的执行步骤和SYNC命令的执行步骤基本一样,它们都是通过让主服务器创建并发送RDB文件,以及向从服务器发送保存在缓冲区里面的写命令来进行同步。(如下图为完整重同步)

完整重同步,需要主服务器生成一个新的 RDB文件,文件生成的开销是有得; RDB文件是整个的redis的数据状态,整个状态的 RDB文件体积不会太小,传输给从服务器需要网络开销及时间; 相比于小体积的命令传播,这个RDB 文件开销大得多。

2.2重同步 - 部分重同步

PSYNC命令具有完整重同步(full resynchronization)和部分重同步(partial resynchronization)两种模式:

部分重同步则用于处理断线后重复制情况:当从服务器在断线后重新连接主服务器时,如果条件允许,主服务器可以将主从服务器连接断开期间执行的写命令发送(命令传播)给从服务器(缓冲区大小是否能够容纳断线期间的命令),从服务器只要接收并执行这些写命令,就可以将数据库更新至主服务器当前所处的状态。

部分重同步过程

  1. 主服务器和从服务器都需要记录一个偏移量,比如从服务器断线时,偏移量是100;断线期间主服务器从100 变为了120, 那么这20的偏移量就是需要进行同步的;
  2. 断线期间的命令必须有一个地方放;
  3. 主服务器有多个从服务器,每个从服务器都需要维护自己的偏移量,主服务器如果想要区分每个服务器的偏移量,需要通过一个唯一的id标识从服务器

2.2.1重同步 - 部分重同步的实现 - PSYNC的实现原理 

  • PSYNC命令的调用方法有两种: ·如果从服务器以前没有复制过任何主服务器,或者之前执行过SLAVEOF no one命令,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC ? -1命令,主动请求主服务器进行完整重同步(因为这时不可能执行部分重同步)。 ·相反地,如果从服务器已经复制过某个主服务器,那么从服务器在开始一次新的复制时将向主服务器发送PSYNC <runid> <offset>命令:其中runid是上一次复制的主服务器的运行ID,而offset则是从服务器当前的复制偏移量,接收到这个命令的主服务器会通过这两个参数来判断应该对从服务器执行哪种同步操作。
  • 根据情况,接收到PSYNC命令的主服务器会向从服务器返回以下三种回复的其中一种: ·如果主服务器返回+FULLRESYNC <runid> <offset>回复,那么表示主服务器将与从服务器执行完整重同步操作:其中runid是这个主服务器的运行ID,从服务器会将这个ID保存起来,在下一次发送PSYNC命令时使用;而offset则是主服务器当前的复制偏移量,从服务器会将这个值作为自己的初始化偏移量。
  • 如果主服务器返回+CONTINUE回复,那么表示主服务器将与从服务器执行部分重同步操作,从服务器只要等着主服务器将自己缺少的那部分数据发送过来就可以了。
  • 如果主服务器返回-ERR回复,那么表示主服务器的版本低于Redis 2.8(SYNC: SYNC 没有部分重同步),它识别不了PSYNC命令,从服务器将向主服务器发送SYNC命令,并与主服务器执行完整同步操作。

3.复制的具体过程

  • 设置主服务器的地址和端口:当客户端向从服务器发送以下命令时:SLAVEOF 127.0.0.1 6379。 从服务器首先要做的就是将客户端给定的主服务器IP地址127.0.0.1以及端口6379保存到服务器状态的masterhost属性和masterport属性里面。SLAVEOF命令是一个异步命令,在完成masterhost属性和masterport属性的设置工作之后,从服务器将向发送SLAVEOF命令的客户端返回OK,表示复制指令已经被接收,而实际的复制工作将在OK返回之后才真正开始执行
  • 建立套接字连接 在SLAVEOF命令执行之后,从服务器将根据命令所设置的IP地址和端口,创建连向主服务器的套接字连接,从服务器可以向主服务器发送命令请求,而主服务器则会向从服务器返回命令回复
  • 发送PING命令:从服务器成为主服务器的客户端之后,做的第一件事就是向主服务器发送一个PING命令。这个PING命令有两个作用: ·通过发送PING命令可以检查套接字的读写状态是否正常。 ·通过发送PING命令可以检查主服务器能否正常处理命令请求。 从服务器在发送PING命令之后将遇到以下三种情况的其中一种: ·如果服务器不能在规定的时限(timeout)内读取出命令回复的内容,那么表示主从服务器之间的网络连接状态不佳,不能继续执行复制工作的后续步骤。当出现这种情况时,从服务器断开并重新创建连向主服务器的套接字。 ·如果主服务器向从服务器返回一个错误,那么表示主服务器暂时没办法处理从服务器的命令请求,不能继续执行复制工作的后续步骤。当出现这种情况时,从服务器断开并重新创建连向主服务器的套接字 ·如果从服务器读取到"PONG"回复,那么表示主从服务器之间的网络连接状态正常,并且主服务器可以正常处理从服务器(客户端)发送的命令请求,在这种情况下,从服务器可以继续执行复制工作的下个步骤。
  • 身份验证 从服务器在收到主服务器返回的"PONG"回复之后,下一步要做的就是决定是否进行身份验证:
  • 如果从服务器设置了masterauth选项,那么进行身份验证。
  • 如果从服务器没有设置masterauth选项,那么不进行身份验证。 从服务器在身份验证阶段可能遇到的情况有以下几种:
  • 如果主服务器没有设置requirepass选项,并且从服务器也没有设置masterauth选项,那么主服务器将继续执行从服务器发送的命令,复制工作可以继续进行。
  • 如果从服务器通过AUTH命令发送的密码和主服务器requirepass选项所设置的密码相同,那么主服务器将继续执行从服务器发送的命令,复制工作可以继续进行。与此相反,如果主从服务器设置的密码不相同,那么主服务器将返回一个invalid password错误。
  • 如果主服务器设置了requirepass选项,但从服务器却没有设置masterauth选项,那么主服务器将返回一个NOAUTH错误。另一方面,如果主服务器没有设置requirepass选项,但从服务器却设置了masterauth选项,那么主服务器将返回一个no password is set错误。
  •  发送端口信息 在身份验证步骤之后,从服务器将执行命令REPLCONF listening-port <port-number>,向主服务器发送从服务器的监听端口号 主服务器在接收到这个命令之后,会将端口号记录在从服务器所对应的客户端状态的slave_listening_port属性中
  • 同步 在这一步,从服务器将向主服务器发送PSYNC命令,执行同步操作,并将自己的数据库更新至主服务器数据库当前所处的状态。 值得一提的是,在同步操作执行之前,只有从服务器是主服务器的客户端,但是在执行同步操作之后,主服务器也会成为从服务器的客户端: ·如果PSYNC命令执行的是完整重同步操作,那么主服务器需要成为从服务器的客户端,才能将保存在缓冲区里面的写命令发送给从服务器执行。 ·如果PSYNC命令执行的是部分重同步操作,那么主服务器需要成为从服务器的客户端,才能向从服务器发送保存在复制积压缓冲区里面的写命令。

3.Sentinel 哨兵模式

  • Sentinel(哨岗、哨兵)是Redis的高可用性(high availability)解决方案:由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
  • 假设这时,主服务器server1进入下线状态,那么从服务器server2、server3、server4对主服务器的复制操作将被中止,并且Sentinel系统会察觉到server1已下线。 首先,Sentinel系统会挑选server1属下的其中一个从服务器,并将这个被选中的从服务器升级为新的主服务器。 ·之后,Sentinel系统会向server1属下的所有从服务器发送新的复制指令,让它们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕。 ·另外,Sentinel还会继续监视已下线的server1,并在它重新上线时,将它设置为新的主服务器的从服务器。

整体流程:

  1.  如果主服务器进行了断开,替换了从服务器。除了被提升为主服务器的从服务器。所有的从服务器都必须做一次完整重同步(服务器的运行ID发生了变化,进行完整重同步)。原来的主服务器,上线之后,无论作为谁的从服务器,都必须进行一次完整重同步。 提升为新的主服务的压力在于 BGSAVE(几乎进行一次即可), 但是需要进行多份的网络传递; 第二次BGSAVE应该发生在 “新的” 从服务器链接到主服务器。 主服务器断链,是一件很可怕的事情。会造成服务短时间不可用(还没选举出新的主服务器的时候),再次造成服务的卡顿(BGSAVE 和 rdb文件多份传输的时候)。
  2. 集群,多个主,这个主服务器只是一个集群里的节点。如果这个节点挂了,那我们的集群就会将请求路由到其他的主服务器节点上,上边说的这个压力,在集群环境下就微乎其服务器微了。
  3. 如果是从服务器下线了,就是之前所说的断线重连 pysnc那部分内容。

3.1启动并初始化Sentinel

启动一个Sentinel可以使用命令:

$ redis-sentinel /path/to/your/sentinel.conf

或者命令: $ redis-server /path/to/your/sentinel.conf --sentinel

这两个命令的效果完全相同。 当一个Sentinel启动时,它需要执行以下步骤:

  1. 初始化服务器。 因为Sentinel执行的工作和普通Redis服务器执行的工作不同,所以Sentinel的初始化过程和普通Redis服务器的初始化过程并不完全相同。普通服务器在初始化时会通过载入RDB文件或者AOF文件来还原数据库状态,但是因为Sentinel并不使用数据库,所以初始化Sentinel时就不会载入RDB文件或者AOF文件。
  2. 将普通Redis服务器使用的代码替换成Sentinel专用代码。 启动Sentinel的第二个步骤就是将一部分普通Redis服务器使用的代码替换成Sentinel专用代码。比如说,普通Redis服务器使用redis.h/REDIS_SERVERPORT常量的值作为服务器端口: #define REDIS_SERVERPORT 6379 而Sentinel则使用sentinel.c/REDIS_SENTINEL_PORT常量的值作为服务器端口: #define REDIS_SENTINEL_PORT 26379 除此之外,普通Redis服务器使用redis.c/redisCommandTable作为服务器的命令表,而Sentinel则使用sentinel.c/sentinelcmds作为服务器的命令表
  3. 初始化Sentinel状态。 在应用了Sentinel的专用代码之后,接下来,服务器会初始化一个sentinel.c/sentinelState结构(后面简称“Sentinel状态”),这个结构保存了服务器中所有和Sentinel功能有关的状态。
struct sentinelState {// 当前纪元,用于实现故障转移uint64_t current_epoch;// 保存了所有被这个sentinel监视的主服务器dict *masters;// 是否进入了TILT模式?int tilt;// 目前正在执行的脚本的数量int running_scripts;// 进入TILT模式的时间mstime_t tilt_start_time;// 最后一次执行时间处理器的时间mstime_t previous_time;// 一个FIFO队列,包含了所有需要执行的用户脚本list *scripts_queue;
} sentinel;

4. 初始化Sentinel状态的masters属性

Sentinel状态中的masters字典记录了所有被Sentinel监视的主服务器的相关信息,其中: ·字典的键是被监视主服务器的名字。 ·而字典的值则是被监视主服务器对应的sentinel.c/sentinelRedisInstance结构

5. 创建连向主服务器的网络连接

对于每个被Sentinel监视的主服务器来说,Sentinel会创建两个连向主服务器的异步网络连接: ·一个是命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复。 ·另一个是订阅连接,这个连接专门用于订阅主服务器的__sentinel__:hello频道

3.2 获取主服务器信息

Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息。如图:主服务器master有三个从服务器slave0、slave1和slave2,并且一个Sentinel正在连接主服务器,那么Sentinel将持续地向主服务器发送INFO命令,并获得类似于以下内容的回复  Server run_id:7611c59dc3a29aa6fa0609f841bb6a1019008a9c # Replication role:master slave0:ip=127.0.0.1,port=11111,state=online,offset=43,lag=0 slave1:ip=127.0.0.1,port=22222,state=online,offset=43,lag=0 slave2:ip=127.0.0.1,port=33333,state=online,offset=43,lag=0 # Other sections 通过分析主服务器返回的INFO命令回复,Sentinel可以获取以下两方面的信息: ·一方面是关于主服务器本身的信息,包括run_id域记录的服务器运行ID,以及role域记录的服务器角色;

3.3获取从服务器信息

当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构之外,Sentinel还会创建连接到从服务器的命令连接和订阅连接。 在创建命令连接之后,Sentinel在默认情况下,会以每十秒一次的频率通过命令连接向从服务器发送INFO命令,并获得类似于以下内容的回复 # Server run_id:32be0699dd27b410f7c90dada3a6fab17f97899f # Replication role:slave master_host:127.0.0.1 master_port:6379 master_link_status:up slave_repl_offset:11887 slave_priority:100 # Other sections

3.4 向主服务器和从服务器发送信息

在默认情况下,Sentinel会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令: PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>" ·其中以s_开头的参数记录的是Sentinel本身的信息 ·而m_开头的参数记录的则是主服务器的信息

3.5 接收来自主服务器和从服务器的频道信息 

  • 当Sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接,向服务器发送以下命令: SUBSCRIBE __sentinel__:hello
  • Sentinel对__sentinel__:hello频道的订阅会一直持续到Sentinel与服务器的连接断开为止。 这也就是说,对于每个与Sentinel连接的服务器,Sentinel既通过命令连接向服务器的__sentinel__:hello频道发送信息,又通过订阅连接从服务器的__sentinel__:hello频道接收信息

  • 对于监视同一个服务器的多个Sentinel来说,一个Sentinel发送的信息会被其他Sentinel接收到,这些信息会被用于更新其他Sentinel对发送信息Sentinel的认知,也会被用于更新其他Sentinel对被监视服务器的认知。
  • 举个例子,假设现在有sentinel1、sentinel2、sentinel3三个Sentinel在监视同一个服务器,那么当sentinel1向服务器的__sentinel__:hello频道发送一条信息时,所有订阅了__sentinel__:hello频道的Sentinel(包括sentinel1自己在内)都会收到这条信息

3.6 接收来自主服务器和从服务器的频道信息 

当一个Sentinel从__sentinel__:hello频道收到一条信息时,Sentinel会对这条信息进行分析,提取出信息中的Sentinel IP地址、Sentinel端口号、Sentinel运行ID等八个参数,并进行以下检查:

  1. 如果信息中记录的Sentinel运行ID和接收信息的Sentinel的运行ID相同,那么说明这条信息是Sentinel自己发送的,Sentinel将丢弃这条信息,不做进一步处理。
  2. 相反地,如果信息中记录的Sentinel运行ID和接收信息的Sentinel的运行ID不相同,那么说明这条信息是监视同一个服务器的其他Sentinel发来的,接收信息的Sentinel将根据信息中的各个参数,对相应主服务器的实例结构进行更新。

3.7检测主、客观下线状态

在默认情况下,Sentinel会以每秒一次的频率向所有与它创建了命令连接的实例(包括主服务器、从服务器、其他Sentinel在内)发送PING命令,并通过实例返回的PING命令回复来判断实例是否在线。

带箭头的连线显示了Sentinel1和Sentinel2是如何向实例发送PING命令的:

·Sentinel1将向Sentinel2、主服务器master、从服务器slave1和slave2发送PING命令。 ·Sentinel2将向Sentinel1、主服务器master、从服务器slave1和slave2发送PING命令。

实例对PING命令的回复可以分为以下两种情况:

有效回复:实例返回+PONG、-LOADING、-MASTERDOWN三种回复的其中一种。

无效回复:实例返回除+PONG、-LOADING、-MASTERDOWN三种回复之外的其他回复,或者在指定时限内没有返回任何回复。

Sentinel配置文件中的down-after-milliseconds选项指定了Sentinel判断实例进入主观下线所需的时间长度:如果一个实例在down-after-milliseconds毫秒内,连续向Sentinel返回无效回复,那么Sentinel会修改这个实例所对应的实例结构,在结构的flags属性中打开SRI_S_DOWN标识,以此来表示这个实例已经进入主观下线状态。

当Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,它会向同样监视这一主服务器的其他Sentinel进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或者客观下线)。当Sentinel从其他Sentinel那里接收到足够数量的已下线判断之后,Sentinel就会将从服务器判定为客观下线,并对主服务器执行故障转移操作。 当认为主服务器已经进入下线状态的Sentinel的数量,超过Sentinel配置中设置的quorum参数的值,那么该Sentinel就会认为主服务器已经进入客观下线状态。

3.8选举领头Sentinel 

  • 当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个Sentinel会进行协商,选举出一个领头Sentinel,并由领头Sentinel对下线主服务器执行故障转移操作。(一旦发现主服务器下线了,一定要选举一个新的主服务器,谁去选择这个新的主服务器呢?  领头的 sentinel 去选择新的主服务器。 谁是领头的 sentinel呢? 看下边的的选举规则、
  • 以下是Redis选举领头Sentinel的规则和方法:
  • 所有在线的Sentinel都有被选为领头Sentinel的资格 ·每次进行领头Sentinel选举之后,不论选举是否成功,所有Sentinel的配置纪元(configuration epoch)的值都会自增一次
  • 在一个配置纪元里面,所有Sentinel都有一次将某个Sentinel设置为局部领头Sentinel的机会,并且局部领头一旦设置,在这个配置纪元里面就不能再更改。
  • 每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel。
  • 当一个Sentinel(源Sentinel)向另一个Sentinel(目标Sentinel)发送SENTINEL is-master-down-by-addr命令,并且命令中的runid参数不是*符号而是源Sentinel的运行ID时,这表示源Sentinel要求目标Sentinel将自己设置为局部领头Sentinel。Sentinel设置局部领头Sentinel的规则是先到先得:最先向目标Sentinel发送设置要求的源Sentinel将成为目标Sentinel的局部领头Sentinel,而之后接收到的所有设置要求都会被目标Sentinel拒绝。
  • 目标Sentinel在接收到SENTINEL is-master-down-by-addr命令之后,将向源Sentinel返回一条命令回复,回复中的leader_runid参数和leader_epoch参数分别记录了目标Sentinel的局部领头Sentinel的运行ID和配置纪元。
  • 源Sentinel在接收到目标Sentinel返回的命令回复之后,会检查回复中leader_epoch参数的值和自己的配置纪元是否相同,如果leader_runid参数的值和源Sentinel的运行ID一致,那么表示目标Sentinel将源Sentinel设置成了局部领头Sentinel。
  • 如果有某个Sentinel被半数以上的Sentinel设置成了局部领头Sentinel,那么这个Sentinel成为领头Sentinel。
  • 因为领头Sentinel的产生需要半数以上Sentinel的支持,并且每个Sentinel在每个配置纪元里面只能设置一次局部领头Sentinel,所以在一个配置纪元里面,只会出现一个领头Sentinel。 如果在给定时限内,没有一个Sentinel被选举为领头Sentinel,那么各个Sentinel将在一段时间之后再次进行选举,直到选出领头Sentinel为止。

3.9故障转移

在选举产生出领头Sentinel之后,领头Sentinel将对已下线的主服务器执行故障转移操作,该操作包含以下三个步骤:

  1. 在已下线主服务器属下的所有从服务器里面,挑选出一个从服务器,并将其转换为主服务器。
  2. 让已下线主服务器属下的所有从服务器改为复制新的主服务器。
  3. 将已下线主服务器设置为新的主服务器的从服务器,当这个旧的主服务器重新上线时,它就会成为新的主服务器的从服务器。

新的主服务器是怎样挑选出来的?

  1. 领头Sentinel会将已下线主服务器的所有从服务器保存到一个列表里面,然后按照以下规则,一项一项地对列表进行过滤:
  2. 删除列表中所有处于下线或者断线状态的从服务器,这可以保证列表中剩余的从服务器都是正常在线的。
  3. 删除列表中所有最近五秒内没有回复过领头Sentinel的INFO命令的从服务器,这可以保证列表中剩余的从服务器都是最近成功进行过通信的。
  4. 删除所有与已下线主服务器连接断开超过down-after-milliseconds*10毫秒的从服务器:down-after-milliseconds选项指定了判断主服务器下线所需的时间,而删除断开时长超过down-after-milliseconds*10毫秒的从服务器,则可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接,换句话说,列表中剩余的从服务器保存的数据都是比较新的。
  5. 之后,领头Sentinel将根据从服务器的优先级,对列表中剩余的从服务器进行排序,并选出其中优先级最高的从服务器。 如果有多个具有相同最高优先级的从服务器,那么领头Sentinel将按照从服务器的复制偏移量,对具有相同最高优先级的所有从服务器进行排序,并选出其中偏移量最大的从服务器(复制偏移量最大的从服务器就是保存着最新数据的从服务器)。 最后,如果有多个优先级最高、复制偏移量最大的从服务器,那么领头Sentinel将按照运行ID对这些从服务器进行排序,并选出其中运行ID最小的从服务器。