痛点
Redis主从复制模式已经实现了数据的多机备份,以及对于读操作的负载均衡。 但是有个主从复制有个致命的缺陷,一旦主节点由于故障不能继续提供服务,需要手动选定一个从节点晋升为主节点,同时还要更改其他从节点的配置指向新的主节点。Redis 2.8正式推出了哨兵模式实现了自动故障恢复。
哨兵功能
Redis Sentinel 是 Redis 高可用 的实现方案。Sentinel 是一个管理多个 Redis 实例的工具,它可以实现对 Redis 的 监控、通知、自动故障转移。下面先对 Redis Sentinel 的 基本概念 进行简单的介绍。
监控:Sentinel会不断检查主实例和副本实例的工作状态。
通知:当被监控的实例出现问题时,Sentinel可以通过API通知系统管理员或其他计算机程序
自动故障转移:主节点出现问题时,Sentinel会开始一次自动故障转移,Sentinel集群会协商选定一个从节点升级为主节点,并将其他的从节点指向新的主节点。
配置提供者:在Sentinel模式下,Sentinel充当客户端服务发现的权威来源,提供主节点的信息

概念介绍
为了更加清楚Sentinel模式是如何工作的,需要先了解以下的概念。
sentinel和salve自动发现机制
sentinel仅需要配置监测的master而不需要配置slaves,sentinel会自动的向master查询slave信息。
sentinel会利用redis的发布订阅功能来实现自动发现。所有的sentinel都会连接到所有master和slave的__sentinel__:hello频道,每两秒钟发布一条带有ip、port、runid的消息。所有sentinel都会收到这个消息,这个消息携带主节点的配置信息,如果自身保存的master配置信息旧于收到的,则立即更新。如果收到的sentinel hello消息携带的runid或地址信息已经存在,则删除自身保存的旧的sentinel,替换为新的这个。
主观下线和客观下线
默认情况下,Sentinel节点会以两秒一次的频率对Redis实例和其他Sentinel实例发送PING命令,并通过实例的回复判断实例的状态。
主观下线(SDOWN)
如果在down-after-millseconds毫秒内,Sentinel实例没有收到目标节点的有效回复,则判定该目标节点主观下线。
有效回复包括:+ PONG 、-LOADING错误、 -MASTERDOWN错误
客观下线(ODWON)
仅仅只有主观下线是不足以触发故障转移,所以对于主节点,还引入了一个客观下线的概念.
如果 主节点 出现故障,Sentinel 节点会通过 sentinel is-master-down-by-addr 命令,向其它 Sentinel 节点询问对该节点的 状态判断。如果超过 <quorum> 个数的节点判定 主节点 不可达,则该 Sentinel 节点会判断 主节点 为 客观下线。
从节点的选择
好了,现在Sentinel集群任务主节点客观下线了,选择哪一个从节点作为新的主节点呢。
Sentinel集群主要有以下评判标准,并且优先级从上往下:
- 从节点与主节点断开连接的时间
- 从节点的优先级
- 从节点从主节点接收数据量
- 节点的Run ID
首先,如果从节点与主节点断开连接的时间超过了(down-after-milliseconds * 10) + 主节点ODOWN时间,这样的从节点会被忽略,不会参与主节点的竞选。
从节点的配置文件中(redis.conf)中的配置项slave-priority(默认为100)声明了从节点的优先级,数值越低代表优先级越高.
slave-priority值为 0,表示该从节点永远不会被推选主节点
如果多个从节点的优先级相同,则会检测Replication offset ,代表从节点从主节点接收数据量,Replication offset数值越大优先级越高。
最后,如果优先级和Replication offset都相同,那么就要看Run ID了,Run ID越小,优先级越高。
配置纪元(epochs)
就像古代皇帝的年号一样,更换主节点时,Sentinel实例会认为进入一个新纪元(epochs)。
当一个sentinel被授权进行故障迁移时,他会获得一个configuration epoch,用于标记配置信息的版本。——每一次故障迁移所使用的配置信息都对应一个配置纪元。
故障迁移还有一个规则:其他sentinel会等待被授权的sentinel failover-timeout的时间进行故障迁移,之后自己才会尝试failover。这保证同一时间只有一个sentinel在进行故障迁移。
哨兵配置
下面来说明一下常用的哨兵模式的配置。默认的哨兵模式配置文件叫做sentinel.conf.
- sentinel monitor
规定了sentinel节点监控的 主节点的配置信息,quorum译为法定人数,超过quorum数量Sentinel节点认为主观下线时,才能认为主节点客观下线。
- sentinel down-after-milliseconds
规定了节点在down-after-milliseconds未回复消息,就会被认为客观下线,默认30秒
- sentinel failover-timeout
规定了,Sentinel节点进行故障转移的时间,默认三分钟。
- sentinel parallel-syncs
指定了在发生failover主备切换时,最多可以有多少个slave同时对新的master进行同步。
- logfile
指定该哨兵节点日志位置
- port
指定该哨兵节点占用端口
- daemonize
指定该哨兵节点是否为守护程序
至少需要三个哨兵实例
先来说明一下,哨兵模式的Redis实例最小要求是一主一从,同时最少需要三个哨兵实例。这可以用TCP连接为什么要三次握手用同样的思路回答------为什么两个哨兵不行。
先来说明一下故障转移的流程,当Sentinel集群认为主节点客观下线时,进行故障转移需要Sentinel集群中的大多数节点协商选出新的主节点和执行故障转移的Sentinel节点。
如果,此时只有两个Sentinel节点,并且其中一个Sentinel节点和主节点位于同一台机器,此时机器挂掉了,那么,就会只剩下一个Sentinel节点。只有一个节点无法满足大多数的概念。从而无法进行故障转移。

Sentinel集群搭建
[Redis主从复制模式]()文章中已经讲述了如何搭建一个Redis主从实例。此处不在赘述。
现在,需要先修改sentinel.conf配置文件
port 16380
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
# 如果节点需要密码的话
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 180000
logfile "/usr/local/redis-4.0.11/log/sentinel-16380.log"
daemonize yes
接着,修改文件名,并复制两份相同的命令
mv ./sentinel.conf ./sentinel_16380.conf
cp ./sentinel_16380.conf ./sentinel_26380.conf
cp ./sentinel_26380.conf ./sentinel_36380.conf
sentinel_26380.conf的配置如下
port 26380
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
# 如果节点需要密码的话
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 180000
logfile "/usr/local/redis-4.0.11/log/sentinel-26380.log"
daemonize yes
sentinel_36380.conf的配置如下
port 36380
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
# 如果节点需要密码的话
sentinel auth-pass mymaster 123456
sentinel failover-timeout mymaster 180000
logfile "/usr/local/redis-4.0.11/log/sentinel-36380.log"
daemonize yes
在Redis主从实例启动后,用下面的命令,启动Sentinel集群
redis-sentinel ./sentinel-16380.conf
redis-sentinel ./sentinel-26380.conf
redis-sentinel ./sentinel-36380.conf
用netstat -nltp命令查看端口情况
[root@localhost script]# netstat -nltp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:26379 0.0.0.0:* LISTEN 7752/redis-server 0
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN 7742/redis-server 0
tcp 0 0 0.0.0.0:26380 0.0.0.0:* LISTEN 7764/redis-sentinel
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 7033/sshd
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 7316/master
tcp 0 0 0.0.0.0:16379 0.0.0.0:* LISTEN 7747/redis-server 0
tcp 0 0 0.0.0.0:36380 0.0.0.0:* LISTEN 7769/redis-sentinel
tcp 0 0 0.0.0.0:16380 0.0.0.0:* LISTEN 7759/redis-sentinel
tcp6 0 0 :::22 :::* LISTEN 7033/sshd
tcp6 0 0 ::1:25 :::* LISTEN 7316/master
可以看到,对应的Redis节点和哨兵节点都已经被启动。
自动故障转移验证
用ps -ef|grep redis命令 查看 与相关Redis进程状态
[root@localhost script]# ps -ef|grep redis
root 7742 1 0 01:50 ? 00:00:08 redis-server 0.0.0.0:6379
root 7747 1 0 01:50 ? 00:00:04 redis-server 0.0.0.0:16379
root 7752 1 0 01:50 ? 00:00:05 redis-server 0.0.0.0:26379
root 7759 1 0 01:50 ? 00:00:06 redis-sentinel 0.0.0.0:16380 [sentinel]
root 7764 1 0 01:50 ? 00:00:06 redis-sentinel 0.0.0.0:26380 [sentinel]
root 7769 1 0 01:50 ? 00:00:06 redis-sentinel 0.0.0.0:36380 [sentinel]
root 7839 7566 0 02:21 pts/0 00:00:00 grep --color=auto redis
可以看到,master节点的pid是7742,现在用kill -9 7742命令,模拟主节点发送了故障。
然后查看,哨兵节点的日志
[root@localhost script]# cat ../log/sentinel-16380.log
21050:X 01 Jul 16:28:29.882 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
21050:X 01 Jul 16:28:29.882 # Redis version=4.0.11, bits=64, commit=00000000, modified=0, pid=21050, just started
21050:X 01 Jul 16:28:29.882 # Configuration loaded
21051:X 01 Jul 16:28:29.894 * Increased maximum number of open files to 10032 (it was originally set to 1024).
21051:X 01 Jul 16:28:29.896 * Running mode=sentinel, port=16380.
21051:X 01 Jul 16:28:29.897 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
// 此哨兵节点的RUN ID
21051:X 01 Jul 16:28:29.920 # Sentinel ID is 20e0390dc2bcfc1f9edbf441aa0d658a67872f2a
// 哨兵节点启动之初6379作为主节点,并且通过主节点发现了两个slave节点 16379 26379
21051:X 01 Jul 16:28:29.920 # +monitor master mymaster 127.0.0.1 6379 quorum 2
21051:X 01 Jul 16:28:29.926 * +slave slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 6379
21051:X 01 Jul 16:28:29.931 * +slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 6379
// 哨兵节点发现了另外的两个哨兵节点 26380 36380
21051:X 01 Jul 16:28:31.910 * +sentinel sentinel cb19acb18a984bd016f47c8c61c13a5b5c7e112d 127.0.0.1 26380 @ mymaster 127.0.0.1 6379
21051:X 01 Jul 16:28:31.935 * +sentinel sentinel 7d011dd04392288cdf5cde9b3f5ec8fee9d8dcd5 127.0.0.1 36380 @ mymaster 127.0.0.1 6379
// 哨兵节点认为6379主观下线
21051:X 01 Jul 17:33:12.842 # +sdown master mymaster 127.0.0.1 6379
// 哨兵集群认为6379客观下线
21051:X 01 Jul 17:33:12.909 # +odown master mymaster 127.0.0.1 6379 #quorum 2/2
// 进入新纪元 开始进行故障转移
21051:X 01 Jul 17:33:12.909 # +new-epoch 1
21051:X 01 Jul 17:33:12.909 # +try-failover master mymaster 127.0.0.1 6379
// 因为此哨兵节点的RUN ID最小,从而成为故障转移的执行哨兵节点
21051:X 01 Jul 17:33:12.911 # +vote-for-leader 20e0390dc2bcfc1f9edbf441aa0d658a67872f2a 1
21051:X 01 Jul 17:33:12.916 # 7d011dd04392288cdf5cde9b3f5ec8fee9d8dcd5 voted for 20e0390dc2bcfc1f9edbf441aa0d658a67872f2a 1
21051:X 01 Jul 17:33:12.916 # cb19acb18a984bd016f47c8c61c13a5b5c7e112d voted for 20e0390dc2bcfc1f9edbf441aa0d658a67872f2a 1
// 选举26379Redis节点成为新的主节点
21051:X 01 Jul 17:33:12.988 # +elected-leader master mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:12.988 # +failover-state-select-slave master mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:13.055 # +selected-slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:13.055 * +failover-state-send-slaveof-noone slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:13.133 * +failover-state-wait-promotion slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:13.205 # +promoted-slave slave 127.0.0.1:26379 127.0.0.1 26379 @ mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:13.205 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:13.292 * +slave-reconf-sent slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:14.015 # -odown master mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:14.015 * +slave-reconf-inprog slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:14.015 * +slave-reconf-done slave 127.0.0.1:16379 127.0.0.1 16379 @ mymaster 127.0.0.1 6379
21051:X 01 Jul 17:33:14.071 # +failover-end master mymaster 127.0.0.1 6379
//故障转移成功
21051:X 01 Jul 17:33:14.071 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 26379
再打开sentinel_16380.conf查看配置文件,是否被修改。
// 哨兵监控配置节点已从
sentinel monitor mymaster 127.0.0.1 26379 2
// 同时被追加以下配置
# Generated by CONFIG REWRITE
sentinel known-slave mymaster 127.0.0.1 16379
sentinel known-slave mymaster 127.0.0.1 6379
sentinel known-sentinel mymaster 127.0.0.1 36380 7d011dd04392288cdf5cde9b3f5ec8fee9d8dcd5
sentinel known-sentinel mymaster 127.0.0.1 26380 cb19acb18a984bd016f47c8c61c13a5b5c7e112d
sentinel current-epoch 1
可以,看到哨兵集群确实是实现了自动故障转移。
哨兵模式未解决的问题
哨兵模式虽然实现了自动故障转移功能,但是没能解决写操作无法负载均衡,存储的能力受到单机的限制。解决的方案是使用Redis集群,具体的集群解决可以参考这篇掘金文章 深入剖析Redis系列(二) - Redis哨兵模式与高可用集群
掘金用户零壹技术栈的文章------深入剖析Redis系列(二) - Redis哨兵模式与高可用集群
Redis官方文档---High Availability