上一篇咱们介绍了 redis 主从节点之间的数据同步复制技术,经过一次全量复制和不间断的命令传播,能够达到主从节点数据同步备份的效果,一旦主节点宕机,咱们能够选择一个工做正常的 slave 成为新的主节点,并让其余 slave 去同步它。java
这是处理 redis 故障转移的一个方式,但却不具有生产实用性,由于毕竟是手动处理故障,而 redis 发生故障时间节点不可预知,咱们须要一个自动监控组件帮咱们自动处理故障转移。git
Redis 哨兵模式(Sentinel)就是一个自动地监控处理 redis 间故障节点转移工做的一个「东西」,准确来讲,Sentinel 实际上是一个 redis 服务端程序,只不过运行在特殊的模式下,不提供数据存储服务,只进行普通 redis 节点监控管理。程序员
Sentinel 其实也是一个 redis 的服务端程序,它也会定时执行 serverCron 函数,只是里面其余的程序用不到,用到的是对普通 redis 节点的监控以及故障转移模块。github
Sentinel 初始化的时候会清空原来的命令表,写入本身独有的命令进去,因此普通 redis 节点支持的数据读写命令,对 Sentinel 来讲都是找不到命令,由于它根本就没有初始化这些命令的执行器。redis
Sentinel 会定时的对本身监控的 master 执行 info 命令,获取最新的主从关系,还会定时的给全部的 redis 节点发送 ping 心跳检测命令,若是检测到某个 master 没法响应了,就会在给其余 Sentinel 发送消息,主观认为该 master 宕机,若是 Sentinel 集群认同该 master 下线的人数达到一个值,那么你们统一意见,下线该 master。bash
下线以前须要作的是找 Sentinel 集群中的某一个来执行下线操做,这个步骤叫领导者选举,选出来之后会从该 master 全部的 slave 节点中挑一个合适的做为新的 master,并让其余 slave 从新同步新的 master。微信
其实以上咱们就简单的介绍了 Sentinel 是什么,本质上作了哪些事情,等下咱们会结合源码细说其中的细节实现。这里咱们再看下,如何配置并启动一个 Sentinel 监控。(生产环境建议配置大于三个)markdown
第一步,启动一个普通的 redis server 节点:函数
这一步没什么好说的,咱们启动在一个默认的 6379 端口上。oop
第二步,启动三个不一样的 slave 节点:
第三步,编写 sentinel 配置文件:
咱们解释一下这几条配置的含义,咱们说过 Sentinel 实际上是运行在特殊模式下的 redis server,因此它须要运行端口。紧接着咱们经过命令 sentinel monitor mymaster 配置当前 sentinel 须要监控的主节点 redis 以及触发客观下线参数,sentinel down-after-milliseconds 配置了一个参数,master 最长响应时间,超过这个时间就主观判断它下线。
sentinel parallel-syncs 配置用于限制主从切换以后,最多的并行同步数据的从节点数量,由于咱们知道,主从进行全量同步阶段,从节点加载数据时是不提供服务的,若是这个参数越大,那么主从切换完成的时间就越短,固然也会致使大量从节点不可提供读服务,反之。
sentinel failover-timeout 配置了执行故障转移的最大等待时间。
第四步,启动 Sentinel:
使用命令,redis-sentinel [config],启动三个 sentinel。
这样的话,其实咱们就完成了一个简单的 sentinel 集群配置,下面咱们手动的让 master 宕机,看看整个 sentinel 有没有为咱们作故障转移。
从结果上看来,sentinel 自动为咱们把原先的从节点 7003 设置为新的 master,具体过程咱们不细说,等下结合源码详细介绍,这里咱们应该大体对 sentinel 的实际应用有了大概的认识。
当咱们使用命令 redis-sentinel 启动 sentinel 的时候,
int main(int argc, char **argv) { 。。。。。 server.sentinel_mode = checkForSentinelMode(argc,argv); 。。。。。 if (server.sentinel_mode) { initSentinelConfig(); initSentinel(); } 。。。。。 } 复制代码
checkForSentinelMode 函数中会根据你的命令以及参数,检查判断是不是以 sentinel 模式启动,若是是则返回 1,反之。若是是以 sentinel 启动,则会进行一个 sentinel 的初始化操做。
void initSentinelConfig(void) {
server.port = REDIS_SENTINEL_PORT; //26379
}
复制代码
initSentinelConfig 实际上就是初始化当前 sentinel 运行端口,默认是 26379。
void initSentinel(void) { unsigned int j; //清空普通redis-server下可用的命令表 dictEmpty(server.commands,NULL); //加载sentinel须要的命令 for (j = 0; j < sizeof(sentinelcmds)/sizeof(sentinelcmds[0]); j++) { int retval; struct redisCommand *cmd = sentinelcmds+j; retval = dictAdd(server.commands, sdsnew(cmd->name), cmd); serverAssert(retval == DICT_OK); } sentinel.current_epoch = 0; //根据配置文件,初始化本身须要监控的master(一个sentinel是可能监控多个 master的) sentinel.masters = dictCreate(&instancesDictType,NULL); sentinel.tilt = 0; sentinel.tilt_start_time = 0; sentinel.previous_time = mstime(); sentinel.running_scripts = 0; sentinel.scripts_queue = listCreate(); sentinel.announce_ip = NULL; sentinel.announce_port = 0; sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE; sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG; memset(sentinel.myid,0,sizeof(sentinel.myid)); } 复制代码
initSentinel 主要的做用仍是清空普通模式的 redis 命令表,加载独属于 sentinel 使用的命令,并初始化本身监控的 master 集合。
至此,sentinel 的初始化就算完成了,剩下的自动监控则在定时函数 serverCron 中,咱们一块儿来看看。
//间隔 100 毫秒执行一次 sentinelTimer run_with_period(100) { if (server.sentinel_mode) sentinelTimer(); } 复制代码
也就是说,sentinel 启动以后,会间隔 100 毫秒在 serverCron 调用一次 sentinelTimer 函数处理一些重要事件(其实,sentinelTimer 中会修改执行间隔)。
void sentinelTimer(void) {
sentinelCheckTiltCondition();
sentinelHandleDictOfRedisInstances(sentinel.masters);
sentinelRunPendingScripts();
sentinelCollectTerminatedScripts();
sentinelKillTimedoutScripts();
server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;
}
复制代码
sentinelTimer 函数体很是简短,但不要高兴太早。sentinelCheckTiltCondition 函数咱们不去多说,redis 高度依赖系统时间,若是屡次检测到系统时钟纪元不许确,它会断定当前系统不稳定,进入 TITL,相似一个休眠的状态,不会为咱们作故障转移,仅仅收集数据,等待系统恢复稳定。
void sentinelHandleDictOfRedisInstances(dict *instances) { dictIterator *di; dictEntry *de; sentinelRedisInstance *switch_to_promoted = NULL; di = dictGetIterator(instances); //递归遍历监控的全部 master,执行监控操做 while((de = dictNext(di)) != NULL) { sentinelRedisInstance *ri = dictGetVal(de); //这是监控的核心逻辑,下文细说 sentinelHandleRedisInstance(ri); if (ri->flags & SRI_MASTER) { //不管是 slave 仍是其余 sentinel,都视做一个redisInstance sentinelHandleDictOfRedisInstances(ri->slaves); sentinelHandleDictOfRedisInstances(ri->sentinels); if (ri->failover_state == SENTINEL_FAILOVER_STATE_UPDATE_CONFIG) { switch_to_promoted = ri; } } } if (switch_to_promoted) sentinelFailoverSwitchToPromotedSlave(switch_to_promoted); dictReleaseIterator(di); } 复制代码
sentinelHandleRedisInstance 主要两个部分组成,监控和故障转移。
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) { sentinelReconnectInstance(ri); sentinelSendPeriodicCommands(ri); if (sentinel.tilt) { if (mstime()-sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return; sentinel.tilt = 0; sentinelEvent(LL_WARNING,"-tilt",NULL,"#tilt mode exited"); } sentinelCheckSubjectivelyDown(ri); if (ri->flags & (SRI_MASTER|SRI_SLAVE)) { /* Nothing so far. */ } if (ri->flags & SRI_MASTER) { sentinelCheckObjectivelyDown(ri); if (sentinelStartFailoverIfNeeded(ri)) sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED); sentinelFailoverStateMachine(ri); sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS); } } 复制代码
sentinelReconnectInstance 函数作两件事,由于当前是一个 sentinel 实例,因此第一件事就是与当前遍历的 instance 创建链接,不论它是 master、slave 或是 sentinel,并在成功创建链接后发送 ping 命令。第二,若是当前遍历的是一个 master 或 slave,则会订阅它的 sentinel_hello 频道,当这个频道上有消息更新,则会广播全部订阅的该频道的客户端。(订阅这个频道的主要做用仍是用于发现其余 sentinel 以及与其余 sentinel 交流本身对监控的节点的见解)
sentinelSendPeriodicCommands 函数默认每间隔十秒给 master 和 slave 发送 info 命令,了解他们的主从关系,若是此 instance 被本身主观下线了,那么会加快发送 info 命令的频率,以保证本身最快知道主从关系变化,还会每间隔一秒 ping 全部类型的实例。
以上实际上是 sentinelHandleRedisInstance 中监控节点的部分,下面咱们继续看其故障转移怎么作的。
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) { sentinelReconnectInstance(ri); sentinelSendPeriodicCommands(ri); //判断是否须要进入 tilt 模式 if (sentinel.tilt) { if (mstime()-sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return; sentinel.tilt = 0; sentinelEvent(LL_WARNING,"-tilt",NULL,"#tilt mode exited"); } //判断是否须要主观下线该节点 sentinelCheckSubjectivelyDown(ri); if (ri->flags & (SRI_MASTER|SRI_SLAVE)) { /* Nothing so far. */ } if (ri->flags & SRI_MASTER) { //判断是否须要客户下线该节点 sentinelCheckObjectivelyDown(ri); //若是肯定该节点客观下线,进行领导者选举 if (sentinelStartFailoverIfNeeded(ri)) sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED); //故障转移 sentinelFailoverStateMachine(ri); sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS); } } 复制代码
sentinelCheckSubjectivelyDown 检测当前节点是否须要主观下线,判断条件是此节点对于本身的配置,若是当前这个实例超过配置的时间段没有回复本身的 ping,那么判断它下线,设置主观下线标志位。
sentinelCheckObjectivelyDown 检测当前是否达到客观下线的条件,检测逻辑是这样的,遍历全部的兄弟 sentinel 结构,看看他们有没有把当前节点主观下线,统计数量,若是达到 quorum,则断定该 master 客观下线,设置标志位并经过频道通知到其余 兄弟 sentinel。
sentinelStartFailoverIfNeeded 判断当前是否已有 sentinel 在进行故障转移(经过 master 的一个标志位,若是有 sentinel 正在进行故障转移,这个标志位会被设置),若是有,则本身不参与,什么都不作。
sentinelAskMasterStateToOtherSentinels 会去给其余 sentinel 发送消息,要求它赞成本身做为领导者对 master 进行故障转移。具体怎么作的呢,首先会拿到本身这边关于全部兄弟 sentinel 的信息进行一个遍历,并给他们发送命令 is-master-down-by-addr 要求他们赞成本身成为领导者,并设置回调函数 sentinelReceiveIsMasterDownReply 处理回复。
若是某个 sentinel 收到别人发来的领导者投票,且本身没有给其余人投过票的话就会赞成,反之不予理睬。
当某个 sentinel 收到足够的票数,则它认为本身就是 leader,标志 master 为故障转移中,并进行真正的故障转移操做。
void sentinelFailoverStateMachine(sentinelRedisInstance *ri) { serverAssert(ri->flags & SRI_MASTER); if (!(ri->flags & SRI_FAILOVER_IN_PROGRESS)) return; switch(ri->failover_state) { //故障转移开始 case SENTINEL_FAILOVER_STATE_WAIT_START: sentinelFailoverWaitStart(ri); break; //选择一个要晋升的从节点 case SENTINEL_FAILOVER_STATE_SELECT_SLAVE: sentinelFailoverSelectSlave(ri); break; //发送slaveof no one命令,使从节点变为主节点 case SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE: sentinelFailoverSendSlaveOfNoOne(ri); break; //等待被选择的从节点晋升为主节点,若是超时则从新选择晋升的从节点 case SENTINEL_FAILOVER_STATE_WAIT_PROMOTION: sentinelFailoverWaitPromotion(ri); break; //给全部的从节点发送slaveof命令,同步新的主节点 case SENTINEL_FAILOVER_STATE_RECONF_SLAVES: sentinelFailoverReconfNextSlave(ri); break; } } 复制代码
sentinelFailoverStateMachine 故障转移包括五个步骤,分五个 sentinelTimer 执行周期处理。当新 master 选举完成,会给其余兄弟 sentinel 广播,告知他们新的 master 已经出现,他们收到后,会撤销对原 master 的主观下线,并从新开始监控新的 master。
至此,咱们对 Sentinel 的介绍与源码分析就结束了,它本质上就是一个运行在特殊模式下的 redis-server,经过不断 ping 主从节点,在感知他们可能出现故障以后,集体进行一个投票认定并选举出一我的去执行 master 的客观下线。
下一篇,咱们看 redis 中更牛逼的 cluster。
关注公众不迷路,一个爱分享的程序员。 公众号回复「1024」加做者微信一块儿探讨学习! 每篇文章用到的全部案例代码素材都会上传我我的 github github.com/SingleYam/o… 欢迎来踩!