哨兵是redis集群架构中非常重要的一个组件,哨兵模式基于主从复制模式
,当主从复制模式下的master主节点宕机,通过选举一个master继续提供服务;
1>.集群监控:
负责监控redis master和slave进程是否正常工作;
2>.消息通知:
如果某个redis实例有故障,那么哨兵负责发送消息作为报警通知给管理员;
3>.故障转移:
如果master node挂掉了,会自动转移到slave node上,会选举一个slave node作为新的master node,让其他的slave node重新连接到新选出的master node;
4>.配置中心:
如果故障转移发生了(/选举了新的master),通知client客户端新的master地址
哨兵本身也是分布式的,作为一个哨兵集群去运行,相互协调工作
1>.故障转移时,判断一个master node宕机了,需要大部分的哨兵(节点)都同意才行,这就涉及到了分布式选举问题;
2>.即使部分哨兵节点挂掉了,哨兵集群中剩下的节点还是可以正常工作的,因为如果作为高可用机制的一个重要组成部分,故障转移系统本身是单点的,那就太坑了;
3>.目前采用的是sentinel 2版本(对于redis3.0以上版本),
相对于sentinel 1版本来说,sentinel 2版本重写了很多代码,主要是让故障转移机制和算法变得更加健壮和简单;
1>.哨兵至少需要3个实例(/节点)
来保证自己的健壮性;
2>.[哨兵 + redis主从]的部署架构,是不能保证数据零丢失的,只能保证redis集群的高可用性;
3>.对于[哨兵+redis主从]这种复杂的部署架构,尽量在测试环境和生产环境都进行充足的测试和演练;
1>.哨兵集群在进行redis故障判断,主备切换的时候需要配置一个参数(qurum),这个参数表示有多少个哨兵节点一致认为master node宕机了(master node状态从sdown变成odown),
就可以准备进行主备切换,之后会选出一个新的master node准备故障转移,同时还需要majority数量,也就是集群中大多数哨兵都是运行的,得到majority大多数哨兵的授权
,才能正式进行主备切换;
majority=n/2+1; quorum=n-1,(0
1>.每次一个哨兵节点要做主备切换,首先需要quorum数量的哨兵认为master node宕机了,然后选举出一个哨兵来做切换,这个哨兵还得得到majority数量的哨兵的授权,才能正式执行切换
①.如果
quorum < majority,
比如5个哨兵,majority就是3,quorum设置为2,那么就3个哨兵授权就可以执行切换;
②.如果quorum >= majority
,那么必须quorum数量的哨兵都授权
,比如5个哨兵,quorum是5,那么必须5个哨兵都同意授权,才能执行切换;
2个哨兵节点的majority=2;
3个哨兵节点的majority=2;
4个哨兵节点的majority=3;
5个哨兵节点的majority=3;
③.假设集群中只有2个节点,quorum=1,majority=2,如果一个节点宕机了,另外一个哨兵准备故障转移/主备切换,但是无法满足majority=2,无法进行真正的故障转移/主备切换;
④.假设集群中只有3个节点,quorum=2,majority=2,如果一个节点宕机了,另外两个哨兵选出一个其中一个哨兵准备故障转移/主备切换,而此时可以满足majority=2,那么就可以进行正式切换;
所以,哨兵集群中至少需要三个实例才能正常工作!!!
因为master–>slave的复制是异步的,所以有可能有部分数据还没有复制到slave,master就宕机了,那么这部分还没有复制过去的数据就丢失了;当故障转移/主备切换之后,client所有的写请求,都会基于这个新的master node,那么之前旧的master node内存中那些还没来得及复制到slave node中的数据就丢失了;
1>.脑裂,也就是说,某个master node所在的机器突然脱离了正常的网络,跟其他的slave node暂时失联了(但是实际上该master node还活着),此时其他的哨兵可能会认为master node已经宕机了,然后开始选举,将某个slave node切换成master node进行故障转移,这个时候集群就会有两个master node,这就是所谓的脑裂;
2>.此时虽然经过故障转移将某个slave node切换成了master node,但是有可能client客户端还没来得及切换到新的master node,有可能还继续和之前的那个master 进行通信(写入操作),当之前旧的master node所在机器网络恢复之后,该master node就会作为一个slave node挂载到后来选举出来的新的master node上,然后清空自己的数据,重新从新的master node上去同步数据,那么在故障期间client写入(到出现网络故障master node)的数据就丢失了;
1>.在集群所有的节点
配置文件中添加以下配置:
min-slaves-to-write 1
min-slaves-max-lag 10
上面两个配置意思是说,要求至少有1个slave,数据复制和同步的延迟不能超过10秒;如果一旦所有的slave,数据复制和同步的延迟都超过了10秒钟,那么这个时候,master就不会再接收任何请求了,可以减少异步复制和脑裂导致的数据丢失
有了min-slaves-max-lag
这个配置,就可以确保一旦slave复制数据速度太慢和ack延时太长,导致与master node上的数据相差/落后太多(超过10s),那么就认为可能master宕机后丢失的数据太多了,那么master node就会拒绝client写请求,然后client就把要写入的数据(/写入的指令)暂时缓存到自己内存中,然后再尝试写入到其他的master node或者直接停顿一段时间(等到master 和 slave node数据同步之后)然后在写[一般来说会在client做降级/限流处理,先将数据写入到磁盘中,然后对外减慢请求涌入速度;或者在client端将数据临时灌入一个kafka消息队列中每隔10分钟去队列中取出数据发回master],这样可以把master宕机时未同步到slave丢失数据降低到可控范围内;
1>.如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己返回ack消息,那么就直接拒绝客户端的写请求;这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失;
2>.上面的配置就确保了,如果跟任何一个slave丢了连接,在10秒后发现没有slave给自己返回ack,那么就拒绝client新的写请求[采取上面的client端降级/限流处理方案];因此在脑裂场景下,最多就丢失10秒的数据;
客观=quorum数量的主观
1>.sdown达成的条件很简单,如果一个哨兵ping一个master,超过了is-master-down-after-milliseconds指定的毫秒数之后,就主观认为master宕机;
2>.sdown到odown转换的条件很简单,如果一个哨兵在指定时间内,收到了quorum数量的其他哨兵也认为master是sdown了的消息/通知,那么所有的哨兵转换成是odown了,
客观认为master宕机;
1>.哨兵互相之间的发现是通过redis的pub/sub(发布订阅)系统实现的
,每个哨兵都会往__sentinel__:hello
这个channel通道里发送一个消息,这时候由于所有哨兵都订阅了这个消息,那么其他哨兵都可以消费到这个消息,每个哨兵并感知到其他的哨兵的存在;
2>.每隔两秒钟,
每个哨兵都会往自己监控的某个master+slaves集群对应的__sentinel__:hello
的channel通道里发送一个消息,内容是自己的host、ip和runid还有对这个master的监控配置;
3>.每个哨兵也会去监听自己监控的每个master+slaves对应的__sentinel__:hello
的channel,然后去感知同样也在监听这个master+slaves集群对应通道的其他哨兵的存在;
4>.每个哨兵还会通过这个pub/sub(发布订阅)系统跟其他哨兵交换对master的监控配置,互相进行监控配置的同步;
哨兵会负责自动纠正slave的一些配置,比如slave如果要成为潜在的master候选人,哨兵会确保slave在复制现有master的数据,如果slave连接到了一个错误的master上,比如故障转移之后,那么哨兵会确保它们连接到正确的master上;
1>.如果一个master被认为odown客观宕机了,而且majority数量的哨兵都允许了主备切换,那么某个哨兵就会执行主备切换操作,此时首先要选举一个slave来准备故障转移;
2>.(选举算法)选举slave时会考虑的一些因素:
①.跟master断开连接的时长;
②.slave优先级;
③.复制offset;
④.run id
3>.如果一个slave跟master断开连接已经超过了(down-after-milliseconds的10倍外加master宕机的时长
),那么这个slave就被认为不适合选举为master;
(down-after-milliseconds * 10) +milliseconds_since_master_is_in_SDOWN_state
4>.然后会对每个slave进行排序:
①.按照slave优先级进行排序,slave priority值越小,优先级就越高;
②.如果slave priority值相同,那么看replica offset,哪个slave的offset越靠后(大),那么就表示哪个slave复制了越多的数据,他的优先级就越高;
③.如果上面两个条件都相同,那么选择run id比较小的那个slave来为故障转移做准备
1>.哨兵会对一套redis的[master+slave]集群进行监控,有相应的监控的配置;
2>.执行切换的那个哨兵,在切换之前会从要切换到的新master(salve->master)那里得到一个configuration epoch,这就是一个version号,每次切换的version号都必须是唯一的;
3>.如果第一个选举出的哨兵切换失败了,那么第二个哨兵会等待failover-timeout时间,然后接替(第一个选举出的哨兵)继续执行切换,此时会重新获取一个新的configuration epoch,作为新的version号;
1>.哨兵完成切换之后,会在自己本地更新生成最新的master配置,然后同步给其他的哨兵,就是通过之前说的pub/sub(发布/订阅)消息机制;
2>.这里新的master的version号就很重要了,因为各种消息都是通过一个channel去发布和监听的,所以一个哨兵完成一次新的切换之后,新的master配置是跟着新的version号的;
3>.其他的哨兵都是根据版本号的大小来更新自己的master配置的;
注意:哨兵模式是基于主从复制(一主多从)模式,所以先要配置主从复制
[三台机器]
1>.安装redis-3.2.8,配置主从复制
2>.sentinel哨兵配置
①.创建一个存放哨兵配置文件的文件夹
# mkdir -p /etc/sentinal/config
②.创建存放哨兵数据文件的文件夹
# mkdir -p /var/sentinal/data # mkdir -p /var/sentinal/log
③.创建哨兵配置文件
sentinel.conf
# cd /etc/sentinal/config # vim sentinel.conf
④添加如下内容:
port 5000 bind 0.0.0.0 dir /var/sentinal/data sentinel monitor mymaster 192.168.1.121 6379 2 sentinel down-after-milliseconds mymaster 30000 sentinel failover-timeout mymaster 60000 sentinel parallel-syncs mymaster 1 daemonize yes logfile /var/sentinal/log/sentinel.log
//参数说明:
- port [xxx] //哨兵端口,默认26379,不能跟其他机器在指定端口连通;只能在本地访问;
所有节点保持一致!!
- bind [xxx] //哨兵集群中每个(当前)节点/服务器IP地址,
保持唯一!
- dir [xxx] //指定哨兵数据文件存放目录;
所有节点保持一致!!
- sentinel monitor [master-group-name] [hostname/ip] [port] [quorum] //哨兵集群中master节点的组名称(随便取),IP地址,当前redis端口,quorum值;
所有节点保持一致!!
- sentinel down-after-milliseconds [master-group-name] [xxx] //超过多少毫秒跟一个指定的redis实例(master)断了连接,哨兵就可能认为这个redis实例挂了;
所有节点保持一致!!
- sentinel failover-timeout [master-group-name] [xxx] //执行故障转移的timeout超时时长;
所有节点保持一致!!
- sentinel parallel-syncs [master-group-name] [xxx] //新的master被切换之后,同时有多少个slave被切换到去连接新master,重新做同步,数字越低,花费的时间越多;但是如果这个数字越大,就意味着越多的slave因为replication而不可用;
所有节点保持一致!!
- daemonize //指定哨兵以守护进程/后台方式启动,这样就不会占用命令行窗口;
所有节点保持一致!!
- logfile //指定哨兵日志文件存放目录;
所有节点保持一致!!
[三台机器]
1>.先启动集群各个节点的redis服务
# cd /etc/init.d/
# ./redis_6379 start
2>.通过redis-sentinel脚本
执行sentinel进程
# redis-sentinel /etc/sentinal/sentinel.conf
①.根据提示信息,每个节点除了监控master节点之外还监控着其他哨兵节点!
②.哨兵之间,互相会自动进行发现,用的就是之前说的pub/sub,消息发布和订阅channel消息系统和机制;
1>.链接任意一个哨兵节点
# redis-cli -h 192.168.1.121 -p 5000
2>.执行哨兵相关的命令
> SENTINEL master mymaster //查看master节点信息
> SENTINEL slaves mymaster //查看(当前)slave节点信息
> SENTINEL sentinels mymaster //查看所有的非master哨兵节点信息
> SENTINEL get-master-addr-by-name mymaster //查看master节点信息
增加一台服务器,按照上面步骤进行配置即可,集群会自动发现新加入的哨兵节点;
1>.停止要剔除的哨兵节点的sentinal进程
2>.在其他所有sentinal哨兵节点上执行清理所有的master状态
> SENTINEL RESET *
3>.在所有sentinal哨兵节点上执行查看所有sentinal对数量是否达成了一致
> SENTINEL master mymaster
1>.在其他所有的哨兵上面执行重置master信息/状态
> SENTINEL RESET <mastername>
1>.通过哨兵看一下当前的master
> SENTINEL get-master-addr-by-name mymaster
2>.把master节点的redis服务关掉(sentinel服务不要关),
删除对应的pid文件
3>.查看其他哨兵节点的sentinal命令行窗口的日志,是否出现+sdown...
的字样,然后出现+odown...
的字样,就是指定的quorum数量个哨兵都认为master宕机了
主备切换具体流程说明:
①.三个哨兵进程都认为master是sdown了;
②.超过quorum指定的哨兵进程都认为sdown之后,就变为odown;
③.哨兵2是被选举为要执行后续的主备切换的那个哨兵;
④.哨兵2去新的master(slave)获取了一个新的config version;
⑤.尝试执行failover故障转移;
⑥.投票选举出一个slave被切换成master,每个哨兵都会执行一次投票;
⑦.让选举出来的这个slave执行slaveof no one,
不让它去做任何节点的slave了; 把slave提拔成master;各节点认为旧的master不再是master了;
⑧.哨兵就自动认为之前的master变成了slave了,新选出来的slave变成了master了;
⑨.哨兵去探查了一下之前的master这个salve的状态,认为它sdown了;
4>.在任意一个节点上查看master信息
5>.查看各节点哨兵的配置文件,发现master由之前的"121"自动变成了"122"
6>.redis客户端链接到新的master节点,执行读写操作
7>.再看其他节点上的数据
//数据已经同步了!
8>.查看主从架构信息
9>.旧的master(121)重新启动,查看是否被哨兵自动切换成slave节点
//旧的master启动之后,哨兵将他变成了slave,挂在新的master上!