哨兵的基础概念
Redis哨兵(sentinel)为redis提供高可用解决方案,在传统的主从复制模式中,当主节点宕机后,我们需要手动将从节点切换成主节点,并且客户端也需要同步修改配置文件中的主节点信息,当哨兵模式启用后,主从节点的切换是自动化的,客户端由原来的连接主节点到现在的客户端连接哨兵节点,主从切换时客户端几乎不会感知.
哨兵(sentinel)的作用集中在下列几个方向
- 监控:哨兵周期性不间断监控主从节点的工作状态.
- 通知:被监控redis实例出现异常时哨兵可以通过API通知系统管理员或者其他电脑应用程序.
- 自动故障转移:如果主节点不能如期正常工作,哨兵可以执行一个自动故障转移操作将其中一个从节点升级为主节点,并将原故障主节点的其他从节点重新配置为新主节点的从节点,客户端被告知新的主节点信息.
- 主从节点的配置信息提供:哨兵充当redis服务信息的权威提供者,客户端连接到哨兵为了获得当前指定服务的主节点信息,主节点故障发生后,哨兵也会通知客户端的新的主节点信息.
哨兵的分布式特性.
redis哨兵是一个分布式的系统,意味着多个哨兵节点协同工作,带来的优势如下所述.
- 主节点是否故障由多个哨兵节点认可,这就降低了哨兵误报的概率.
- 即使不是所有的哨兵节点都正常工作,也可以保证一个强健的redis复制架构.
哨兵的版本选择和运行
- 哨兵的版本
哨兵的版本分为版本1和版本2,版本1在redis2.6正式发布,但是是不稳定版本,已经遗弃,从redis2.8开始,哨兵版本2正式推出.
- 哨兵的运行
哨兵服务支持以下两种方式启动,哨兵默认监听tcp的26379端口.
redis-sentinel /path/to/sentinel.conf
redis-server /path/to/sentinel.conf --sentinel
在部署哨兵前,我们需要知道下列基础知识
- 部署哨兵至少需要三个不同的节点,且该三个节点最好相互独立,即不在同一个交换机或机柜.
- 哨兵+redis分布式系统并不能确保当故障发生时已知的写操作会被持久化到硬盘,因为redis复制是异步的,但是我们可以通过一些方法让可能丢失的写操作在指定范围内.
- 客户端需要配置哨兵信息,而不是配置主从复制中的主节点信息.
配置哨兵基本的参数
在哨兵的主配置文件sentinel.conf中,基本的配置信息如下.
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 60000
sentinel failover-timeout mymaster 180000
sentinel parallel-syncs mymaster 1
sentinel monitor resque 192.168.1.3 6380 4
sentinel down-after-milliseconds resque 10000
sentinel failover-timeout resque 180000
sentinel parallel-syncs resque 5
- sentinel monitor <master-group-name> <ip> <port> <quorum>
该参数用于指定哨兵监控一个主从复制架构中的主节点信息,其中<master-group-name>用于指定该主从复制架构在哨兵中的别名,该命名可以随意从这里我们也可以看出,哨兵可以同时对多个主从复制的架构进行监控,只要别名不重复即可, <ip>为主节点的ip,<port>为主节点的port,<quorum>的中文意思是法定人数,这里代指sentinel节点,意思就是在当前这个主从复制的架构中,有多少个哨兵节点认为当前redis主节点故障才会发生故障转移,如值为3,说明至少要有3个哨兵节点监测到主节点故障后,才会执行故障转移操作,quorum只是为了用来认定主节点故障的最低哨兵节点数量,并不负责故障转移,故障转移操作是由优先级最高的哨兵节点负责.这个优先级最高的哨兵节点是经过大多数的哨兵节点授权的.当有2个哨兵节点认为主节点故障时并不会触发故障转移,因为没有达到最低的quorum数量.
这里需要注意的是,我们只需要填写一个主从复制架构中的主节点地址端口信息即可,不需要填写从节点的信息,因为填写主节点后,哨兵会通过主节点的info命令获取该主节点的所有从节点信息.那么这里我们可以想到一个问题,如果我们把主节点的ip地址写到配置文件中,那么相当于写死了这个主ip地址,故障转移后,哨兵将从节点升级为了主节点,主节点的ip地址也会发生变化,但是配置文件记录的主ip地址还是故障以前的主节点的IP地址,这里会不会有问题?实际上不会的,刚才说到,多个哨兵节点中会授权其中一个优先级最高的哨兵节点来负责故障转移,将一个优先级最高的从节点升级为主节点,并将这个新主节点的ip地址更新到配置文件中,然后重新配置原来的从节点并让其成为新主节点的从节点,哨兵将新的主节点信息广播到其他哨兵节点,其他哨兵节点再更新自己的配置文件,到最后各个哨兵节点的额监控信息会最终达到一致性状态.
- sentinel down-after-milliseconds <master-group-name> <milliseconds >
该参数表示哨兵节点认为监视的主从架构中主节点的故障超时时间
- sentinel failover-timeout <master-group-name> <milliseconds >
该参数作用:
指定同一个哨兵节点对同一个主节点执行两次故障转移之间的时间.
指定当一个从节点从一个错误的主节点同步数据到从节点被纠正为向正确的主节点同步数据的时间.
指定取消一个正在进行的failover所需要的时间.
指定当进行故障转移时,配置所有从节点指向新的主节点所需的最大时间.
- sentinel parallel-syncs <master-group-name> <number>
这个配置项指定了在发生故障转移时主备切换时最多可以有多少个从节点同时对新的主节点进行同步,这个数字越小,完成故障恢复所需的时间就越长,但是如果这个数字越大,就意味着越多的从节点因为故障转移而不能被访问,可以通过将这个值设为1来保证每次只有一个从节点同步新的主节点.
哨兵中一些高级概念
- 主观下线(SDOWN)和客观下线(ODOWN)
一个redis主节点被哨兵节点认定处于下线时有两种不同的下线状态,第一个阶段为主观下线(Subjectively Down)状态,即当前的哨兵节点认为主节点下线,第二个阶段是客观下线(Objectively Down),当第一个主观下线阶段发生后,超过quorum个哨兵节点认为主节点下线,那么当前哨兵节点就会从其他哨兵节点得到主节点“SENTINEL is-master-down-by-addr”的命令反馈,主节点就会进入客观下线状态.
从上一节可知,主节点被认为进入主观下线(SDOWN)的条件是达到了参数down-after-milliseconds的指定超时时间,即在该时间内哨兵没有收到对主节点的ping命令返回。
哨兵节点通过每秒发送ping命令到主节点,以检测主节点是否下线.主节点的返回值包含以下三种
- PING返回+PONG正常
- PING返回-LOADING错误
- PING返回-MASTERDOWN错误
除此之外的其他返回值是无效的。
如我们将参数down-after-milliseconds设置为30000毫秒,即30秒,那么在29秒的时候。哨兵收到主节点的ping返回值为+PONG,那么我们认为主节点是正常的。
主节点被认为处于主观下线状态并不能触发哨兵的故障转移操作,它仅仅意味着主节点在当前哨兵节点看来不可达,只有到客观下线状态时,故障转移操作才会发生.这里需要注意的是,客观下线的状态只发生在监测主节点时,从节点并没有客观下线状态这一说.而如果从节点由于故障处于主观下线状态后,在遇到主节点不可达时,哨兵在进行故障转移时并不会考虑将该从节点提升为主节点
- 哨兵和从节点的自动发现机制
各个哨兵节点之间互相周期性通信并交互信息以检查哨兵是否正常工作,但是我们在配置哨兵的配置文件时却没有指定其他哨兵的地址,这是为什么呢?前面我们介绍过哨兵中只需要配置主节点的地址即可,实际上哨兵节点之间的互相通信使用的是redis的发布订阅功能,通过该功能,哨兵可以知道还有其他哨兵节点也在监控主节点和从节点信息。
哨兵节点通过主节点的info命令获取连接该主节点的其他从节点信息,并向主节点和从节点的频道 __sentinel__:hello发送hello消息,下列描述了哨兵节点的自动发现机制。
- 每一个哨兵节点每隔2秒向被监控的主节点和从节点的channel __sentinel__:hello频道发布消息,宣告它自己的ip,端口号和运行id。
- 每一个哨兵节点也会订阅被监控的主节点和从节点的channel __sentinel__:hello频道来获取未知的哨兵节点,当有新的哨兵节点加入时,新的哨兵节点会被其他哨兵认为并一起监控该主节点。
- hello消息中也包括一个完整的当前主节点的配置信息,如果一个哨兵通过订阅频道接收到的主节点的配置比自己已保存的更新,哨兵节点就会更新自己的配置信息。
- 哨兵对故障之外的主从节点进行重新配置
即使当前没有发生故障转移,哨兵节点仍然会试着尝试更新被监控节点的配置。
- 根据当前配置,redis会将一个声称自己为主节点的节点配置为当前的主节点的从节点。
- 如果一个redis从节点连接了错误的主节点,哨兵会将该从节点配置到正确的主节点的从节点。
对于哨兵节点重新配置从节点而言,错误的配置在一定的时间内必须被发现,该时间一定大于广播新配置的周期,这阻止了刚从网络故障中恢复并且包含过期信息的哨兵节在接收到更新之前尝试修改从节点配置信息,因为刚从网络故障中恢复的哨兵可能包含老的主节点数据,其他哨兵节点必须保证该刚恢复的哨兵拥有最新的配置数据才能参加到新的监控环境中继续工作。
哨兵节点也会采取更加稳健的措施来保证尽可能的将网络故障的影响降到最低。
- 原来的主节点故障转移后从网络故障恢复时会被哨兵节点配置为新主节点的从节点。
- 一旦从节点从网络故障中恢复,哨兵也会立刻对其进行配置更新。
- 从多个哨兵节点选举一个优先级最高的哨兵节点执行故障转移
虽然多个哨兵节点功能监控redis主从架构的运行,但是实际执行故障转移时只能由一个领头哨兵节点来完成,该领头哨兵节点必须得到大多数哨兵节点的授权,所以需要选举除一个优先级最高的领头节点,该选举过程只用了Raft算法。
- 当前哨兵节点发现主节点客观下线后,向其他哨兵节点发送命令告知主节点已经客观下线,并要求其他哨兵节点投票选举当前节点为领头哨兵。
- 如果目标哨兵节点没有收到其他哨兵节点的选举请求,那么就为当前节点投一票并同意选举它为领头哨兵。
- 如果当前哨兵节点获取超过半数的票数,那么当前节点就成为领头哨兵。
- 当有多个哨兵节点同时选举自己成为领头节点,则可能在一个周期内没有选出领头节点,此时每个哨兵节点将会等待一个随机时间重新发起选举请求,直到选举出领头哨兵。
- 从节点的选举与优先级。
当一个哨兵节点准备做故障转移时,会挑选一个优先级最高的从节点升级为主节点,挑选一个优先级最高的从节点遵从以下原则。
- 和主节点断开时间最短的从节点优先级最高。
- 断开时间相同时,配置文件中指定的replica-priority的值最低的优先级最高,但是该值为0代表不参加主节点选举。
- 优先级相同时,复制偏移量最大的节点优先级最高。
- 复制偏移量相同时,runid最小的优先级最高。
- 配置纪元(Configuration epochs)和配置传播(Configuration propagation)
哨兵节点执行故障转移时需要得到大部分哨兵节点的授权,这是因为当一个领头哨兵被授权时,它会获得一个唯一的配置纪元用来为主节点执行故障转移,这个配置纪元用来在故障转移后标注新的配置的版本,例如新的配置版本中主节点的ip地址发生了改变,因为大多数哨兵节点同意一个给定的配置纪元分配给一个指定的领头哨兵,没有其他哨兵会使用这个配置纪元,这就意味着每一次故障转移都会有一个新的配置版本和新的配置纪元相匹配。
除此之外哨兵节点还有一个遵守的规则,如果一个哨兵节点由于主节点需要故障转移而投票给另外一个哨兵节点同意选举为领头节点,等待一段时间后,领头节点依然会对同一个主节点执行故障转移,该时间通过failover-timeout参数指定,这也限制了同一时间只有一个哨兵节点可以执行故障转移。
一旦领头哨兵节点执行完故障转移后,便会广播新的配置信息,这样其他哨兵节点收到信息后及时更细自己的配置信息。如何判断一个故障转移操作是否完成呢,它需要哨兵节点能够在优先级最高的从节点成功转型REPLICAOF NO ONE命令,该命令会将从节点升级为主节点,稍后听过info命令便可以观察到角色已经切换为master。
此时,即使哨兵节点正在重新配置从节点的新主节,故障转移也会被认为是成功的,所有的哨兵节点都会被领头哨兵节点要求更新配置信息,这也是之前介绍的领头哨兵会被分配一个配置纪元。
每一个哨兵节点通过redis的发布订阅功能在主节点和从节点不间断的广播关于主节点的配置版本,与此同时,所以的哨兵节点也会观察其他哨兵节点广播的配置信息。
这里广播的发生在我们前面介绍的__sentinel__:hello频道。
因为每一个哨兵广播的配置都可能有不同的版本号,版本号最大的总是可以赢得版本号较小的,如在故障转移之前,哨兵节点A广播主节点的地址是192.168.1.50:6379,配置版本号是1,哨兵节点A负责故障转移操作并成功后,配置版本号变成了2,那么哨兵A在广播时指定新的主节点的地址是192.168.1.60:6379,配置版本号是2,其他哨兵节点看到哨兵A的配置版本号比自己的高,会立刻更新自己的配置数据,这也说明了在一个周期结束后,所有的哨兵节点都会根据最高的版本号更新自己的配置信息并最终达到一致性。