上一篇博客讲完Redis复制技术的应用,知道了Redis中复制的原理和使用方式后,在一个典型的一主已从或一主多从的Redis系统中,从数据库虽然可以起到了数据冗余备份和读写分离的作用。但是也能发现,当主节点发生故障后,需要人为干预手动提升一个从节点为主节点继续对外提供服务,难以实现自动化。
为此,Redis 2.8版本开始提供了哨兵工具来实现自动化的系统监控和故障恢复功能。
什么是哨兵?
Redis哨兵为Redis提供了高可用性。实际上这意味着你可以使用哨兵模式创建一个可以不用人为干预而应对各种故障的Redis部署。
哨兵模式还提供了其他的附加功能,如监控,通知,为客户端提供配置。
下面是在宏观层面上哨兵模式的功能列表:
- 监控:哨兵不断的检查master和slave是否正常的运行。
- 通知:当监控的某台Redis实例发生问题时,可以通过API通知系统管理员和其他的应用程序。
- 自动故障转移:如果一个master不正常运行了,哨兵可以启动一个故障转移进程,将一个slave升级成为master,其他的slave被重新配置使用新的master,并且应用程序使用Redis服务端通知的新地址。
- 配置提供者:哨兵作为Redis客户端发现的权威来源:客户端连接到哨兵请求当前可靠的master的地址。如果发生故障,哨兵将报告新地址。
Redis哨兵是一个独立的进程,使用哨兵的一个典型架构图如下:
这是一张多哨兵的架构图,以保证系统足够的稳健,防止单节点哨兵故障,当然也可以只使用一个哨兵节点。
哨兵的分布式特性
Redis哨兵是一个分布式系统:
哨兵自身被设计成和多个哨兵进程一起合作运行,有多个哨兵进程合作的好处有:
- 当多个哨兵对一个master不再可用达成一致时执行故障检测。这会降低错误判断的概率。
- 即使在不是所有的哨兵都工作时哨兵也会工作,使系统健壮的抵抗故障。毕竟在故障系统里单点故障没有什么意义。
Redis的哨兵、Redis实例(master和slave)、和客户端是一个有特种功能的大型分布式系统。在这个文档里将逐步从为了理解哨兵基本性质需要的基础信息,到为了理解怎样正确的使用哨兵工作的更复杂的信息(这是可选的)进行介绍。
哨兵配置
1)一个健壮的部署至少需要三个哨兵实例。
2)三个哨兵实例应该放置在客户使用独立方式确认故障的计算机或虚拟机中。例如不同的物理机或不同可用区域的虚拟机。
3)sentinel + Redis实例不保证在故障期间保留确认的写入,因为Redis使用异步复制。然而有方式部署哨兵使丢失数据限制在特定时刻,虽然有更安全的方式部署它。
4)你的客户端要支持哨兵,流行的客户端都支持哨兵,但不是全部。
5)没有HA设置是安全的,如果你不经常的在开发环境测试,在生产环境他们会更好。你可能会有一个明显的错误配置只是当太晚的时候。
6)Sentinel,Docker,或者其他形式的网络地址交换或端口映射需要加倍小心:Docker执行端口重新映射,破坏Sentinel自动发现其他的哨兵进程和master的slave列表。稍后在这个文档里检查关于Sentinel和Docker的部分,了解更多信息。
二、部署哨兵
在理解哨兵的原理前,接下来我们首先实际使用一下哨兵,来了解哨兵是如何工作的。下面我们在同一台主机上配置一个一主多从的Redis架构,使用一个哨兵进程。Redis实例分别是主数据库(6379),从数据库(6380,6381)
1 2 3 |
redis-server --port 6379 & redis-server --port 6380 --slaveof 127.0.0.1 6379 & redis-server --port 6381 --slaveof 127.0.0.1 6379 & |
我们使用redis命令行客户端来获取复制状态,以保证复制配置正确。
首先是主数据库
1 2 3 4 5 6 7 8 9 10 11 |
127.0.0.1:6379> info replication # Replication role:master connected_slaves:2 slave0:ip=127.0.0.1,port=6380,state=online,offset=295,lag=0 slave1:ip=127.0.0.1,port=6381,state=online,offset=295,lag=1 master_repl_offset:295 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:294 |
可见其连接了两个从数据库,配置正确,然后用同样的方法查看两个从数据库。
1 2 3 4 5 |
127.0.0.1:6380> info replication # Replication role:slave master_host:127.0.0.1 master_port:6379 |
1 2 3 4 5 |
127.0.0.1:6381> info replication # Replication role:slave master_host:127.0.0.1 master_port:6379 |
出现如上信息证明主从复制没有问题。
接下来开始配置哨兵,建立一个配置文件,如sentinel.conf,内容为:
1 2 3 |
$ mkdir /data/sentinel $ cat /data/sentinel/sentinel.conf sentinel monitor mymaster 127.0.0.1 6379 1 |
其中mymaster表示要监控的主数据库的名字,可以自己定义一个,这个名字必须仅由大小写字母、数字和“.-_”这三个字符组成。后两个参数表示主数据库的地址和端口号,这里我们要监控的是主数据库6379。最后的”1″表示最低通过票数,但是Quorum(票数)需要解释一下:
- quorum是Sentinel需要协商同意master是否可到达的数量。为了真正的标记slave为失败,并最终是否需要启动一个故障转移进程。
- quorum只用于检测故障。为了实际执行故障转移,Sentinel需要选举leader并进行授权。这只发生在大多数Sentinel进程的选举。
例如你有5个哨兵进程,并且给定的master的quorum的值设置为2,这是将发生的事情:
- 如果两个哨兵进程同时同意master是不可到达的,这两个哨兵的其中一个将启动一个故障转移(sentinel自身会根据Raft算法选出一个leader sentinel去进行故障转移,这样可以保证同一时间只有一个哨兵节点来执行故障转移)。
- 如果至少三个哨兵可获得,故障转移将会被授权并真实的启动。
实际上这意味着在故障转移期间如果大多数的Sentinel进程不能通信,Sentinel将会永不启动故障转移(即在少数分区没有故障转移)。
除了上面一行最重要的配置外,还可以设置如下参数:
1 2 3 4 5 6 7 |
port 5000 sentinel down-after-milliseconds mymaster 4000 sentinel failover-timeout mymaster 18000 sentinel parallel-syncs mymaster 1 sentinel notification-script mymaster /data/script/notify.sh sentinel auth-pass mymaster 123456 protected-mode no |
port – 指定哨兵的运行端口。
down-after-milliseconds:指定Sentinel认为服务器已经断线所需的毫秒数,如果服务器在给定的毫秒数之内,没有返回Sentinel发送的PING命令的回复,或者返回一个错误,那么Sentinel将这个服务器标记为主观下线(subjectively down,简称SDOWN)。不过只有一个Sentinel将服务器标记为主观下线并不一定会引起服务器的自动故障迁移,只有在足够数量的Sentinel都将一个服务器标记为主观下线之后,服务器才会被标记为客观下线(objectively down,简称ODOWN),这时自动故障迁移才会执行。而将服务器标记为客观下线所需的Sentinel数量是由对主服务器的配置决定。
failover-timeout:failover过期时间,当failover开始后,在此时间内仍然没有触发任何failover操作,当前sentinel将会认为此次failoer失败。
parallel-syncs:选项指定了在执行故障转移时,最多可以有多少个从服务器同时对新的主服务器进行同步,这个数字越小,完成故障转移所需的时间就越长。如果从服务器被设置为允许使用过期数据集(参见对redis.conf文件中对slave-serve-stale-data选项的说明),那么你可能不希望所有从服务器都在同一时间向新的主服务器发送同步请求, 因为尽管复制过程的绝大部分步骤都不会阻塞从服务器,但从服务器在载入主服务器发来的RDB文件时, 仍然会造成从服务器在一段时间内不能处理命令请求:如果全部从服务器一起对新的主服务器进行同步,那么就可能会造成所有从服务器在短时间内全部不可用的情况出现。你可以通过将这个值设为1来保证每次只有一个从服务器处于不能处理命令请求的状态。
notification-script:当failover时,可以指定一个”通知”脚本用来告知系统管理员,当前集群的情况。脚本被允许执行的最大时间为60秒,如果超时,脚本将会被终止(KILL) 。
auth-pass:如果redis配置有密码,那么哨兵也需要指定密码(主从密码需要一致)。
protected-mode:redis 3.0开始多了安全保护,默认是开启的;如果redis的protected-mode关闭了,那么哨兵的安全保护也需要关闭,不然会有报错。
PS:上面的配置只是监控了一组Redis复制实例,如果要监控两组直接把相同的配置再次添加一组即可,只需要把mymaster的名字改一下。需要注意的是,配置哨兵监控一个系统时,只需要配置其监控主数据库即可,哨兵会自动发现所有复制该主数据库的从数据库。
主观下线和客观下线
前面说过,Redis的Sentinel中关于下线(down)有两个不同的概念:
- 主观下线(Subjectively Down,简称SDOWN)指的是单个Sentinel实例对服务器做出的下线判断。
- 客观下线(Objectively Down,简称ODOWN)指的是多个Sentinel实例在对同一个服务器做出SDOWN判断,并且通过SENTINEL is-master-down-by-addr命令互相交流之后,得出的服务器下线判断。(一个Sentinel可以通过向另一个Sentinel发送SENTINEL is-master-down-by-addr命令来询问对方是否认为给定的服务器已下线。)
如果一个服务器没有在master-down-after-milliseconds选项所指定的时间内,对向它发送PING命令的Sentinel返回一个有效回复(valid reply), 那么Sentinel就会将这个服务器标记为主观下线。服务器对PING命令的有效回复可以是以下三种回复的其中一种:
- 返回 +PONG 。
- 返回 -LOADING错误。
- 返回 -MASTERDOWN错误。
如果服务器返回除以上三种回复之外的其他回复, 又或者在指定时间内没有回复PING命令,那么Sentinel认为服务器返回的回复无效(non-valid)。注意, 一个服务器必须在master-down-after-milliseconds毫秒内,一直返回无效回复才会被Sentinel标记为主观下线。
举个例子, 如果master-down-after-milliseconds选项的值为30000 毫秒(30 秒),那么只要服务器能在每29秒之内返回至少一次有效回复,这个服务器就仍然会被认为是处于正常状态的。从主观下线状态切换到客观下线状态并没有使用严格的法定人数算法(strong quorum algorithm),而是使用了流言协议: 如果Sentinel在给定的时间范围内,从其他Sentinel那里接收到了足够数量的主服务器下线报告,那么Sentinel就会将主服务器的状态从主观下线改变为客观下线。如果之后其他Sentinel不再报告主服务器已下线,那么客观下线状态就会被移除。客观下线条件只适用于主服务器,对于任何其他类型的Redis实例, Sentinel在将它们判断为下线前不需要进行协商,所以从服务器或者其他Sentinel永远不会达到客观下线条件。只要一个Sentinel发现某个主服务器进入了客观下线状态,这个Sentinel就可能会被其他Sentinel推选出,并对失效的主服务器执行自动故障迁移操作。
接下来指定启动sentinel进程,并将上述配置文件的路径传递给哨兵。
三、启动哨兵
1 |
redis-sentinel /data/sentinel/sentinel.conf & |
另外你可以直接使用可执行的redis-server在哨兵模式下启动。
1 |
redis-server /data/sentinel/sentinel.conf --sentinel |
两种方式效果都是一样的。
然而在启动哨兵时必须使用一个配置文件,因为这个配置文件将用于系统保存当前状态和在重启时重新加载。哨兵会在没有指定配置文件或指定的配置文件不可写的时候拒绝启动。
Redis哨兵默认监听26379 TCP端口,所以为了哨兵的正常工作,你的26379端口必须开放接收其他哨兵实例的IP地址的连接。否则哨兵不能通信和商定做什么,故障转移将永不会执行。
启动哨兵后,哨兵输出信息如下:
1 2 3 4 |
8874:X 09 Aug 11:12:54.123 # Sentinel ID is 8d593e37d372843fd34cce032f2cde17aee1856d 8874:X 09 Aug 11:12:54.123 # +monitor master mymaster 127.0.0.1 6379 quorum 1 8874:X 09 Aug 11:12:54.123 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379 8874:X 09 Aug 11:12:54.123 * +slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379 |
其中+monitor表示监控主实例,后面的quorum 1表示只要有一个投票节点就会进行故障切换。
其中+slave表示新发现了从数据库,可见哨兵成功地发现了两个从数据库。现在哨兵已经在监控这3个redis实例了。
开始使用Sentinel最明显的事情是,检查master是否正常监控:
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 |
127.0.0.1:5000> sentinel master mymaster 1) "name" 2) "mymaster" 3) "ip" 4) "127.0.0.1" 5) "port" 6) "6379" 7) "runid" 8) "4b5b77d09f0dbc232775ba657f5e5870f3f299be" 9) "flags" 10) "master" 11) "link-pending-commands" 12) "0" 13) "link-refcount" 14) "1" 15) "last-ping-sent" 16) "0" 17) "last-ok-ping-reply" 18) "960" 19) "last-ping-reply" 20) "960" 21) "down-after-milliseconds" 22) "10000" 23) "info-refresh" 24) "9014" 25) "role-reported" 26) "master" 27) "role-reported-time" 28) "29102" 29) "config-epoch" 30) "0" 31) "num-slaves" 32) "2" 33) "num-other-sentinels" 34) "0" 35) "quorum" 36) "1" 37) "failover-timeout" 38) "180000" 39) "parallel-syncs" 40) "1" |
它会打印了大量的关于master的信息。有一些是对于我们非常有用的:
- num-other-sentinels是0,证明我们的这个sentinel是单点。如果这个值是2,那么我们知道Sentinel已经检测到这个master的其他两个Sentinel(加自身节点,一共有3个sentinel节点)。如果你检查日志,你会看到生成了+sentine事件。
- flags是master,如果master down了,我们在这里希望看到s_down或者o_down。
- num-slaves这里是2,所以Sentinel还检测到master有两个slave。
为了更多的探索这个实例,你可能想尝试下面的两个命令:
1 2 |
sentinel slaves mymaster sentinel sentinels mymaster |
第一个会打印连接到master的slave的信息,第二个是关于其他Sentinel的信息。
六、获取当前master地址
我们已经说明,Sentinel还可以作为客户端的配置提供者,连接到一组master和slave。因为故障转移和重新配置,客户端没有办法指定谁是当前活动master,所以Sentinel提供了一个API解决这个问题:
1 2 3 |
127.0.0.1:5000] SENTINEL get-master-addr-by-name mymaster 1) "127.0.0.1" 2) "6379" |
七、测试故障转移
这时我们将主数据库(即运行在6379端口上的Redis实例)关闭,等待指定时间后(可以配置,默认为30秒)哨兵会输出如下内容:
1 2 |
9372:X 09 Aug 11:17:16.651 # +sdown master mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:16.651 # +odown master mymaster 127.0.0.1 6379 #quorum 1/1 |
其中+sdown表示哨兵主观认为主数据库停止服务了,而+odown则表示哨兵客观认为主数据库停止服务了,关于主观和客观的区别前面已经介绍过了。此时哨兵开始执行故障恢复,即挑选一个从数据库,将其升格为主数据库,同时输出如下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
9372:X 09 Aug 11:17:16.651 # +try-failover master mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:16.651 # +vote-for-leader c72b5bc1ba1de4604b20d1d3ac7259fa5be94ddd 1 9372:X 09 Aug 11:17:16.651 # +elected-leader master mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:16.651 # +failover-state-select-slave master mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:16.709 # +selected-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:16.709 * +failover-state-send-slaveof-noone slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:16.800 * +failover-state-wait-promotion slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:17.681 # +promoted-slave slave 127.0.0.1:6381 127.0.0.1 6381 @ mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:17.681 # +failover-state-reconf-slaves master mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:17.756 * +slave-reconf-sent slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:18.705 * +slave-reconf-inprog slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:18.705 * +slave-reconf-done slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:18.763 # +failover-end master mymaster 127.0.0.1 6379 9372:X 09 Aug 11:17:18.763 # +switch-master mymaster 127.0.0.1 6379 127.0.0.1 6381 9372:X 09 Aug 11:17:18.763 * +slave slave 127.0.0.1:6380 127.0.0.1 6380 @ mymaster 127.0.0.1 6381 9372:X 09 Aug 11:17:18.763 * +slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381 9372:X 09 Aug 11:17:48.799 # +sdown slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381 |
+try-failover:表示哨兵开始进行故障恢复;
+elected-leader:表示赢得选举,可以进行故障迁移操作了。
+selected-slave:选择一个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端口的从数据库被升格为主数据库;同时两个+slave则列出了新的主数据库的两个从数据库,端口分别为6380和6379.其中6379就是之前停止主数据库,可见哨兵并没有彻底清除停止服务的实例的信息,这是因为停止服务的实例有可能会在之后的某个时间恢复服务,这时哨兵会让其重新加入进来,所以当实例停止服务后,哨兵会更新该实例的信息,使得当其重新加入后可以按照当前信息继续对外提供服务。此例中6379端口的主数据库实例停止服务了,而6381端口的从数据库已经升格为主数据库,当6379端口的实例恢复服务后,会转变为6381端口实例的从数据库来运行,所以哨兵将6379端口实例的信息修改成了6380端口实例的从数据库。
+sdown:表示该主节点处于主观下线。对应的还有-sdown表示给定节点不再处于主观下线。
故障恢复完成后,可以使用Redis命令行客户端重新检查6380和6381两个端口上的实例的复制信息:
1 2 3 4 5 |
127.0.0.1:6381> info replication # Replication role:master connected_slaves:1 slave0:ip=127.0.0.1,port=6380,state=online,offset=135894,lag=1 |
1 2 3 4 5 |
127.0.0.1:6380> info replication # Replication role:slave master_host:127.0.0.1 master_port:6381 |
可以看到6381端口上的实例已经确实升格为主数据库了,同时6381端口上的实例是其从数据库。整个故障恢复过程就此完成,哨兵会重新监控新的主数据库,他自己会重写配置文件信息。
那么此时我们将6379端口上的实例重新启动,会发生什么情况呢?首先哨兵会监控到这一变化,并输出:
1 2 |
9372:X 09 Aug 12:28:24.593 * +reboot slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381 9372:X 09 Aug 12:28:24.593 * +convert-to-slave slave 127.0.0.1:6379 127.0.0.1 6379 @ mymaster 127.0.0.1 6381 |
-sdown表示实例6379已经恢复服务了(与+sdown相反),同时+convert-to-slave表示将6379端口的实例设置为6381端口实例的从数据库。这时使用Redis命令客户端查看6379端口实例的复制信息为:
1 2 3 4 5 6 |
127.0.0.1:6379> info replication # Replication role:slave master_host:127.0.0.1 master_port:6381 master_link_status:up |
6379端口已经启动,同时看6381实例的复制信息,复制节点已经已经增加了6379的实例:
1 2 3 4 5 6 |
127.0.0.1:6381> info replication # Replication role:master connected_slaves:2 slave0:ip=127.0.0.1,port=6380,state=online,offset=521960,lag=1 slave1:ip=127.0.0.1,port=6379,state=online,offset=521960,lag=1 |
Sentinel状态的持久化
Sentinel的状态会被持久化在Sentinel配置文件里面。每当Sentinel接收到一个新的配置, 或者当领头Sentinel为主服务器创建一个新的配置时,这个配置会与配置纪元一起被保存到磁盘里面,这意味着停止和重启Sentinel进程都是安全的。
Sentinel标准配置文件
1 2 3 4 5 6 |
port 26379 # 6379 sentinel monitor mymaster 10.99.73.11 6379 1 sentinel down-after-milliseconds mymaster 4000 sentinel failover-timeout mymaster 18000 sentinel parallel-syncs mymaster 1 |
到这里就结束了sentinel部分,关于sentinel集群可以看下一章节。