哨兵,Redis sentinel,在主从复制的基础上实现故障恢复的自动化。其核心功能是主节点(master)的自动故障转移。html
主要功能:java
它由两部分组成,哨兵节点和数据节点:redis
redis sentinel是一个分布式的架构,其中包含若干个sentinel节点,对于节点的故障判断是由多个独立的sentinel节点共同完成,这样能够有效防止误判。多个sentinel节点使得即便个别sentinel节点不可用,整个sentinel节点集合依然是健壮的。算法
总体部署在一台机器上(192.168.118.129 ),经过不一样的端口号来区分主从节点以及哨兵节点。架构
部署一台主节点(端口号为6379),2台从节点(端口号分别为6380和6381)。分布式
把6380配置为6379的slave。ide
把6381配置为6379的slave。函数
查看master节点的复制信息线程
配置三个哨兵节点,和主从在同一台机器上(192.168.118.129),用三个不一样的端口号来区分(分别为26379,26380,26381)。debug
哨兵节点是特殊的redis节点,有特殊的配置,针对每个哨兵分别创建一个配置文件。
以26379哨兵为例,创建sentinel26379.conf配置文件,基本配置以下:
sentinel monitor mymaster 193.168.129 6379 2
这句是哨兵节点的核心配置,表明这个哨兵监控193.168.129: 6379这个master节点,且命名为mymaster。2表明至少有两个哨兵赞成才能认定主节点产生故障而且进行故障转移。
按照上述步骤配置sentinel26380.conf和sentinel26381.conf。至此,三个哨兵节点配置完成。
接下来启动哨兵节点。有两种方法,两个方法等效
src/redis-sentinel sentinel26379.conf
src/redis-server sentinel26379.conf --sentinel
一次启动三个哨兵,启动后能够查看。
启动后哨兵节点会发现主节点,以及主节点对应的salve节点,同时哨兵节点会发现彼此。哨兵节点启动后最终的结构以下。
演示主节点down机后哨兵进行自动故障转移的case。目前主节点是6379节点。
杀掉主节点的进程
迁移
主节点已经被迁移成了6380节点。
转移后配置文件也被自动作了修改。
哨兵做为配置的提供者,客户端能够直接基于哨兵进行链接,而无需直到具体主节点的ip,这样实现了和主节点的解耦,当主节点down机后,哨兵从新选主,对客户端透明。
public static void main(String[] args) { String masterName="mymaster";// 主节点名字 Set<String> sentinels=new HashSet<>();//哨兵集合 sentinels.add("192.168.118.129:26379"); sentinels.add("192.168.118.129:26380"); sentinels.add("192.168.118.129:26381"); JedisSentinelPool jedisSentinelPool=new JedisSentinelPool(masterName,sentinels); Jedis jedis= jedisSentinelPool.getResource(); jedis.set("name","cnblogs-new"); System.out.println(jedis.get("name")); jedisSentinelPool.close(); }
执行结果:
[main] INFO redis.clients.jedis.JedisSentinelPool - Trying to find master from available Sentinels... [main] INFO redis.clients.jedis.JedisSentinelPool - Redis master running at 192.168.118.129:6380, starting Sentinel listeners... [main] INFO redis.clients.jedis.JedisSentinelPool - Created JedisPool to master at 192.168.118.129:6380 cnblogs-new
能够看到经过哨兵找到了master节点。
核心实如今JedisSentinelPool
的构造函数中
private HostAndPort initSentinels(Set<String> sentinels, final String masterName) { HostAndPort master = null; log.info("Trying to find master from available Sentinels..."); for (String sentinel : sentinels) { final HostAndPort hap = HostAndPort.parseString(sentinel); log.debug("Connecting to Sentinel {}", hap); jedis = new Jedis(hap.getHost(), hap.getPort(), sentinelConnectionTimeout, sentinelSoTimeout); List<String> masterAddr = jedis.sentinelGetMasterAddrByName(masterName); //查找master master = toHostAndPort(masterAddr); return master; } }
经过代码能够发现:
遍历哨兵节点
链接哨兵节点
经过哨兵节点获取master节点 sentinelGetMasterAddrByName
。改方法内部调用了redis的命令来实现 sentinel get-master-addr-by-name mymaster
上述代码省略了一部分,订阅。
for (String sentinel : sentinels) { final HostAndPort hap = HostAndPort.parseString(sentinel); MasterListener masterListener = new MasterListener(masterName, hap.getHost(), hap.getPort()); // whether MasterListener threads are alive or not, process can be stopped masterListener.setDaemon(true); masterListeners.add(masterListener); masterListener.start(); }
为每个sentinel节点独立启动一个线程,利用redis的发布订阅功能,每一个线程订阅sentinel节点上切换master的相关频道+switch-master
public void onMessage(String channel, String message) { String[] switchMasterMsg = message.split(" "); if (masterName.equals(switchMasterMsg[0])) { initPool(toHostAndPort(Arrays.asList(switchMasterMsg[3], switchMasterMsg[4]))); } else { log.debug("Ignoring message on +switch-master for master name {}, our master name is {}",switchMasterMsg[0], masterName); } }
发现当前master发生了switch,使用initPool从新初始化链接池。
经过监控来判断节点是否不可达,sentinel经过三个定时任务来完成后对各个节点的监控和发现。
Sentinel节点定时执行info命令来获取拓扑信息
每隔10秒执行
Sentinel节点发布和订阅 _sentinel_hello频道
每隔两秒执行
每隔2秒,每隔sentinel节点会向master的_sentinel_hello 频道发送自身的节点信息,以及本身对master状态的判断,同时每一个sentinel也会订阅改频道,最 终实现了sentinel节点之间的互相发现以及彼此交换本身对主节点状态的判断,做为客观下线的依据。
Sentinel节点向其他节点发送ping命令
每隔1秒执行
实现对每一个节点的监控,这个定时任务是节点失败断定的重要依据。
一个参数: down-after-milliseconds
# sentinel down-after-milliseconds <master-name> <milliseconds> # # Number of milliseconds the master (or any attached replica or sentinel) should # be unreachable (as in, not acceptable reply to PING, continuously, for the # specified period) in order to consider it in S_DOWN state (Subjectively # Down). # # Default is 30 seconds.
主观下线
上一节里面的ping操做,若是超过 down-after-milliseconds
没有获得回复,sentinel会把改节点作失败判断,叫作主观下线。就是当前sentinel一家认为这个节点是失败的,有可能有误判。
客观下线
当sentinel对master作了主观下线后会经过命令: sentinel is-master-down-by-addr
来询问其余sentinel 节点对主节点是否下线的判断。当超过
总结举例
master至关于一个嫌疑人,在审判,法律规定必需要3(quorum)个陪审员都认为有罪,才会判断为有罪。当任何一个陪审员认为嫌疑人有罪的时候,就问其余陪审员的意见,若是最终汇总下来发现有3个陪审员认为有罪,则最终才会判断master有罪,经过多个确认来减小误判的可能。
客观下线是针对master节点的,slave节点只须要主观下线便可。
当最终对主节点进行客观下线后,并非立刻进行故障转移,而是进入领导者sentinel节点的选举,由于最终的故障转移由一个sentinel节点来完成,这个节点就是领导者sentinel节点。
领导者选取使用的算法是raft算法,(参考2),是一个共识算法,大体思路以下。
is-master-down-by-addr
,要求将本身设置为领导者,固然给本身投了一票is-master-down-by-addr
命令,则赞成该请求(只投一次赞成票)。S1最早完成了客观下线。
S1获得两票,知足条件,成为leader。
节点 | 发出赞成的节点 | 接受赞成的节点 |
---|---|---|
S1 | S2,S3 | |
S2 | S1 | |
S3 | S1 |
从新选取master
当领导者sentinel选举完成后,由该sentinel节点完成故障转移。总体流程以下。
过滤不健康节点是指主观下线,断线的salve
先比较replica_priority, 值越小,优先级越高
若是priority相同,则选择复制偏移量最大的节点(最接近原来master的节点,数据最全)
若是偏移量相同,则选择runid最小的节点
设置过程