sentinal,中文名哨兵,是 redis 集群架构中非常重要的一个组件,主要功能如下:
① 集群监控,负责监控 redis master 和 slave 进程是否正常工作
② 消息通知,如果某个 redis 实例有故障,那么哨兵负责发送信息作为报警通知给管理员
③ 故障转移,如果 master node 挂掉了,会自动转移到 slave node 上
④ 配置中心,如果故障转移发生了,通知 client 客户端新的 master 地址
哨兵本身也是分布式的,作为一个哨兵集群去运行,互相协同工作
① 故障转移时,判断一个 master node 是宕机了,需要大部分的哨兵都同意才行,涉及到了分布式选举的问题
② 即使部分哨兵节点挂掉了,哨兵集群还是能正常工作的,因为如果一个作为高可用机制重要组成部分的故障转移系统本身是单点的,那就很坑了
哨兵集群必须部署 2 个以上的节点,如果哨兵集群仅仅部署了 2 个哨兵实例,quorum = 1
+----+ +----+ | M1 |---------| R1 | | S1 |---------| S2 | +----+ +----+
Configuration:quorum = 1
master 宕机,s1 和 s2 中只要有 1 个哨兵认为 master 宕机就可以进行切换,同时 s1 和 s2 中会选举出一个哨兵来执行故障转移。同时这个时候,需要 majority,也就是大多数哨兵都是运行的,2个哨兵的 majority 就是2(2的majority=2,3的majority=2,4的majority=3,5的majority=3),2个哨兵都运行着,就可以允许执行故障转移;但是如果整个 M1 和 S1 运行的机器宕机了,那么哨兵只有1个了,此时就没有 majority 来允许执行故障转移,虽然另外1台机器中还有一个R1,但是故障转移不会执行
+----+ | M1 | | S1 | +----+ | +----+ | +----+ | R2 |----+----| R3 | | S2 | | S3 | +----+ +----+
Configuration: quorum = 2
如果M1所在机器宕机了,那么三个哨兵还剩下2个,S2和S3可以一致认为master宕机,然后选举出一个来执行故障转移;同时3个哨兵的majority是2,所以还剩下的2个哨兵运行着,就可以允许执行故障转移
因为 master->slave 的复制是异步的,所以可能有部分数据还没复制到 slave,master 就宕机了,此时这些部分数据就丢失了
脑裂,也就是说,某个 master 所在机器突然脱离了正常的网络,跟其他 slave 机器不能连接,但是实际上 master 还运行着;此时哨兵可能就会认为 master 宕机了,然后开启选举,将其他 slave 切换成了 master。这个时候,集群里就会出现有两个 master,也就是所谓的脑裂
此时虽然某个 salve 被切换成了 master,但是 client 还没来得及切换到新的 master,还继续写向旧 master 的数据可能也丢失了,因此旧 master 再次恢复 的时候,会被作为一个 slave 挂到新的 master 上去,自己的数据会清空,重新从新的 master 恢复数据
# 下面2个配置可以减少异步复制和脑裂导致的数据丢失 # 要求至少有1个 slave,数据复制和同步的延迟不能超过10秒 # 如果说一旦所有的 slave,数据复制和同步的延迟都超过了10秒钟 # 那么这个时候,master 就不会再接受任何请求了 min-slaves-to-write 1 min-slaves-max-lag 10
减少异步复制的数据丢失
有了 min-slaves-max-lag 这个配置,就可以确保说,一旦 slave 复制数据和 ack 延时太长,就认为可能 master 宕机后损失的数据太多了,那么就拒绝写请求,这样可以把 master 宕机时由于部分数据未被同步到 slave 导致的数据丢失降低的可控范围内
减少脑裂的数据丢失
如果一个 master 出现了脑裂,跟其他 slave 丢了连接,那么上面两个配置可以确保说,如果不能继续给指定数量的 slave 发送数据,而且 slave 超过 10秒 没有给自己 ack 信息,那么就直接拒绝客户端的写请求。这样脑裂后的旧的 master 就不会接受 client 的新数据,也就避免了数据丢失
上面的配置就确保了,如果跟任何一个 slave 丢了连接,在 10秒 后发现没有 slave 给自己 ack,那么就拒绝新的写请求。因此在脑裂场景下,最多就丢失 10秒 的数据
sdown 和 odown 两种失败状态
sdown 是主观宕机,就一个哨兵,如果自己觉得一个 master 宕机了,那么就是主观宕机
odown 是客观宕机,如果 quorum 数量的哨兵觉得一个 master 宕机了,那么就是客观宕机
sdown 达成的条件很简单,如果一个哨兵 ping 一个 master,超过了 is-master-down-after-milliseconds 指定的毫秒数之后,就主观认为 master 宕机
sdown 到 odown 转换的条件很简单,如果一个哨兵在指定时间内,收到了 quorum 指定数量的其他哨兵也认为那个 master 是 sdown了,那么就认为是 odown了,客观认为 master 宕机
哨兵互相之间的发现,是通过 redis 的 pub/sub 系统实现的,每个哨兵都会往 sentinel:hello 这个channel 里发送一个消息,这时候所有其他哨兵都可以消费到这个消息,并感知到其他的哨兵的存在
每隔2秒钟,每个哨兵都会往自己监控的某个 master + slaves 对应的 sentinel:hello channel 里发送一个消息,内容是自己的 host、ip 和 runid 还有对这个 master 的监控配置
每个哨兵也会去监听自己监控的每个 master + slaves 对应的 sentinel:hello channel,然后去感知到同样在监听这个 master + slaves 的其他哨兵的存在
每个哨兵还会跟其他哨兵交换对 master 的监控配置,互相进行监控配置的同步
哨兵会负责自动纠正 slave 的一些配置,比如 slave 如果要成为潜在的 master 候选人,哨兵会确保 slave 在复制现有 master 的数据;如果 slave 连接到了一个错误的 master 上,比如故障转移之后,那么哨兵会确保它们连接到正确的 master 上
如果一个 master 被认为 odown 了,而且 majority 哨兵都允许了主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个 slave 来
会考虑 slave 的一些信息
如果一个 slave 跟 master 断开连接已经超过了 down-after-milliseconds 的 10倍,外加 master 宕机的时长,那么 slave 就被认为不适合选举为 master
(down-after-milliseconds * 10) + milliseconds_since_master_is_in_SDOWN_state
接下来会对 slave 进行排序
每次一个哨兵要做主备切换,首先需要 quorum 数量的哨兵认为 odown,然后选举出一个哨兵来做切换,这个哨兵还得得到 majority 哨兵的授权,才能正式执行切换
如果 quorum < majority,比如5个哨兵,majority就是3,quorum设置为2,那么就3个哨兵授权就可以执行切换
但是如果 quorum >= majority,那么必须 quorum数量的哨兵都授权,比如5个哨兵,quorum是5,那么必须5个哨兵都同意授权,才能执行切换
哨兵会对一套redis master+slave进行监控,有相应的监控的配置。执行切换的那个哨兵,会从要切换到的新master(slave->master)那里得到一个configuration epoch,这就是一个version号,每次切换的version号都必须是唯一的
如果第一个选举出的哨兵切换失败了,那么其他哨兵,会等待failover-timeout时间,然后接替继续执行切换,此时会重新获取一个新的configuration epoch,作为新的version号
哨兵完成切换之后,会在自己本地更新生成最新的master配置,然后同步给其他的哨兵,就是通过之前说的pub/sub消息机制.这里之前的version号就很重要了,因为各种消息都是通过一个channel去发布和监听的,所以一个哨兵完成一次新的切换之后,新的master配置是跟着新的version号的.其他的哨兵都是根据版本号的大小来更新自己的master配置的
三台机器配置哨兵,配置文件sentinal.conf
# 主要配置 sentinal.conf # 端口配置无所谓,这里以 5000 为端口号 # 哨兵默认端口 26379 port 5000 # bind 自己的 ip bind 192.168.1.132 # 配置监听的主服务器 sentinel monitor mymaster 192.168.1.132 6379 2 # 设置主节点多长时间没有响应就代表挂了, # 后边 30000 是毫秒,也就是 30 秒。 sentinel down-after-milliseconds mymaster 30000 # 同步的过程中,多长时间完成算有效 sentinel failover-timeout mymaster 60000 # 故障转移时,最多有多少个从节点对新的主节点进行同步 sentinel parallel-syncs mymaster 1
# 主要配置 sentinal.conf # 端口配置无所谓,这里以 5000 为端口号 port 5000 # bind 自己的 ip bind 192.168.1.133 # 配置监听的主服务器 sentinel monitor mymaster 192.168.1.132 6379 2 # 设置主节点多长时间没有响应就代表挂了, # 后边 30000 是毫秒,也就是 30 秒。 sentinel down-after-milliseconds mymaster 30000 # 同步的过程中,多长时间完成算有效 sentinel failover-timeout mymaster 60000 # 故障转移时,最多有多少个从节点对新的主节点进行同步 sentinel parallel-syncs mymaster 1
# 主要配置 sentinal.conf # 端口配置无所谓,这里以 5000 为端口号 port 5000 # bind 自己的 ip bind 192.168.1.134 # 配置监听的主服务器 sentinel monitor mymaster 192.168.1.132 6379 2 # 设置主节点多长时间没有响应就代表挂了, # 后边 30000 是毫秒,也就是 30 秒。 sentinel down-after-milliseconds mymaster 30000 # 同步的过程中,多长时间完成算有效 sentinel failover-timeout mymaster 60000 # 故障转移时,最多有多少个从节点对新的主节点进行同步 sentinel parallel-syncs mymaster 1
# redis-sentinel sentinel.conf 文件地址 # 在 三台机器分别执行下面语句启动 # redis-sentinel 可能需要配置,默认情况下在 /etc/init.d 目录下运行 redis-sentinel /etc/redis/sentinel.conf
redis-cli -h 192.168.1.133 -p 5000 sentinel master mymaster SENTINEL slaves mymaster SENTINEL sentinels mymaster