Sentinel集群介绍
由上一篇文章:Redis哨兵技术的应用(一)我们知道了哨兵的作用以及工作原理。
其中配置哨兵服务时最重要的有这么一行配置:
1 |
sentinel monitor mymaster 10.99.73.11 6379 1 |
其中mymaster表示要监控的主数据库的名字,可以自己定义一个,这个名字必须仅由大小写字母、数字和“.-_”这三个字符组成。后两个参数表示主数据库的地址和端口号,这里我们要监控的是主数据库6379。最后的“1”表示最低通过票数,但是Quorum(票数)需要解释一下:
- quorum是Sentinel需要协商同意master是否可到达的数量,为了真正的标记master为失败,并最终是否需要启动一个故障转移进程。
- quorum只用于检测故障,为了实际执行故障转移,Sentinel需要选举leader并进行授权,这只发生在大多数Sentinel进程的选举。
例如你有5个哨兵进程,并且给定的master的quorum的值设置为2,这是将发生的事情:
- 如果两个哨兵进程同时同意master是不可到达的,这两个哨兵的其中一个将启动一个故障转移(sentinel自身会根据Raft算法选出一个leader sentinel去进行故障转移,这样可以保证同一时间只有一个哨兵节点来执行故障转移)。
- 如果至少三个哨兵可获得,故障转移将会被授权并真实的启动。
实际上这意味着在故障转移期间如果大多数的Sentinel进程不能通信,Sentinel将会永不启动故障转移(即在少数分区没有故障转移)。
由上面的简单说明,下面我们可以引入Sentinel集群了。虽然Sentinel可以进行主从的故障转移,但是如果Sentinel自身就是单点的话,那么这个高可用其实是不完美的。解决这个问题就是给Sentinel自身也做成一个集群,一般节点数最少是3个(允许挂一个,如果挂两个就无法完成故障转移),其实Sentinel集群很简单,就是多开几个节点就行了,各个sentinel配置文件可以一样,他们之间会自动协商。
官方文档:https://redis.io/topics/sentinel。
Sentinel集群配置
一、环境配置
1 2 3 |
$ mkdir /data/redis/6379/{conf,data,log,pid} -p $ mkdir /data/redis/6380/{conf,data,log,pid} -p $ mkdir /data/redis/{26379,26380,16381} -p |
二、Redis配置启动
redis(6379)配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
$ cat /data/redis/6379/conf/redis.conf ##基本参数### daemonize yes pidfile "/data/redis/6379/pid/redis.pid" port 6379 tcp-backlog 65535 bind 0.0.0.0 timeout 0 tcp-keepalive 0 loglevel notice logfile "/data/redis/6379/log/redis.log" databases 16 lua-time-limit 5000 maxclients 10000 #protected-mode yes dir "/data/redis/6379/data" ###慢日志参数### slowlog-log-slower-than 10000 slowlog-max-len 128 ###内存参数### maxmemory 1000000000 maxmemory-policy volatile-lru ###RDB持久化参数### save 3600 1 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename "dump.rdb" ###AOF持久化参数### no-appendfsync-on-rewrite yes appendonly yes appendfilename "appendonly.aof" appendfsync no auto-aof-rewrite-min-size 512mb auto-aof-rewrite-percentage 100 aof-load-truncated yes aof-rewrite-incremental-fsync yes ###客户端Buffer参数### client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 ###其他参数### hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-entries 512 list-max-ziplist-value 64 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 hll-sparse-max-bytes 3000 activerehashing yes latency-monitor-threshold 0 hz 10 |
redis(6380)配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
$ cat /data/redis/6380/conf/redis.conf ##基本参数### daemonize yes pidfile "/data/redis/6380/pid/redis.pid" port 6380 tcp-backlog 65535 bind 0.0.0.0 timeout 0 tcp-keepalive 0 loglevel notice logfile "/data/redis/6380/log/redis.log" databases 16 lua-time-limit 5000 maxclients 10000 #protected-mode yes dir "/data/redis/6380/data" ###慢日志参数### slowlog-log-slower-than 10000 slowlog-max-len 128 ###内存参数### maxmemory 1000000000 maxmemory-policy volatile-lru ###RDB持久化参数### save 3600 1 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename "dump.rdb" ###AOF持久化参数### no-appendfsync-on-rewrite yes appendonly yes appendfilename "appendonly.aof" appendfsync no auto-aof-rewrite-min-size 512mb auto-aof-rewrite-percentage 100 aof-load-truncated yes aof-rewrite-incremental-fsync yes ###客户端Buffer参数### client-output-buffer-limit normal 0 0 0 client-output-buffer-limit slave 256mb 64mb 60 client-output-buffer-limit pubsub 32mb 8mb 60 ###其他参数### hash-max-ziplist-entries 512 hash-max-ziplist-value 64 list-max-ziplist-entries 512 list-max-ziplist-value 64 set-max-intset-entries 512 zset-max-ziplist-entries 128 zset-max-ziplist-value 64 hll-sparse-max-bytes 3000 activerehashing yes latency-monitor-threshold 0 hz 10 ###主从### slaveof 127.0.0.1 6379 |
启动Redis
1 2 |
$ redis-server /data/redis/6379/conf/redis.conf $ redis-server /data/redis/6380/conf/redis.conf |
三、Sentinel配置启动
sentinel(26379)配置文件
1 2 3 4 5 6 7 8 |
$ cat /data/redis/26379/sentinel.conf port 26379 # 6379 sentinel monitor mymaster 10.99.73.11 6379 2 sentinel down-after-milliseconds mymaster 4000 sentinel failover-timeout mymaster 18000 sentinel parallel-syncs mymaster 1 |
sentinel(26380)配置文件
1 2 3 4 5 6 7 8 |
$ cat /data/redis/26380/sentinel.conf port 26380 # 6379 sentinel monitor mymaster 10.99.73.11 6379 2 sentinel down-after-milliseconds mymaster 4000 sentinel failover-timeout mymaster 18000 sentinel parallel-syncs mymaster 1 |
sentinel(26381)配置文件
1 2 3 4 5 6 7 8 |
$ cat /data/redis/26381/sentinel.conf port 26381 # 6379 sentinel monitor mymaster 10.99.73.11 6379 2 sentinel down-after-milliseconds mymaster 4000 sentinel failover-timeout mymaster 18000 sentinel parallel-syncs mymaster 1 |
由于是启动了sentinel集群,所以在配置sentinel启用故障转移的投票数为2,该配置表示只有当至少两个sentinel节点(包括当前节点)认为该主数据库主观下线时,当前sentinel节点才会认为该主数据库客观下线。然后接下来进行选举哨兵leader步骤。
虽然当前哨兵节点发现了主数据库客观下线,需要故障恢复,但是故障恢复需要由领头哨兵来完成,这样可以保证同一时间只有一个哨兵节点来执行故障恢复。选举领头哨兵的过程使用了Raft算法,具体过程如下:
1)发现主数据库客观下线的哨兵节点(下面称作A)向每个哨兵节点发送命令,要求对方选自己成为领头哨兵。
2)如果目标哨兵节点没有选过其他人,则会同意将A设置成领头哨兵。
3)如果A发现有超过半数且超过quorum参数值的哨兵节点同意选自己成为领头哨兵,则A成功成为哨兵leader。
4)当有多个哨兵节点同时参选领头哨兵,则会出现没有任何节点当选的可能。此时每个参选节点将等待一个随机时间重新发起参选请求,进行下一轮选举,知道选举成功为止。具体过程可参数Raft算法的过程https://raft.github.io/。因为要成为领头哨兵必须有超过半数的哨兵节点支持(哨兵节点部署为奇数),所以每次选举最多只会选出一个领头哨兵。
选出领头哨兵后,领头哨兵将会开始对主数据库进行故障恢复,故障恢复的过程相对简单,具体如下。首先领头哨兵将从停止的主数据库的从数据库中挑选一个来充当新的主数据库。挑选的依据如下:
1)所有在线的从数据库中,选择优先级最高的从数据库,优先级可以通过slave-priority选择来设置。
2)如果有多个最高优先级的从数据库,则复制的命令偏移量越大(复制数据越完整)越有优先。
3)如果以上条件都一样,则选择运行ID较小的从数据库。
选出一个从数据库后,领头哨兵将向从数据库发送SLAVEOF NO ONE命令使其升格为主数据库。而后领头哨兵向其他从数据库发送SLAVEOF命令来使其成为新主数据库的从数据库。最后一步则是更新内部的记录,将已经停止服务的旧的主数据库更新为新的主数据库的从数据库,使得当其恢复服务时自动以从数据库的身份继续服务。
启动sentinel
1 2 3 4 |
$ nohup redis-sentinel /data/redis/26379/sentinel.conf & [13127] 22 Dec 11:26:24.174 # Sentinel runid is f01077eecd54bc06421b8530b4424c913b6de37b [13127] 22 Dec 11:26:24.174 # +monitor master mymaster 10.99.73.7 6379 quorum 2 [13127] 22 Dec 11:26:24.177 * +slave slave 10.99.73.7:6380 10.99.73.7 6380 @ mymaster 10.99.73.7 6379 |
runid:表示sentinel生成的ID。
+monitor:表示sentinel监控到的主节点,以及sentinel知道了quorum的数量。
+slave:表示sentinel监控到的从节点。
1 2 3 4 5 6 |
$ nohup redis-sentinel /data/redis/26379/sentinel.conf & [13130] 22 Dec 11:26:47.268 # Sentinel runid is e81507ce3de48e1e275547ee98eeb8df9c240d1a [13130] 22 Dec 11:26:47.268 # +monitor master mymaster 10.99.73.7 6379 quorum 2 [13130] 22 Dec 11:26:47.269 * +slave slave 10.99.73.7:6380 10.99.73.7 6380 @ mymaster 10.99.73.7 6379 [13130] 22 Dec 11:26:48.594 * +sentinel sentinel 10.99.73.7:26379 10.99.73.7 26379 @ mymaster 10.99.73.7 6379 [13127] 22 Dec 11:26:49.302 * +sentinel sentinel 10.99.73.7:26380 10.99.73.7 26380 @ mymaster 10.99.73.7 6379 |
+sentinel:表示当前对主redis监控的sentinel数量,有sentinel的端口(26379,26380)。
1 2 3 4 5 6 7 8 |
$ nohup redis-sentinel /data/redis/26379/sentinel.conf & [13133] 22 Dec 11:27:02.563 # Sentinel runid is 1b0727c885083dbff87fa14d64924ae527ce7a02 [13133] 22 Dec 11:27:02.563 # +monitor master mymaster 10.99.73.7 6379 quorum 2 [13133] 22 Dec 11:27:02.565 * +slave slave 10.99.73.7:6380 10.99.73.7 6380 @ mymaster 10.99.73.7 6379 [13133] 22 Dec 11:27:02.933 * +sentinel sentinel 10.99.73.7:26379 10.99.73.7 26379 @ mymaster 10.99.73.7 6379 [13133] 22 Dec 11:27:03.480 * +sentinel sentinel 10.99.73.7:26380 10.99.73.7 26380 @ mymaster 10.99.73.7 6379 [13127] 22 Dec 11:27:04.627 * +sentinel sentinel 10.99.73.7:26381 10.99.73.7 26381 @ mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:04.627 * +sentinel sentinel 10.99.73.7:26381 10.99.73.7 26381 @ mymaster 10.99.73.7 6379 |
三个sentinel都启动成功后,然后就可以查看一下sentinel信息:
1 |
127.0.0.1:26379> sentinel master mymaster |
其中有这么几行信息
1 2 3 4 5 6 |
29) "num-slaves" 30) "1" 31) "num-other-sentinels" 32) "2" 33) "quorum" 34) "2" |
主数据库的从数据库数量,其他的sentinel节点数量(2+自身),还有启动故障转移时的quorum数量。
四、Sentinel相关命令
ping:返回PONG 。
sentinel masters:命令可以列出所有被监视的主Redis服务实例,以及这些主服务实例的当前状态。
sentinel slaves:列出给定主服务实例的所有从实例,以及这些从实例的当前状态。
sentinel get-master-addr-by-name:返回给定名字的主实例的IP地址和端口号,如果这个主实例正在执行故障转移操作,或者针对这个主实例的故障转移操作已经完成,那么这个命令返回新的主服务器的IP地址和端口号。
sentinel reset:重置所有名字和给定模式pattern相匹配的主服务器,pattern参数是一个Glob风格的模式,重置操作清除该sentinel的所保存的所有状态信息,并进行一次重新的发现过程。
SENTINEL failover :进行一次主动的failover。即在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移 。发起故障转移的 Sentinel 会向其他 Sentinel 发送一个新的配置,其他 Sentinel 会根据这个配置进行相应的更新。
五、测试Sentinel自动Failover
现在Redis和sentinel都完成后,就可以进行主redis(6379)的关闭,测试看是否能进行主从切换。
1 |
$ redis-cli -p 6379 shutdown |
大概等个几秒钟,然后就会看到sentinel吐出的如下日志:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
[13133] 22 Dec 11:27:40.824 # +sdown master mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:40.843 # +sdown master mymaster 10.99.73.7 6379 [13127] 22 Dec 11:27:40.848 # +sdown master mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:40.902 # +odown master mymaster 10.99.73.7 6379 #quorum 2/2 [13130] 22 Dec 11:27:40.902 # +new-epoch 1 [13130] 22 Dec 11:27:40.902 # +try-failover master mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:40.904 # +vote-for-leader e81507ce3de48e1e275547ee98eeb8df9c240d1a 1 [13133] 22 Dec 11:27:40.906 # +new-epoch 1 [13127] 22 Dec 11:27:40.906 # +new-epoch 1 [13127] 22 Dec 11:27:40.907 # +vote-for-leader e81507ce3de48e1e275547ee98eeb8df9c240d1a 1 [13133] 22 Dec 11:27:40.907 # +vote-for-leader e81507ce3de48e1e275547ee98eeb8df9c240d1a 1 [13130] 22 Dec 11:27:40.907 # 10.99.73.7:26381 voted for e81507ce3de48e1e275547ee98eeb8df9c240d1a 1 [13130] 22 Dec 11:27:40.907 # 10.99.73.7:26379 voted for e81507ce3de48e1e275547ee98eeb8df9c240d1a 1 [13127] 22 Dec 11:27:40.948 # +odown master mymaster 10.99.73.7 6379 #quorum 3/2 [13127] 22 Dec 11:27:40.948 # Next failover delay: I will not start a failover before Thu Dec 22 11:28:17 2016 [13130] 22 Dec 11:27:40.981 # +elected-leader master mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:40.981 # +failover-state-select-slave master mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:41.033 # +selected-slave slave 10.99.73.7:6380 10.99.73.7 6380 @ mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:41.033 * +failover-state-send-slaveof-noone slave 10.99.73.7:6380 10.99.73.7 6380 @ mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:41.133 * +failover-state-wait-promotion slave 10.99.73.7:6380 10.99.73.7 6380 @ mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:41.924 # +promoted-slave slave 10.99.73.7:6380 10.99.73.7 6380 @ mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:41.924 # +failover-state-reconf-slaves master mymaster 10.99.73.7 6379 [13133] 22 Dec 11:27:41.938 # +odown master mymaster 10.99.73.7 6379 #quorum 3/2 [13133] 22 Dec 11:27:41.938 # Next failover delay: I will not start a failover before Thu Dec 22 11:28:17 2016 [13130] 22 Dec 11:27:41.976 # +failover-end master mymaster 10.99.73.7 6379 [13130] 22 Dec 11:27:41.976 # +switch-master mymaster 10.99.73.7 6379 10.99.73.7 6380 [13130] 22 Dec 11:27:41.976 * +slave slave 10.99.73.7:6379 10.99.73.7 6379 @ mymaster 10.99.73.7 6380 [13127] 22 Dec 11:27:41.978 # +config-update-from sentinel 10.99.73.7:26380 10.99.73.7 26380 @ mymaster 10.99.73.7 6379 [13127] 22 Dec 11:27:41.978 # +switch-master mymaster 10.99.73.7 6379 10.99.73.7 6380 [13133] 22 Dec 11:27:41.978 # +config-update-from sentinel 10.99.73.7:26380 10.99.73.7 26380 @ mymaster 10.99.73.7 6379 [13133] 22 Dec 11:27:41.978 # +switch-master mymaster 10.99.73.7 6379 10.99.73.7 6380 [13127] 22 Dec 11:27:41.978 * +slave slave 10.99.73.7:6379 10.99.73.7 6379 @ mymaster 10.99.73.7 6380 [13133] 22 Dec 11:27:41.978 * +slave slave 10.99.73.7:6379 10.99.73.7 6379 @ mymaster 10.99.73.7 6380 [13133] 22 Dec 11:27:46.012 # +sdown slave 10.99.73.7:6379 10.99.73.7 6379 @ mymaster 10.99.73.7 6380 [13127] 22 Dec 11:27:46.028 # +sdown slave 10.99.73.7:6379 10.99.73.7 6379 @ mymaster 10.99.73.7 6380 [13130] 22 Dec 11:27:46.030 # +sdown slave 10.99.73.7:6379 10.99.73.7 6379 @ mymaster 10.99.73.7 6380 |
+sdown:表示sentinel认为主redis主观下线。对应-sdown表示给定节点不再处于主观下线。
+odown:表示sentinel认为主redis客观下线,因为已经有两个sentinel节点认为主redis宕机。对应-odown表示给定节点不再处于客观下线。
+try-failover:表示哨兵开始进行故障恢复;
+vote-for-leader:表示哨兵集群之间开始进行领头哨兵的选举。
+elected-leader:表示赢得选举,可以进行故障迁移操作了。
+failover-state-select-slave:表示故障转移操作现在处于select-slave状态,Sentinel正在寻找可以升级为主服务器的从服务器。
+selected-slave:顺利找到适合进行升级的从服务器。
+failover-state-send-slaveof-noone:表示Sentinel正在将指定的从服务器升级为主服务器,等待升级功能完成。
+failover-state-wait-promotion:等待其他sentinel的确认:
+promoted-slave:确认成功;
+failover-state-reconf-slaves:开始对slaves进行reconfig操作;
+failover-end:表示哨兵完成故障恢复,期间涉及的内容比较复杂,包括领头哨兵的选举、备选从数据库的选择等;然后所有从服务器都开始复制新的主服务器了。
+switch-master:监听新的master,表示主数据库从6379端口迁移到6380端口,即6380端口的从数据库被升格为主数据库;sentinel配置变更,主服务器的IP 和地址已经改变。
+config-update-from:更新sentinel配置。
这是一个成功的选举和故障转移过程,然后可以找一个sentinel看一下其配置文件信息的变化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
port 26379 # 6379 sentinel monitor mymaster 10.99.73.7 6380 2 sentinel down-after-milliseconds mymaster 4000 sentinel failover-timeout mymaster 18000 sentinel config-epoch mymaster 1 # Generated by CONFIG REWRITE dir "/data/redis/26379" sentinel leader-epoch mymaster 1 sentinel known-slave mymaster 10.99.73.7 6379 sentinel known-sentinel mymaster 10.99.73.7 26381 1b0727c885083dbff87fa14d64924ae527ce7a02 sentinel known-sentinel mymaster 10.99.73.7 26380 e81507ce3de48e1e275547ee98eeb8df9c240d1a sentinel current-epoch 1 |
可以看到已经把6380提升为主监控起来了,而known-slave设置为了6379(当6379存活时就把它提升为6380的从)。还有known-sentinel就是当前节点发现的其他sentinel节点信息,而leader-epoch和current-epoch后的值表示此次选举的次数,1表示1次,很有可能有多次。而因素有很多。比如:各个节点的down-after-milliseconds时间设置最好不一样,不然就有很大的机会出现选举多次,导致主从切换时间过长。案例:http://www.ithao123.cn/content-8821082.html。
最后需要注意的是,sentinel集群自身也需要多数机制,也就是当有2个sentinel进程时,挂掉一个另一个就不可用了。
五、测试Sentinel主动Failover
进行一次主动的failover,即在不询问其他Sentinel意见的情况下,强制开始一次自动故障迁移。发起故障转移的Sentinel会向其他Sentinel发送一个新的配置,其他Sentinel会根据这个配置进行相应的更新。
1 2 |
127.0.0.1:26379> sentinel failover mymaster OK |
第二种方法是使用发布与订阅功能,通过接收Sentinel发送的通知:当执行故障转移操作,或者某个被监视的实例被判断为主观下线或者客观下线时,Sentinel就会发送相应的信息。一个频道能够接收和这个频道的名字相同的事件。 比如说,名为+sdown的频道就可以接收所有实例进入主观下线(SDOWN)状态的事件。
1 2 3 4 5 |
127.0.0.1:26379> PSUBSCRIBE * Reading messages... (press Ctrl-C to quit) 1) "psubscribe" 2) "*" 3) (integer) 1 |
此时进行一次主动的failover,各个频道的输出中涉及了新纪元(epoch)通知、投票、选举、新主和新slave的通告、角色转变通知,最终完成了这次主动failover过程。比如还可以使用Python来订阅一下哨兵,当发生主从切换时会通知这个程序。
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/usr/bin/env python import redis ip_sen = "127.0.0.1" port_sen = "26379" rc = redis.client.StrictRedis(host=ip_sen,port=port_sen) p = rc.pubsub() channel=['-role-change'] p.subscribe(channel) while True: message = p.get_message() if message: print message |
PS:最后说一点,我在Redis 3.2.0编译版本中测试sentinel集群这个功能,在领头哨兵选举时一直有问题,就是一直选不出领头哨兵,原因未知。这个问题卡了我3个小时,后来换了一个版本就OK了。