Redis之Sentinel
阅读原文时间:2023年07月11日阅读:2

  Redis的主从复制模式下,一旦主节点由于故障不能提供服务,需要人工将从节点晋升为主节点,同时还要通知应用方更新主节点地址,对于很多应用场景这种故障处理的方式是无法接受的。可喜的是Redis从 2.8开始正式提供了 Redis Sentinel (哨兵)架构来解决这个问题。

  由于对Redis的许多概念都有不同的名词解释,所以在介绍Redis Sentinel之前,先对几个名词进行说明,这样便于在后面的介绍中达成一致,如表9-1所示。

名词

逻辑结构

物理结构

主节点(master)

Redis主服务器/数据库

一个独立的Redis进程

从节点(slave)

Redis从服务器/数据库

一个独立的Redis进程

Redis数据节点

主节点和从节点

主节点和从节点的进程

Sentinel 节点

监控Redis数据节点

一个独立的Sentinel进程

Sentinel节点集合

若干Sentinel节点的抽象组合

若干Sentinel节点进程

Redis Sentinel

Redis高可用实现方案

Sentinel节点集合和Redis数据节点进程

应用方

泛指一个或多个客户端

一个或者多个客户端进程或者线程

  Redis Sentinel是 Redis的高可用实现方案,在实际的生产环境中,对提高整个系统的高可用性是非常有帮助的,本节首先会回顾主从复制模式下故障处理可能产生的问题,而后引出高可用的概念,最后重点分析Redis Sentinel的基本架构、优势,以及是如何实现高可用的。

1.1 主从复制的问题

  Redis的主从复制模式可以将主节点的数据改变同步给从节点,这样从节点就可以起到两个作用:第一,作为主节点的一个备份,一旦主节点出了故障不可达的情况,从节点可以作为后备“顶”上来,并且保证数据尽量不丢失(主从复制是最终一致性)。第二,从节点可以扩展主节点的读能力,一旦主节点不能支撑住大并发量的读操作,从节点可以在一定程度上帮助主节点分担读压力。

  但是主从复制也带来了以下问题:

□ 一旦主节点出现故障,需要手动将一个从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令其他从节点去复制新的主节点,整个过程都需要人工干预。

□ 主节点的写能力受到单机的限制。

□ 主节点的存储能力受到单机的限制。

  其中第一个问题就是Redis的高可用问题,将在下一个小节进行分析。第二、三个问题属于Redis的分布式问题,属于集群的范围,在集群里面介绍。

1.2 高可用

  Redis主从复制模式下 ,一旦主节点出现了故障不可达,需要人工干预进行故障转移,无论对于Redis的应用方还是运维方都带来了很大的不便。对于应用方来说无法及时感知到主节点的变化,必然会造成一定的写数据丢失和读数据错误,甚至可能造成应用方服务不可用。对于Redis的运维方来说,整个故障转移的过程是需要人工来介入的,故障转移实时性和准确性上都无法得到保障,图 9-1到图9-5展示了一个1主 2 从的Redis主从复制模式下的主节点出现故障后,是如何进行故障转移的,过程如下所示。

1) 如图9-1所示,主节点发生故障后,客户端(client)连接主节点失败,两个从节点与主节点连接失败造成复制中断。

2) 如图9-2所示,如果主节点无法正常启动,需要选出一个从节点(slave-1),对其执行 slaveof no one命令使其成为新的主节点。

3) 如图9-3所示,原来的从节点(slave-1)成为新的主节点后,更新应用方的主节点信息,重新启动应用方。

4) 如图9-4所示,客户端命令另一个从节点(slave-2)去复制新的主节点(new-master)。

5) 如图9-5所示,待原来的主节点恢复后,让它去复制新的主节。

  上述处理过程就可以认为整个服务或者架构的设计不是高可用的,因为整个故障转移的过程需要人介入。考虑到这点,有些公司把上述流程自动化了,但是仍然存在如下问题:第一,判断节点不可达的机制是否健全和标准。第二,如果有多个从节点,怎样保证只有一个被晋升为主节点。第三,通知客户端新的主节点机制是否足够健壮。Redis Sentinel正是用于解决这些问题。

1.3 Redis Sentinel的高可用性

  当主节点出现故障时,Redis Sentinel能自动完成故障发现和故障转移,并通知应用方,从而实现真正的高可用。

注意: Redis 2.6版本提供Redis Sentinel vl版本,但是功能性和健壮性都有一些问题,如果想使用Redis Sentinel的话,建议使用2.8以上版本,也就是v2 版本的Redis Sentinel。

  Redis Sentinel是一个分布式架构,其中包含若干个Sentinel节点和Redis数据节点,每个Sentinel节点会对数据节点和其余Sentinel节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它还会和其他Sentinel节点进行“协商”,当大多数Sentinel节点都认为主节点不可达时,它们会选举出一个Sentinel节点来完成自动故障转移的工作,同时会将这个变化实时通知给Redis应用方。整个过程完全是自动的,不需要人工来介入,所以这套方案很有效地解决了 Redis的高可用问题。

注意:这里的分布式是指:Redis数据节点、Sentinel节点集合、客户端分布在多个物理节点的架构,不要与Redis Cluster分布式混淆。

  如图9-6所示,Redis Sentinel与 Redis主从复制模式只是多了若干Sentinel节点,所以Redis Sentinel并没有针对Redis节点做了特殊处理,这里是很多开发和运维人员容易混淆的。

  从逻辑架构上看,Sentinel节点集合会定期对所有节点进行监控,特别是对主节点的故障实现自动转移。

  下面以1个主节点、2 个从节点、3 个 Sentinel节点组成的Redis Sentinel为例子进行说明,拓扑结构如图9-7所示。

  整个故障转移的处理逻辑有下面4 个步骤:

1) 如图9-8所示,主节点出现故障,此时两个从节点与主节点失去连接,主从复制失败。

2) 如图9-9所示,每个Sentinel节点通过定期监控发现主节点出现了故障。

3) 如图9-10所示,多个Sentinel节点对主节点的故障达成一致,选举出sentinel-3节点作为领导者负责故障转移。

4) 如图9-11所示,Sentinel领导者节点执行了故障转移,是自动化完成的。

5) 故障转移后整个Redis Sentinel的拓扑结构图9-12所示。

  通过上面介绍的Redis Sentinel逻辑架构以及故障转移的处理,可以看出Redis Sentinel具有以下几个功能:

□ 监控:Sentinel节点会定期检测Redis数据节点、其余Sentinel节点是否可达。

□ 通知:Sentinel节点会将故障转移的结果通知给应用方。

□ 主节点故障转移:实现从节点晋升为主节点并维护后续正确的主从关系。

□ 配置提供者:在 Redis Sentinel结构中,客户端在初始化的时候连接的是Sentinel节点集合,从中获取主节点信息。同时看到,Redis Sentinel包含了若个Sentinel节点,这样做也带来了两个好处:

□ 对于节点的故障判断是由多个Sentinel节点共同完成,这样可以有效地防止误判。

□ Sentinel节点集合是由若干个Sentinel节点组成的,这样即使个别Sentinel节点不可用,整个Sentinel节点集合依然是健壮的。

  但是Sentinel节点本身就是独立的Redis节点,只不过它们有一些特殊,它们不存储数据,只支持部分命令。下一节将完整介绍Redis Sentinel的部署过程,相信在安装和部署完Redis Sentinel后,读者能更清晰地了解Redis Sentinel的整体架构。

2.1 部署拓扑结构

  下面将以3个Sentinel节点、1个主节点、2 个从节点组成一个Redis Sentinel进行说明,拓扑结构如图9-13所示。

  具体的物理部署如表9-2所示

角色

ip

port

别名

master

127.0.0.1

6379

主节点或者6379节点

slave-1

127.0.0.1

6380

slave-1节点或者6380节点

slave-2

127.0.0.1

6381

slave-2节点或者6381节点

sentinel-1

127.0.0.1

26379

sentinel-1节点或者26379节点

sentinel-2

127.0.0.1

26380

sentinel-2节点或者26380节点

sentinel-3

127.0.0.1

26381

sentinel-3节点或者26381节点

2.2 部署Redis数据节点

  Redis Sentinel中Redis数据节点没有做任何特殊配置,按照之前章节介绍的方法启动就可以,下面以一个比较简单的配置进行说明。

1.启动主节点

  配置:

redis-6379.conf
port 6379
daemonize yes
logfile "6379.log"
dbfilename "dump-6379.rdb"
dir "/opt/soft/redis/data/"

  启动主节点:

redis-server redis-6379.conf

  确认是否启动。一般来说只需要ping命令检测一下就可以,确认Redis数据节点是否已经启动。

$ redis-cli -h 127.0.0.1 -p 6379 ping
PONG

2.启动两个从节点

  配置:

  两个从节点的配置是完全一样的,下面以一个从节点为例子进行说明,和主节点的配置不一样的是添加了slaveof配置。

redis-6380.conf
port 6380
daemonize yes
logfile "6380.log"
dbfilename "dump-6380.rdb"
dir " /op t/soft/red is/data/"
slaveof 127.0.0.1 6379

  启动两个从节点:

redis-server redis-6380.conf
redis-server redis-6381.conf

  验证:

$ redis-cli -h 127.0.0.1 -p 6380 ping
PONG
$ redis-cli -h 127.0.0.1 -p 6381 ping
PONG

3.确认主从关系

  主节点的视角,它有两个从节点,分别是127.0.0.1:6380和 127.0.0.1:6381:

$ redis-cli -h 127.0.0.1 -p 6379 info replication

Replication

role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380,state=online,offset=281,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=281,lag=0

  从节点的视角,它的主节点是127.0.0.1:6379:

$ redis-cli -h 127.0.0.1 -p 6380 info replication

Replication

role:slave
master_host:127.0.0.1
master_port:6379
master_link_status:up
…………………………….

  此时拓扑结构如图9-15所示。

2.3 部署Sentinel节点

  3个Sentinel节点的部署方法是完全一致的(端口不同),下面以sentinel-1节点的部署为例子进行说明。

1.配置 Sentinel 节点

redis-sentinel-26379.conf
port 26379
daemonize yes
logfile "26379.log"
dir /opt/soft/redis/data
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-time out mymaster 180000

1) Sentinel节点的默认端口是26379。

2) sentinel monitor mymaster 127.0.0.1 6379 2 配置代表sentinel-1 节点需要监控127.0.0.1:6379这个主节点,2代表判断主节点失败至少需要2个Sentine节点同意,mymaster是主节点的别名,其余Sentinel配置将在下一节进行详细说明。

2.启动 Sentinel节

  Sentinel节点的启动方法有两种:

  方法一,使用redis-sentinel命令:

redis-sentinel redis-sentinel-26379.conf

  方法二,使用redis-server命令加--sentinel参数:

redis-server redis-sentinel-26379.conf --sentinel

  两种方法本质上是一样的。

3.确认

  Sentinel节点本质上是一个特殊的Redis节点,所以也可以通过info命令来查询它的相关信息,从下面info的 Sentinel片段来看,Sentinel节点找到了主节点127.0.0.1:6379,发现了它的两个从节点,同时发现Redis Sentinel —共有3 个 Sentinel节点。这里只需要了解Sentinel节点能够彼此感知到对方,同时能够感知到Redis数据节点就可以了:

$ redis-cli -h 127.0.0.1 -p 26379 info Sentinel

Sentinel

sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster, status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

  当三个Sentinel节点都启动后,整个拓扑结构如图9-16所示。

  至此Redis Sentinel已经搭建起来了,整体上还是比较容易的,但是有2 点需要强调一下:

1) 生产环境中建议Redis Sentinel的所有节点应该分布在不同的物理机上。

2) Redis Sentinel中的数据节点和普通的Redis数据节点在配置上没有任何区别,只不过是添加了一些Sentinel节点对它们进行监控。

2.4 配置优化

  了解每个配置的含义有助于更加合理地使用Redis Sentinel, 因此本节将对每个配置的使用和优化进行详细介绍。Redis安装目录下有一个sentinel.conf, 是默认的Sentinel节点配置文件,下面就以它作为例子进行说明。

1.配置说明和优化

port 26379
dir /opt/soft/redis/data
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000
#sentinel auth-pass 〈 password 〉
#sentinel notification-script

sentinel client-reconfig-script

  port和dir分别代表Sentinel节点的端口和工作目录,下面重点对sentinel相关配置进行详细说明。

(1)sentinel monitor

配置如下:

sentinel monitor

Sentinel节点会定期监控主节点,所以从配置上必然也会有所体现,本配置说明Sentinel节点要监控的是一个名字叫做, ip 地址和端口为 的主节点。代表要判定主节点最终不可达所需要的票数。但实际上Sentinel节点会对所有节点进行监控,但是在Sentinel节点的配置中没有看到有关从节点和其余Sentinel节点的配置,那是因为Sentinel节点会从主节点中获取有关从节点以及其余Sentinel节点的相关信息,有关这部分是如何实现的,下一节会介绍。

例如某个Sentinel初始节点配置如下:

port 26379
daemonize yes
logfile "26379.log"
dir /opt/soft/redis/data
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down -after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

  当所有节点启动后,配置文件中的内容发生了变化,体现在三个方面:

□ Sentinel节点自动发现了从节点、其余Sentinel节点。

口 去掉了默认配置,例如 parallel-syncs、failover-timeout 参数。

□ 添加了配置纪元相关参数。

启动后变化为:

port 26379
daemonize yes
logfile "26379.log"
dir " /opt/soft/redis/data"
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel config-epoch mymaster 0
sentinel leader-epoch mymaster 0

发现两个slave节点

sentinel known-slave mymaster 127.0.0.1 6380
sentinel known-slave mymaster 127.0.0.1 6381

发现两个 sentinel 节点

sentinel known-sentinel mymaster 127.0.0.1 26380 282a70ff56c36ed56e8f7ee6ada74124140d6f53
sentinel known-sentinel mymaster 127.0.0.1 26381 f714470d30a61a8e39ae031192fifeae7eb5b2be
sentinel current-epoch 0

参数用于故障发现和判定,例如将quorum配置为2,代表至少有2 个Sentinel节点认为主节点不可达,那么这个不可达的判定才是客观的。对于设置的越小,那么达到下线的条件越宽松,反之越严格。 一般建议将其设置为Sentinel节点的一半加1。

同时 还与Sentinel节点的领导者选举有关,至少要有max (quorum,num(se ntinels)/2 + 1)个 Sentinel节点参与选举,才能选出领导者Sentinel, 从而完成故障转移。例如有5个sentinel节点, quorum=4;那么至少要有max(quorum,num(sentinels)/2 + 1)个在线Sentinel节点才可以进行领导者选举。

(2) sentinel down-after-milliseconds

配置如下:

sentinel down-after-milliseconds

每个Sentinel节点都要通过定期发送ping命令来判断Redis数据节点和其余Sentinel节点是否可达,如果超过了down-after-milliseconds 配置的时间且没有有效的回复,则判定节点不可达, (单位为毫秒)就是超时时间。这个配置是对节点失败判定的重要依据。

优化说明:down-after-milliseconds越大,代表Sentinel节点对于节点不可达的条件越宽松,反之越严格。条件宽松有可能带来的问题是节点确实不可达了,那么应用方需要等待故障转移的时间越长,也就意味着应用方故障时间可能越长。条件严格虽然可以及时发现故障完成故障转移,但是也存在一定的误判率。

down-after-milliseconds虽然以 为参数,但实际上对Sentinel 节点、主节点、从节点的失败判定同时有效。

(3) sentinel parallel-syncs

配置如下:

sentinel parallel-syncs

当Sentinel节点集合对主节点故障判定达成一致时,Sentinel领导者节点会做故障转移操作,选出新的主节点,原来的从节点会向新的主节点发起复制操作, parallel-syncs就是用来限制在一次故障转移之后,每次向新的主节点发起复制操作的从节点个数。如果这个参数配置的比较大,那么多个从节点会向新的主节点同时发起复制操作,尽管复制操作通常不会阻塞主节点,但是同时向主节点发起复制,必然会对主节点所在的机器造成一定的网络和磁盘IO开销。图 9-17 展示了parallel-syncs=3和parallel-syncs=l的效果, parallel-syncs=3 会同时发起复制, parallel-syncs=l 时从节点会轮询发起复制。

(4) sentinel failover-timeout

配置如下:

sentinel failover-timeout

failover-timeout 通常被解释成故障转移超时时间,但实际上它作用于故障转移的各个阶段:

a) 选出合适从节点。

b) 晋升选出的从节点为主节点。

c) 命令其余从节点复制新的主节点。

d) 等待原主节点恢复后命令它去复制新的主节点。

failover-timeout 的作用具体体现在四个方面 :

1) 如果 Redis Sentinel 对一个主节点故障转移失败,那么下次再对该主节点做故障转移的起始时间是 failover-timeout 的 2 倍。

2) 在b)阶段时,如果Sentinel节点向 a) 阶段选出来的从节点执行 slaveof no one一直失败(例如该从节点此时出现故障),当此过程超过 failover-timeout 时,则故障转移失败。

3) 在b)阶段如果执行成功, Sentinel 节点还会执行 info 命令来确认a)阶段选出来的节点确实晋升为主节点,如果此过程执行时间超过 failover-timeout 时,则故障转移失败。

4) 如果c)阶段执行时间超过了failover-timeout(不包含复制时间),则故障转移失败。注意即使超过了这个时间 , Sentinel 节点也会最终配置从节点去同步最新的主节点。

(5) sentinel auth-pass

配置如下:

sentinel auth-pass

如果Sentinel监控的主节点配置了密码,sentinel auth-pass配置通过添加主节点的密码,防止Sentinel节点对主节点无法监控。

(6) sentinel notification-script

配置如下:

sentinel notification-script

sentinel notification-script的作用是在故障转移期间,当一些警告级别的Sentinel事件发生(指重要事件,例如-sdown:客观下线、-odown:主观下线)时,会触发对应路径的脚本,并向脚本发送相应的事件参数。

例如在/opt/redis/scripts/下配置了 notification.sh,该脚本会接收每个Sentinel节点传过来的事件参数,可以利用这些参数作为邮件或者短信报警依据:

#! /bin/sh
#获取所有参数
msg=$*
#报警脚本或者接口,将 msg 作为参数
exit 0

如果需要该功能,就可以在Sentinel节点添加如下配置(=mymaster):

sentinel notification-script mymaster /opt/redis/scripts/notification.sh

例如下面就是某个Sentinel节点对主节点做了主观下线(有关主观下线的概念将在9.5节进行详细介绍)后脚本收到的参数:

+sdown master mymaster 127.0.0.1 6379

(7) sentinel client-reconfig-script

配置如下:

sentinel client-reconfig-script

sentinel client-reconfig-script的作用是在故障转移结束后,会触发对应路径的脚本,并向脚本发送故障转移结果的相关参数。和 notification-script类似,可以在 /opt/redis/scripts/下配置了 client-reconfig.sh,该脚本会接收每个 Sentinel节点传过来的故障转移结果参数,并触发类似短信和邮件报警。

#! /bin/sh

获取所有参数

msg=$*
#报警脚本或者接口,将 msg 作为参数
exit 0

如果需要该功能,就可以在Sentinel节点添加如下配置(=mymaster):

sentinel client-reconfig-script mymaster /opt/redis/scripts/client-reconfig.sh

故障转移结束,每个Sentinel节点会将故障转移的结果发送给对应的脚本,具体参数如下:

< to-port>

: 主节点名。

: Sentinel节点的角色,分别是leader和observer, leader代表当前Sentinel节点是领导者,是它进行的故障转移;observer是其余Sentinel节点。

: 原主节点的ip地址。

: 原主节点的端口。

: 新主节点的ip地址。

: 新主节点的端口。

例如以下内容分别是三个Sentinel节点发送给脚本的,其中一个是leader,另外两个是 observer:

mymaster leader start 127.0.0.1 6379 127.0.0.1 6380
mymaster observer start 127.0.0.1 6379 127.0.0.1 6380
mymaster observer start 127.0.0.1 6379 127.0.0.1 6380

有关sentinel notification -script和 sentinel client-reconfig-script有几点需要注意:

必须有可执行权限。

开头必须包含shell脚本头(例如#!/bin/sh),否则事件发生时Redis将无法执行脚本产生如下错误:

-script-error /opt/sentinel/ notification.sh 0 2

□ Redis规定脚本的最大执行时间不能超过60秒,超过后脚本将被杀掉。

□ 如果shell脚本以exit 1 结束,那么脚本稍后重试执行。如果以exit 2 或者更高的值结束,那么脚本不会重试。正常返回值是exit 0。

□ 如果需要运维的Redis Sentinel比较多,建议不要使用这种脚本的形式来进行通知,这样会增加部署的成本。

2.如何监控多个主节点

  Redis Sentinel可以同时监控多个主节点,具体拓扑图类似于图9-18。配置方法也比较简单,只需要指定多个masterName来区分不同的主节点即可,例如下面的酉己置监控 monitor master-business-1(10.10.xx.1:6379)和monitor master-business-2 (10.10.xx.2:6379)两个主节点:

sentinel monitor master-business-1 10.10.x x .1 6379 2
sentinel down-after-milliseconds master-business-1 60000
sentinel failover-timeout master-business-1 180000
sentinel parallel-syncs master-business-1 1
sentinel monitor master-business-2 10.16.x x .2 6380 2
sentinel down-after-milliseconds master-business-2 10000
sentinel failover-timeout master-business-2 180000
sentinel parallel-syncs master-business-2 1

3.调整配置

  和普通的Redis数据节点一样,Sentinel节点也支持动态地设置参数,而且和普通的Redis数据节点一样并不是支持所有的参数,具体使用方法如下:

sentinel set

表9-3是 sentinel set命令支持的参数

参数

使用方法

quorum

sentinel set mymaster quorum 2

down-after-milliseconds

sentinel set mymaster down-after-milliseconds 30000

failover-timeout

sentinel set mymaster failover-timeout 360000

parallel-syncs

sentinel set mymaster parallel-syncs 2

notification-script

sentinel set mymaster notification -script /opt/xx.sh

client-reconfig-script

sentinel set mymaster client-reconfig -script /opt/yy.sh

auth-pass

sentinel set mymaster auth-pass masterPassword

  有几点需要注意一下:

1) sentinel set 命令只对当前Sentinel节点有效。

2) sentinel set 命令如果执行成功会立即刷新配置文件,这点和Redis普通数据节点设置配置需要执行config rewrite刷新到配置文件不同。

3) 建议所有Sentinel节点的配置尽可能一致,这样在故障发现和转移时比较容易达成一致。

4) 表9-3中为sentinel set支持的参数,具体可以参考源码中的sentinel.c的sentinelSetCommand 函数。

5) Sentinel对外不支持config命令。

2.5 部署技巧

  到现在有关Redis Sentinel的配置和部署方法相信读者已经基本掌握了,但在实际生产环境中都有哪些部署的技巧?本节将总结一下。

1) Sentinel 节点不应该部署在一台物理“机器”上。

这里特意强调物理机是因为一台物理机做成了若干虚拟机或者现今比较流行的容器,它们虽然有不同的IP地址,但实际上它们都是同一台物理机,同一台物理机意味着如果这台机器有什么硬件故障,所有的虚拟机都会受到影响,为了实现 Sentinel 节点集合真正的高可用,请勿将 Sentinel 节点部署在同一台物理机器上。

2) 部署至少三个且奇数个的 Sentinel 节点。

3个以上是通过增加 Sentinel 节点的个数提高对于故障判定的准确性,因为领导者选举需要至少一半加1个节点,奇数个节点可以在满足该条件的基础上节省一个节点。有关Sentinel 节点如何判断节点失败,如何选举出一个 Sentinel 节点进行故障转移将在 9.5 节进行介绍。

3) 只有一套 Sentinel, 还是每个主节点配置一套 Sentinel?

Sentinel 节点集合可以只监控一个主节点,也可以监控多个主节点,也就意味着部署拓扑可能是图 9-19 和图 9-20 两种情况。

那么在实际生产环境中更偏向于哪一种部署方式呢,下面分别分析两种方案的优缺点。

方案一: 一套Sentinel,很明显这种方案在一定程度上降低了维护成本,因为只需要维护固定个数的Sentinel 节点,集中对多个 Redis 数据节点进行管理就可以了。但是这同时也是它的缺点,如果这套 Sentinel 节点集合出现异常,可能会对多个 Redis 数据节点造成影响。还有如果监控的 Redis 数据节点较多,会造成 Sentinel 节点产生过多的网络连接,也会有一定的影响。

方案二:多套 Sentinel, 显然这种方案的优点和缺点和上面是相反的,每个 Redis 主节点都有自己的 Sentinel 节点集合,会造成资源浪费。但是优点也很明显,每套 Redis Sentinel都是彼此隔离的。

如果Sentinel节点集合监控的是同一个业务的多个主节点集合,那么使用方案一、否则一般建议采用方案二。

  Sentinel节点是一个特殊的Redis节点,它有自己专属的API, 本节将对其进行介绍。为了方便演示,以图9-21进行说明:Sentinel节点集合监控着两组主从模式的Redis数据节点。

1.sentinel masters

  展示所有被监控的主节点状态以及相关的统计信息,例如:

127.0.0.1:26379> sentinel masters
1) 1) "name"
2) "mymaster-2"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6382"
………. 忽 略 ……………
2) 1) "name"
2) "mymaster-1"
3) "ip "
4) "127.0.0.1"
5) "port"
6) "6379"
………. 忽略 ……………

2.sentinel master 〈master name>

  展示指定的主节点状态以及相关的统计信息,例如:

127.0.0.1:26379> sentinel master mymaster-1
1) "name"
2) "mymaster-1"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6379"
……. 忽略 ………..

3.sentinel slaves

  展示指定〈master name> 的从节点状态以及相关的统计信息,例如:

127.0.0.1:26379> sentinel slaves mymaster-1
1) 1) "name"
2) "127.0.0.1:6380"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6380"
……… 忽略 …………..
2) 1) "name"
2) "127.0.0.1:6381"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "6381"
……… 忽略 …………..

4.sentinel sentinels

  展示指定 的 Sentinel节点集合(不包含当前Sentinel节点),例如:

127.0.0.1:26379> sentinel sentinels mymaster-1
1) 1) "name"
2) "127.0.0.1:26380"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "26380"
……..忽略………..
2) 1) "name"
2) "127.0.0.1:26381"
3) "ip"
4) "127.0.0.1"
5) "port"
6) "26381"

5.sentinel get-master-addr-by-name

  返回指定 主节点的IP地址和端口,例如:

127.0.0.1:26379> sentinel get-master-addr-by-name mymaster-1
1) "127.0.0.1"
2) "6379"

6.sentinel reset

  当前Sentinel节点对符合(通配符风格)主节点的配置进行重置,包含清除主节点的相关状态(例如故障转移),重新发现从节点和Sentinel节点。

  例如sentinel- 1节点对mymaster-1节点重置状态如下:

127.0.0.1:26379> sentinel reset mymaster-1
(integer) 1

7.sentinel failover

  对指定主节点进行强制故障转移(没有和其他Sentine节点“协商”),当故障转移完成后,其他Sentinel节点按照故障转移的结果更新自身配置,这个命令在Redis Sentinel的日常运维中非常有用,将在9.6节进行详细介绍。

  例如,对mymaster-2进行故障转移:

127.0.0.1:26379> sentinel failover mymaster-2
OK

  执行命令前,mymaster-2 是127.0.0.1:6382

127.0.0.1:26379> info sentinel

Sentinel

sentinel_masters:2
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster-2,status=ok,address=127.0.0.1:6382,slaves=2,sentinels=3
master1:name=mymaster-1,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

  执行命令后:mymaster-2 由原来的一个从节点127.0.0.1:6383代替。

127.0.0.1:26379> info sentinel

Sentinel

sentinel_masters:2
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster-2,status=ok,address=127.0.0.1:6383,slaves=2,sentinels=3
master1:name=mymaster-1,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

8.sentinel ckquorum 〈master name>

  检测当前可达的 Sentinel 节点总数是否达到 的个数。例如quorum=3 , 当前可达的Sentinel节点个数为2个,那么将无法进行故障转移,Redis Sentinel 的高可用特性也将失去。

  例如:

127.0.0.1:26379> sentinel ckquorum mymaster-1
OK 3 usable Sentinels. Quorum and failover authorization can be reached

9.sentinel flushconfig

  将Sentinel节点的配置强制刷到磁盘上,这个命令Sentinel节点自身用得比较多,对于开发和运维人员只有当外部原因(例如磁盘损坏)造成配置文件损坏或者丢失时,这个命令是很有用的。

  例如:

127.0.0.1:26379> sentinel flushconfig
OK

10.sentinel remove

  取消当前Sentinel节点对于指定〈master name> 主节点的监控。

  例如sentinel-1 当前对mymaster-1进行了监控:

127.0.0.1:26379> info sentinel

Sentinel

sentinel_masters:2
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster-2,status=ok,address=127.0.0.1:6382,slaves=2,sentinels=3
master1:name=mymaster-1,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

  例如下面,sentinel-1节点取消对mymaster-1节点的监控,但是要注意这个命令仅仅对当前Sentinel节点有效。

127.0.0.1:26379> sentinel remove mymaster-1
OK

  再执行info sentinel命令,发现sentinel-1已经失去对mymaster-1的监控:

127.0.0.1:26379> info sentinel

Sentinel

sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster-2,status=ok,address=127.0.0.1:6383,slaves=2,sentinels=3

11.sentinel monitor

  这个命令和配置文件中的含义是完全一样的,只不过是通过命令的形式来完成Sentinel节点对主节点的监控。

  例如命令sentinel-1节点重新监控mymaster-1节点:

127.0.0.1:26379> sentinel monitor mymaster-1 127.0.0.1 6379 2
OK

  命令执行后,发现sentinel-1节点重新对mymaster-1节点进行监控:

# Sentinel
sentinel_masters:2
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=mymaster-2,status=ok,address=127.0.0.1:6383,slaves=2,sentinels=3
master1:name=mymaster-1,status=ok,address=127.0.0.1:6379,slaves=2,sentinels=3

12.sentinel set 〈master name>

  动态修改Sentinel节点配置选项,这个命令已经之前进行了说明,这里就不赘述了。

13.sentinel is-master-down-by-addr

  Sentinel节点之间用来交换对主节点是否下线的判断,根据参数的不同,还可以作为Sentinel领导者选举的通信方式,具体细节9.5节会介绍。

  本节将介绍应用方如何正确地连接Redis Sentinel。有人会说这有什么难的,已经知道了主节点的ip地址和端口,用对应编程语言的客户端连接主节点不就可以了吗?但试想一下,如果这样使用客户端,客户端连接Redis Sentinel和主从复制的Redis又有什么区别呢,如果主节点挂掉了,虽然Redis Sentinel可以完成故障转移,但是客户端无法获取这个变化,那么使用Redis Sentinel的意义就不大了,所以各个语言的客户端需要对Redis Sentinel进行显式的支持。

4.1 Redis Sentinel的客户端

  Sentinel节点集合具备了监控、通知、自动故障转移、配置提供者若干功能,也就是说实际上最了解主节点信息的就是Sentinel节点集合,而各个主节点可以通过进行标识的,所以,无论是哪种编程语言的客户端,如果需要正确地连接Redis Sentinel, 必须有Sentinel节点集合和masterName两个参数。

4.2 Redis Sentinel客户端基本实现原理

  实现一个Redis Sentinel客户端的基本步骤如下:

1) 遍历Sentinel节点集合获取一个可用的Sentinel节点,后面会介绍Sentinel节点之间可以共享数据,所以从任意一个Sentinel节点获取主节点信息都是可以的,如图9-22所示。

2) 通过 sentinel get-master-addr_by-name master-name 这个API来获取对应主节点的相关信息,如图9-23所示。

3) 验证当前获取的“主节点”是真正的主节点,这样做的目的是为了防止故障转移期间主节点的变化,如图9-24所示。

  4) 保持和Sentinel节点集合的“联系”,时刻获取关于主节点的相关“信息”,如图9-25所示。

从上面的模型可以看出,Redis Sentinel客户端只有在初始化和切换主节点时需要和Sentinel节点集合进行交互来获取主节点信息,所以在设计客户端时需要将Sentinel节点集合考虑成配置(相关节点信息和变化)发现服务。

上述过程只是从客户端设计的角度进行分析,在开发客户端时要考虑的细节还有很多,但是这些问题并不需要深究。至于一些客户端怎么连接Sentinel,属于研发范畴,这里不做讲解,可以上对应开发语言官网查找相对应的文档。

  本节将介绍Redis Sentinel的基本实现原理,具体包含以下几个方面:Redis Sentinel的三个定时任务、主观下线和客观下线、Sentinel领导者选举、故障转移,相信通过本节的学习读者能对Redis Sentinel的高可用特性有更加深入的理解和认识。

5.1 三个定时监控任务

  一套合理的监控机制是Sentinel节点判定节点不可达的重要保证,Redis Sentinel通过三个定时监控任务完成对各个节点发现和监控:

1) 每隔10秒,每个Sentinel节点会向主节点和从节点发送info命令获取最新的拓扑结构,如图9-26所示。

例如下面就是在一个主节点上执行info replication的结果片段:

# Replication
role:master
connected_slaves:2
slave0:ip=127.0.0.1,port=6380;state=online,offset=4917,lag=1
slave1:ip=127.0.0.1,port=6381,state=online,offset=4917,lag=1

Sentinel节点通过对上述结果进行解析就可以找到相应的从节点。

这个定时任务的作用具体可以表现在三个方面:

□ 通过向主节点执行info 命令,获取从节点的信息,这也是为什么Sentinel节点不需要显式配置监控从节点。

□ 当有新的从节点加人时都可以立刻感知出来。

□ 节点不可达或者故障转移后,可以通过info命令实时更新节点拓扑信息。

2) 每隔2秒,每个Sentinel节点会向Redis 数据节点的_sentinel_:hello 频道上发送该Sentinel节点对于主节点的判断以及当前Sentinel节点的信息(如图9-27所示),同时每个Sentinel节点也会订阅该频道,来了解其他Sentinel节点以及它们对主节点的判断,所以这个定时任务可以完成以下两个工作:

□ 发现新的Sentinel节点:通过订阅主节点的_sentinel_:hello了解其他的Sentinel节点信息,如果是新加入的Sentinel节点,将该Sentinel节点信息保存起来,并与该Sentinel节点创建连接。

□ Sentinel节点之间交换主节点的状态,作为后面客观下线以及领导者选举的依据。Sentinel节点publish的消息格式如下:


<主节点名字> < 主节点 lp> <主节点端口> < 主节点配置版本>

3) 每隔1秒,每个Sentinel节点会向主节点、从节点、其余Sentinel节点发送一条ping命令做一次心跳检测,来确认这些节点当前是否可达。如图9-28所示。通过上面的定时任务,Sentinel节点对主节点、从节点、其余Sentinel节点都建立起连接,实现了对每个节点的监控,这个定时任务是节点失败判定的重要依据。

5.2 主观下线和客观下线

1.主观下线

  上一小节介绍的第三个定时任务,每个Sentinel节点会每隔1秒对主节点、从节点、其他Sentinel节点发送ping命令做心跳检测,当这些节点超过down-after-milliseconds没有进行有效回复,Sentinel节点就会对该节点做失败判定,这个行为叫做主观下线。从字面意思也可以很容易看出主观下线是当前Sentinel节点的一家之言,存在误判的可能,如图9-29所示。

2.客观下线

  当Sentinel主观下线的节点是主节点时,该 Sentinel节点会通过sentinel is-master-down-by-addr命令向其他Sentinel节点询问对主节点的判断,当超过 个数,Sentinel节点认为主节点确实有问题,这时该Sentinel节点会做出客观下线的决定,这样客观下线的含义是比较明显了,也就是大部分Sentinel节点都对主节点的下线做了同意的判定,那么这个判定就是客观的,如图9-30所示。

  注意: 节点、Sentinel节点在主观下线后,没有后续的故障转移操作。

  这里有必要对sentinel is-master-down-by-addr命令做一个介绍,它的使用方法如下:

  sentinel is-master-down-by-addr

□ ip: 主节点IP。

□ port: 主节点端口。

□ current epoch: 当前配置纪元。

□ runid: 此参数有两种类型,不同类型决定了此API作用的不同。

  当runid等于"*"时,作用是Sentinel节点直接交换对主节点下线的判定。

  当runid等于当前Sentinel节点的runid时,作用是当前Sentinel节点希望目标Sentinel节点同意自己成为领导者的请求,有关Sentinel领导者选举,后面会进行介绍。

  例如sentinel-1节点对主节点做主观下线后,会向其余Sentinel节点(假设sentinel-2和sentinel-3节点)发送该命令:

sentinel is-master-down-by-addr 127.0.0.1 6379 0 *

  返回结果包含三个参数,如下所示:

□ down_State: 目标Sentinel节点对于主节点的下线判断,1是下线,0是在线。

□ leader_runid: 当leader_runid等于时,代表返回结果是用来做主节点是否不可达,当 leader_runid等于具体的runid,代表目标节点同意runid成为领导者。

□ leader_epoch: 领导者纪元。

5.3 领导者 Sentinel节点选举

  假如Sentinel节点对于主节点已经做了客观下线,那么是不是就可以立即进行故障转移了?当然不是,实际上故障转移的工作只需要一个Sentinel节点来完成即可,所以Sentinel节点之间会做一个领导者选举的工作,选出一个Sentinel节点作为领导者进行故障转移的工作。Redis使用了 Raft算法实现领导者选举,因为Raft算法相对比较抽象和复杂,以及篇幅所限,所以这里给出一个Redis Sentinel进行领导者选举的大致思路:

1) 每个在线的Sentinel节点都有资格成为领导者,当它确认主节点主观下线时候,会向其他Sentinel节点发送sentinel is-master-down-by-addr命令,要求将自己设置为领导者。

2) 收到命令的Sentinel节点,如果没有同意过其他Sentinel节点的sentinel is-master-down-by-addr 命令, 将同意该请求 ,否则拒绝。

3) 如果该Sentinel节点发现自己的票数已经大于等于max(quorum, num(sentinels)/2+ 1),那么它将成为领导者。

4) 如果此过程没有选举出领导者,将进入下一次选举。

  图9-31展示了一次领导者选举的大致过程:

1) s1(sentinel-1)最先完成了客观下线,它会向s2(sentinel-2)和s3(sentinel-3)发送sentinel is-master-down-by-addr命令,s2和s3同意选其为领导者。

2) s1此时已经拿到2张投票,满足了大于等于max(quorum, num(sentinels)/2+ 1)=2的条件,所以此时s1成为领导者。

  由于每个Sentinel节点只有一票,所以当s2向s1和S3索要投票时,只能获取一票,而S3由于最后完成主观下线,当S3向s1和s2索要投票时一票都得不到,整个过程如图9-32和 9-33所示。

  实际上Redis Sentinel实现会更简单一些,因为一旦有一个"Sentinel节点获得了max (quorum,num(sentinels)/2 + 1 ) 的票数,其他Sentinel节点再去确认已经没有意义了,因为每个Sentinel 节点只有一票,如果读者有兴趣的话,可以修改sentinel.c源码,在 Sentinel 的执行命令列表中添加monitor命令:

struct redisCoirunand sentinelcmds [] = {
{"monitor",monitorCommand,1 , " " ,0,NULL,0 ,0 ,0 ,0 ,0 } ,
{"ping",pingCommand,1 , " " , 0 ,NULL,0 ,0 ,0 ,0 ,0 },
{"sentinel", sentinelCommand,-2, " " , 0,NULL,0 ,0 ,0 ,0 ,0 },
…..
}

  重新编译部署 Redis Sentinel 测试环境,在3个Sentinel节点上执行monitor命令:

1) 可以看到sentinel is-master-down-by-addr命令,此命令的执行过程并没有在Redis的日志中有所体现,monitor监控类似如下命令:

// 因为最后参数是所以此时是Sentinel节点之间交换对主节点的失败判定
[0 127.0.0.1:38440] "SENTINEL" "is-master-down-by-addr" "127.0.0.1" "6379" "0" "*"
// 因为最后参数是具体的runid , 所以此时代表runid="2f4430bb62c039fbl25c5771d7cde2571a7a5ab4" 的节点希望目标 Sentinel节点同意自己成为领导者。
[0 127.0.0.1:38440] "SENTINEL" "is-master-down-by-addr" "127.0.0.1" "6379" "1" "2f4430bb62c039fbl25c5771d7cde2571a7a5ab4"

2) 选举的过程非常快,基本上谁先完成客观下线,谁就是领导者。

3) —旦Sentinel得到足够的票数,不存在图9-32和图9-33的过程。

5.4 故障转移

  领导者选举出的 Sentinel 节点负责故障转移,具体步骤如下:

1) 在从节点列表中选出一个节点作为新的主节点,选择方法如下:

a) 过滤:“不健康”(主观下线、断线)、5秒内没有回复过 Sentinel节点 ping响应、与主节点失联超过down-after-milliseconds*10秒。

b) 选择slave-priority(从节点优先级)最高的从节点列表,如果存在则返回,不存在则继续。

c) 选择复制偏移量最大的从节点(复制的最完整),如果存在则返回,不存在则继续。

d) 选择runid最小的从节点。

  整个过程如图 9-34 所示。

2) Sentinel领导者节点会对第一步选出来的从节点执行slaveof no one命令让其成为主节点。

3) Sentine领导者节点会向剩余的从节点发送命令,让它们成为新主节点的从节点,复制规则和Parallel-syncs参数有关。

4) Sentinel 节点集合会将原来的主节点更新为从节点,并保持着对其关注,当其恢复后命令它去复制新的主节点。

  本节首先分析 Redis Sentinel 故障转移的日志,读懂日志是运维中的重要方法,接下来将介绍 Redis Sentinel 中节点的常见运维方法,最后介绍如何借助 Redis Sentinel 实现读写分离。

6.1 故障转移日志分析

1.Redis Sentinel 拓扑结构

  本次故障转移的分析直接使用9.2的拓扑和配置进行说明,为了方便分析故障转移的过程,表 9-4列出了每个节点的角色、ip、端口、进程号、runId。

表9-4 Redis Sentinel拓扑表

序号

角色

ip

端口

进程号

runId

1

master

127.0.0.1

6379

19661

d5671ff4160ab3782d61079ebd62ff629aaaf605

2

slave-1

127.0.0.1

6380

19667

a683630c3ebd60106a938287cb5bc310f9da2d58

3

slave-2

127.0.0.1

6381

19685

ee31e5150ed8acf5f58b1deflb4d0086f7d71fl2

4

sentinel-1

127.0.0.1

26379

19697

94dde2f5426ed7ae6125a74da76ca5aac31edb8b

5

sentinel-2

127.0.0.1

26380

19707

b8d15be5e55501513aec6c388e58c50198870134

6

sentinel-3

127.0.0.1

26381

19713

7044753f564e42b1578341acf4c49dca3681151c

  因为故障转移涉及节点关系的变化,所以下面说明中用端口号代表节点。

2.开始故障转移测试

  模拟故障的方法有很多,比较典型的方法有以下几种:

□ 方法一,强制杀掉对应节点的进程号,这样可以模拟出宕机的效果。

□ 方法二,使用Redis的debug sleep命令,让节点进人睡眠状态,这样可以模阻塞的效果。

□ 方法三,使用Redis的shutdown命令,模拟正常的停掉 Redis。

  本次我们使用方法一进行测试,因为从实际经验来看,数百上千台机器偶尔宕机一两台是会不定期出现的,为了方便分析日志行为,这里记录一下操作的时间和命令。

  用kill -9使主节点的进程宕机,操作时间2016-07-24 09:40:35:

$ kill -9 19661

3.观察效果

  6380节点晋升为主节点,6381节点成为6380节点的从节点。

4.故障转移分析

  相信故障转移的效果和预想的一样,这里重点分析相应节点的日志。

  (1) 6379节点日志

  两个复制请求,分别来自端口为6380和6381的从节点:

19661:M 24 Jul 09:22:16.907 * Slave 127.0.0.1:6380 asks for synchronization
19661:M 24 Jul 09:22:16.907 * Full resync requested by slave 127.0.0.1:6380
…..
19661:M 24 Jul 09:22:16.919 * Synchronization with slave 127.0.0.1:6380 succeeded
19661:M 24 Jul 09:22:23.396 * Slave 127.0.0.1:6381 asks for synchronization
19661:M 24 Jul 09:22:23.396 * Full resync requested by slave 127.0.0.1:6381
…..
19661:M 24 Jul 09:22:23.432 * Synchronization with slave 127.0.0.1:6381 succeeded

09:40:35做了kill -9操作,由于模拟的是宕机效果,所以6379节点没有看到任何日志(这点和shutdown操作不太相同)。

(2) 6380节点日志

6380节点在09:40:35之后发现它与6379节点已经失联:

19667:S 24 Jul 09:40:35.788 # Connection with master lost.
19667:S 24 Jul 09:40:35.788 * Caching the disconnected master state .
19667:S 24 Jul 09:40:35.974 * Connecting to MASTER 127.0.0.1:6379
19667:S 24 Jul 09:40:35.974 * MASTER <-> SLAVE sync started
19667:S 24 Jul 09:40:35.975 # Error condition on socket for SYNC : Connection refused
….

09:41:06 时它接到 Sentinel节点的命令:清理原来缓存的主节点状态,Sentinel 节点将6380 节点晋升为主节点,并重写配置:

19667:M 24 Jul 09:41:06.161 * Discarding previously cached master state.
19667:M 24 Jul 09:41:06.161 * MASTER MODE enabled (user request from ' id=7
addr=127.0.0.1:46759 fd=10 name = sentinel-7044753f-cmd age=1111 idle=0
flags=x db=0 sub=0 psub=0 multi=3 qbuf=0 qbuf-free=32768 obl=36 oll=0
omem=0 events=rw cmd=exec')
19667:M 24 Jul 09:41:06.161 # CONFIG REWRITE executed with success.

6381节点发来了复制请求:

19667:M 24 Jul 09:41:07.499 * Slave 127.0.0.1:6381 asks for synchronization
19667:M 24 Jul 09:41:07.499 * Full resync requested by slave 127.0.0.1:6381

19667:M 24 Jul 09:41:07.548 * Background saving terminated with success
19667:M 24 Jul 09:41:07.548 * Synchronization with slave 127.0.0.1:6381 succeeded

(3) 6381节点日志

6381节点同样与6379节点失联:

19685:S 24 Jul 09:40:35.788 * Connection with master lost .
19685:S 24 Jul 09:40:35.788 * Caching the disconnected master state .
19685:S 24 Jul 09:40:36.425 * Connecting to MASTER 127.0.0.1:6379
19685:S 24 Jul 09:40:36.425 * MASTER <-> SLAVE sync started
19685:S 24 Jul 09:40:36.425 #Error condition on socket for SYNC:Connection refused

后续操作如下:

1) 09:41:06时它接到Sentinel节点的命令,清理原来缓存的主节点状态,让它去复制新的主节点(6380节点):

19685:S 24 Jul 09:41:06.497 # Error condition on socket for SYNC: Connection refused
19685:S 24 Jul 09:41:07.008 * Discarding previously cached master state.
19685:S 24 Jul 09:41:07.008 * SLAVE OF 127.0.0.1:6380 enabled (user request
from ' id=7 addr=127.0.0.1:55872 fd=10 name=sentinel-7044753f-cmd age=1111
idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=133 qbuf-free=32635 obl=36
oll=0 omem=0 events=rw cmd=exec')
19685:S 24 Jul 09:41:07.008 # CONFIG REWRITE executed with success.

2) 向新的主节点(6380节点)发起复制操作:

19685:S 24 Jul 09:41:07.498 * Connecting to MASTER 127.0.0.1:6380
19685:S 24 Jul 09:41:07.549 * MASTER <-> SLAVE sync: Finished with success

(4) sentinel-1 节点日志

09:41:05对6379节点作了主观下线(+sdown),注意这个时间正好是kill -9后的30秒,和down-after -milliseconds的配置是一致的。Sentinel节点更新自己的配置纪元(new-epoch):

19697:X 24 Jul 09:41:05.850 # +sdown master mymaster 127.0.0.1 6379
19697:X 24 Jul 09:41:05.928 # +new-epoch 1

后续操作如下:

1) 投票给sentinel-3节点:

19697:X 24 Jul 09:41:05.929 # +vote-for-leader 7044753f564e42bl578341acf4c49dca3681151c 1
19697:X 24 Jul 09:41:06.913 # -odown master mymaster 127.0.0.1 6379 #quorum 3/2

2) 更新状态: 从sentinel-3 节点(领导者)得知:故障转移后6380节点变为主节点,并发现了两个从节点6381和 6379,并在30秒后对(09:41:07 ~ 09:41:37) 6379节点做了主观下线:

19697:X 24 Jul 09:41:06.913 # Next failover delay: I will not start a failover before Sun Jul 24 09:47:06 2016
19697:X 24 Jul 09:41:07.008 # +config-update-from sentinel 127.0.0.1:26381 127.0.0.1 26381 @ mymaster 127.0.0.1 6379
19697:X 24 Jul 09:41:07.008 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6380
19697:X 24 Jul 09:41:07.008 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 mymaster 127.0.0.1 6380
19697:X 24 Jul 09:41:07.008 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 mymaster 127.0.0.1 6380
19697:X 24 Jul 09:41:37.060 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 mymaster 127.0.0.1 6380

(5) sentinel-2 节点日志

整个过程和sentinel-1节点是一样的,这里就不占用篇幅分析了。

(6) sentinel-3 节点日志

从 sentinel-1节点和sentinel-2 节点的日志来看,sentinel-3节点是领导者,所以分析sentinel-3 节点的日志至关重要。

后续操作如下。

1) 达到了客观下线的条件:

19713:X 24 Jul 09:41:05.854 # +sdown master mymaster 127.0.0.1 6379
19713:X 24 Jul 09:41:05.909 # +odown master mymaster 127.0.0.1 6379 #quorum 2/2
19713:X 24 Jul 09:41:05.909 # +new-epoch 1

2) sentinel-3节点被选为领导者:

19713:X 24 Jul 09:41:05.909 # +try-failover master mymaster 127.0.0.1 6379
19713:X 24 Jul 09:41:05.911 # + vote-for-leader 7044753f564e42bl578341acf4c49dca3681151c 1
19713:X 24 Jul 09:41:05.929 # 127.0.0.1:26379 voted for 7044753f564e42bl578341acf4c49dca3681151c 1
19713:X 24 Jul 09:41:05.930 # 127.0.0.1:26380 voted for 7044753f564e42bl578341acf4c49dca3681151c 1
19713:X 24 Jul 09:41:06.001 # + elected -leader master mymaster 127.0.0.1 6379

  表9-5展示了3个Sentinel节点完成客观下线的时间点,从时间点可以看到sentinel-3节点最先完成客观下线。

表9-5 3个Sentinel节点客观下线时间

节点

下线时间

Sentinel-1

09:41:06.913 # +odown master mymaster 127.0.0.1 6379 #quorum 3/2

Sentinel-2

09:41:05.943 # +odown master mymaster 127.0.0.1 6379 #quorum 3/2

Sentinel-3

09:41:05.909 # +odown master mymaster 127.0.0.1 6379 #quorum 2/2

3) 故障转移。每一步都可以通过发布订阅来获取,对于每个字段的说明可以参考表9-6。寻找合适的从节点作为新的主节点:

19713:X 24 Jul 09:41:06.001 # +failover-state-select-slave master mymaster 127.0.0.1 6379

选出了合适的从节点(6380节点):

19713:X 24 Jul 09:41:06.077 # + selected-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379

命令6380节点执行slaveof no one, 使其成为主节点:

19713:X 24 Jul 09:41:06.077 * + failover-state-send-slaveof-noone slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379

等待6380节点晋升为主节点:

19713:X 24 Jul 09 :41:06.161 * + failover-state-wait-promotion slave 127.0. 0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379

确认6380节点已经晋升为主节点:

19713:X 24 Jul 09:41:06.927 # +promoted-slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379

故障转移进入重新配置从节点阶段:

19713:X 24 Jul 09:41:06.927 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379

命令6381节点复制新的主节点:

19713:X 24 Jul 09:41:07.008 * + slave-reconf-sent slave "127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379

6381节点正在重新配置成为6380节点的从节点,但是同步过程尚未完成:

19713:X 24 Jul 09:41:07.955 * + slave-reconf-inprog slave 127.0 .0.1:6381
127.0.0.1 6381 @ mymaster 127.0.0.1 6379

6381节点完成对6380节点的同步:

19713:X 24 Jul 09:41:07.955 * +slave-reconf-done slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379

故障转移顺利完成:

19713:X 24 Jul 09:41:08.045 # + failover-end master mymaster 127.0.0.1 6379

故障转移成功后,发布主节点的切换消息:

19713:X 24 Jul 09:41:08.045 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6380

表9-6记录了 Redis Sentinel在故障转移一些重要的事件消息对应的频道。

表9-6Sentinel节点发布订阅频道

状态

说明

+reset-master

主节点被重置

+slave

一个新的从节点被发现并关联

+ failover-state-reconf-slaves <instance

Details>

故障转移进入 reconf-slaves状态

+ slave-reconf-sent

领导者 Sentinel 节点命令其他从节点复制新的主节点

+ slave-reconf-inprog

从节点正在重新配置主节点的 slave , 但是同步过程尚未完成

+slave-reconf-done

其余从节点完成了和新主节点的同步

+sentinel

一个新的 sentinel 节点被发现并关联

+sdown

添加对某个节点被主观下线

-sdown

撤销对某个节点被主观下线

+odown

添加对某个节点被客观下线

-odown

撤销对某个节点被客观下线

+new-epoch

当前纪元被更新

+try-failover

故障转移开始

+elected-leader

选出了故障转移的 Sentinel 节点

+failover-state-select-slave <instance

Details>

故障转移进入select-slave状态(寻找合适的从节点)

no-good-slave

没有找到适合的从节点

selected-slave

找到了适合的从节点

failover-state-send-slaveof-noone

故障转移进入: failover-state-send-slaveof-noone状 态(对找到的从节点执行 slaveof no one)

failover-end-for-timeout

故障转移由于超时而终止

failover-end

故障转移顺利完成

switch-master <old

port>

更新主节点信息,这个是许多客户端重点关注的

格式如下:

@

5.原主节点后续处理

  重新启动原来的6379节点:

redis-server redis-6379.conf
操作时间: 2016-07-24 09:46:21

(1) 6379节点

启动后接到Sentinel节点的命令,让它去复制6380节点:

22223:M 24 Jul 09:46:21.260 * The server is now ready to accept connections on port 6379
22223:S 24 Jul 09:46:31.323 * SLAVE OF 127.0.0.1:6380 enabled (user request from 'id=2 addr=127.0.0.1:51187 fd=6 name=sentinel-94dde2 f5-cmd age= 10 idle=0 flags=x db=0 sub=0 psub=0 multi=3 qbuf=0 qbuf-free=32768 obl=36 oll=0 omem=0 events=rw cmd=exec')
22223:S 24 Jul 09:46:31.323 # CONFIG REWRITE executed with success.

(2) 6380节点

接到6379节点的复制请求,做复制的相应处理:

19667:M 24 Jul 09:46:32.284 * Slave 127.0.0.1:6379 asks for synchronization
19667:M 24 Jul 09:46:32.284 * Full resync requested by slave 127.0.0.1:6379
19667:M 24 Jul 09:46:32.353 * Synchronization with slave 127.0.0.1:6379 succeeded

(3) sentinel-1 节点日志

撤销对6379节点主观下线的决定:

19707:X 24 Jul 09:46:21.406 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380

(4) sentinel-2 节点日志

撤销对6379节点主观下线的决定:

19713:X 24 Jul 09:46:21.408 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380

(5) sentinel-3 节点日志

撤销对6379节点主观下线的决定,更新Sentinel节点配置:

19697:X 24 Jul 09:46:21.367 # -sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380
19697:X 24 Jul 09:46:31.322 * + convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6380

6.注意点

  部署各个节点的机器时间尽量要同步,否则日志的时序性会混乱,例如可以给机器添加NTP服务来同步时间。

6.2 节点运维

1.节点下线

  在介绍如何进行节点下线之前,首先需要弄清两个概念:临时下线和永久下线。

□ 临时下线:暂时将节点关掉,之后还会重新启动,继续提供服务。

□ 永久下线:将节点关掉后不再使用,需要做一些清理工作,如删除配置文件、持久化文件、日志文件。

  所以运维人员需要弄清楚本次下线操作是临时下线还是永久下线。

  通常来看,无论是主节点、从节点还是Sentinel节点,下线原因无外乎以下几种:

□ 节点所在的机器出现了不稳定或者即将过保被回收。

□ 节点所在的机器性能比较差或者内存比较小,无法支撑应用方的需求。

□ 节点自身出现服务不正常情况,需要快速处理。

(1) 主节点

如果需要对主节点进行下线,比较合理的做法是选出一个“合适”(例如性能更高的机器)的从节点,使用sentinel failover功能将从节点晋升主节点,sentinel failover已经在9.3节介绍过了,只需要在任意可用的Sentinel节点执行如下操作即可。

sentinel failover

如图9-35所示,在任意一个Sentinel节点上(例如26379端口节点)执行sen tinel failover即可。

运维提示: Redis Sentinel存在多个从节点时,如果想将指定从节点晋升为主节点,可以将其他从节点的slave priority配置为0,但是需要注意failover后,将slave-priority调回原值。

(2) 从节点和Sentinel节点

如果需要对从节点或者Sentinel节点进行下线,只需要确定好是临时还是永久下线后执行相应操作即可。如果使用了读写分离,下线从节点需要保证应用方可以感知从节点的下线变化,从而把读取请求路由到其他节点。需要注意的是,Sentinel节点依然会对这些下线节点进行定期监控,这是由Redis Sentinel的设计思路所决定的。下面日志显示(需要设置loglevel=debug),6380 节点下线后, Sentinel 节点还是会定期对其监控,会造成一定的网络资源浪费。

-cmd-link slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
#Connection refused
-pubsub-link slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379
#Connection refused

2.节点上线

(1) 添加从节点

添加从节点的场景大致有如下几种:

□ 使用了读写分离,但现有的从节点无法支撑应用方的流量。

□ 主节点没有可用的从节点,无法支持故障转移。

□ 添加一个更强悍的从节点利用手动failover替换主节点。

添加方法:添加 slaveof {masterlp} {masterPort}的配置,使用 redis-server启动即可,它将被Sentinel节点自动发现。

(2) 添加 Sentinel 节点

添加Sentinel节点的场景可以分为以下几种:

□ 当前Sentinel节点数量不够,无法达到 Redis Sentinel 健壮性要求或者无法达到票数。

□ 原Sentinel节点所在机器需要下线。

添加方法:添加sentinel monitor主节点的配置,使用redis-sentinel启动即可,它将被其余 Sentinel 节点自动发现。

(3) 添加主节点

因为Redis Sentinel中只能有一个主节点,所以不需要添加主节点,如果需要替换主节点,可以使用Sentinel failover手动故障转移。

3.节点配置

  有关Redis数据节点和Sentinel节点配置修改以及优化的方法,前面的章节已经介绍过了,这里给出 Sentinel 节点配置时要注意的地方:

□ Sentinel节点配置尽可能一致,这样在判断节点故障时会更加准确。

□ Sentinel节点支持的命令非常有限,例如config命令是不支持的,而 Sentinel 节点也需要dir、loglevel之类的配置,所以尽量在一开始规划好,不过所幸 Sentinel 节点不存储数据,如果需要修改配置,重新启动即可。

Sentinel 节点只支持如下命令:ping、sentinel、subscribe、unsubscribe、psubscribe、punsubscribe、publish、info、role、client、shutdown。具体可以参考源码中sentinel.c。

6.3 高可用读写分离

1.从节点的作用

  从节点一般可以起到两个作用:第一,当主节点出现故障时,作为主节点的后备“顶”上来实现故障转移,Redis Sentinel已经实现了该功能的自动化,实现了真正的高可用。第二,扩展主节点的读能力,尤其是在读多写少的场景非常适用,通常的模型如图9-36所示。

  但上述模型中,从节点不是高可用的,如果slave-1节点出现故障,首先客户端client-1将与其失联,其次Sentinel节点只会对该节点做主观下线,因为Redis Sentinel的故障转移是针对主节点的。所以很多时候,Redis Sentinel中的从节点仅仅是作为主节点一个热备,不让它参与客户端的读操作,就是为了保证整体高可用性,但实际上这种使用方法还是有一些浪费,尤其是在有很多从节点或者确实需要读写分离的场景,所以如何实现从节点的高可用是非常有必要的。

2.Redis Sentinel读写分离设计思路

  Redis Sentinel在对各个节点的监控中,如果有对应事件的发生,都会发出相应的事件消息(见表9-6),其中和从节点变动的事件有以下几个:

□ +switch-master: 切换主节点(原来的从节点晋升为主节点),说明减少了某个从节点。

□ +convert-to-slave: 切换从节点(原来的主节点降级为从节点),说明添加了某个从节点。

□ +sdown: 主观下线,说明可能某个从节点可能不可用(因为对从节点不会做客观下线),所以在实现客户端时可以采用自身策略来实现类似主观下线的功能。

□ +reboot :重新启动了某个节点,如果它的角色是slave, 那么说明添加了某个从节点。

  所以在设计Redis Sentinel的从节点高可用时,只要能够实时掌握所有从节点的状态,把所有从节点看做一个资源池(如图9-37所示),无论是上线还是下线从节点,客户端都能及时感知到(将其从资源池中添加或者删除),这样从节点的高可用目标就达到了。

1. Redis Sentinel是 Redis的高可用实现方案:故障发现、故障自动转移、配置中心、客户端通知。

2. Redis Sentinel从 Redis 2.8版本开始才正式生产可用,之前版本生产不可用。

3. 尽可能在不同物理机上部署Redis Sentinel所有节点。

4. Redis Sentinel中的Sentinel节点个数应该为大于等于3 且最好为奇数。

5. Redis Sentinel中的数据节点与普通数据节点没有区别。

6. 客户端初始化时连接的是Sentinel节点集合,不再是具体的Redis节点,但 Sentinel只是配置中心不是代理。

7. Redis Sentinel通过三个定时任务实现了 Sentinel节点对于主节点、从节点、其余Sentinel节点的监控。

8. Redis Sentinel在对节点做失败判定时分为主观下线和客观下线。

9. Redis Sentinel实现读写分离高可用可以依赖Sentinel节点的消息通知,获取Redis数据节点的状态变化。