redis源码分析(七):集群--哨兵模式

redis在启动时,如果进程名是”redis-sentinel”,或者参数中带了”–sentinel”,这时redis便以哨兵的方式运行。一个sentinel可以监控多个master。

sentinel的配置如下


// 当前Sentinel节点监控 127.0.0.1:6379 这个主节点
// 2代表判断主节点失败至少需要2个Sentinel节点节点同意
// mymaster是主节点的别名
sentinel monitor mymaster 127.0.0.1 6379 2

//每个Sentinel节点都要定期PING命令来判断Redis数据节点和其余Sentinel节点是否可达,如果超过30000毫秒且没有回复,则判定不可达
sentinel down-after-milliseconds mymaster 30000

//当Sentinel节点集合对主节点故障判定达成一致时,Sentinel领导者节点会做故障转移操作,选出新的主节点,原来的从节点会向新的主节点发起复制操作,限制每次向新的主节点发起复制操作的从节点个数为1
sentinel parallel-syncs mymaster 1

//故障转移超时时间为180000毫秒
sentinel failover-timeout mymaster 180000

sentinel支持的如下的命令列表:

struct redisCommand sentinelcmds[] = {
    {"ping",pingCommand,1,"",0,NULL,0,0,0,0,0},
    {"sentinel",sentinelCommand,-2,"",0,NULL,0,0,0,0,0},
    {"subscribe",subscribeCommand,-2,"",0,NULL,0,0,0,0,0},
    {"unsubscribe",unsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
    {"psubscribe",psubscribeCommand,-2,"",0,NULL,0,0,0,0,0},
    {"punsubscribe",punsubscribeCommand,-1,"",0,NULL,0,0,0,0,0},
    {"publish",sentinelPublishCommand,3,"",0,NULL,0,0,0,0,0},
    {"info",sentinelInfoCommand,-1,"",0,NULL,0,0,0,0,0},
    {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
};

sentinel的时间事件也区别于一般服务,入口是sentinelTimer。

void sentinelTimer(void) {

    // 记录本次 sentinel 调用的事件,
    // 并判断是否需要进入 TITL 模式
    sentinelCheckTiltCondition();

    // 执行定期操作
    // 比如 PING 实例、分析主服务器和从服务器的 INFO 命令
    // 向其他监视相同主服务器的 sentinel 发送问候信息
    // 并接收其他 sentinel 发来的问候信息
    // 执行故障转移操作,等等
    sentinelHandleDictOfRedisInstances(sentinel.masters);

    // 运行等待执行的脚本
    sentinelRunPendingScripts();

    // 清理已执行完毕的脚本,并重试出错的脚本
    sentinelCollectTerminatedScripts();

    // 杀死运行超时的脚本
    sentinelKillTimedoutScripts();

    /* We continuously change the frequency of the Redis "timer interrupt"
     * in order to desynchronize every Sentinel from every other.
     * This non-determinism avoids that Sentinels started at the same time
     * exactly continue to stay synchronized asking to be voted at the
     * same time again and again (resulting in nobody likely winning the
     * election because of split brain voting). */
    server.hz = REDIS_DEFAULT_HZ + rand() % REDIS_DEFAULT_HZ;
}

一次时间事件中,主要做了以下几件事情。

  • 1.判断是否上次执行时间事件距今是否差了超过了2秒,则进入默认持续30秒的TITL模式,进入了此模式的sentinel认为当前自身的状态可能是有问题的,会阉割一部分选举master相关的功能。

  • 2.连接其他master/slave/sentinel,发送auth命令,master的地址是配置文件配好的,所以可以直接拿到;slave和其他sentinel的地址通过master拿到。

  • 3.向master和slave定期发送 ping,info命令。其中info命令可以得到对方的运行状态,主从关系等。

  • 4.哨兵在连接建立的时候会向master和其slave订阅频道”_sentinel_:hello”的消息,同时周期性的发布一条hello消息,给其他哨兵打招呼,这条消息包含了自身地址,自身runid,自身的epoch,master的基本信息和epoch。
    假设有个哨兵B也是检测这个master的,会接受到A发送的推送,把A保存到自己的哨兵列表中。

  • 5.根据redis服务回复ping的情况,判断redis是否已经主观下线(Subject Down),如果下线了,将结果告知给其他sentinel。

  • 6.如果master主观下线了,判断这个master是否客观下线(Object Down),方法就是收集其他sentinel的意见,如果认为该master下线的sentinel数量大于配置,则认为这个master下线了,此时开始执行故障转移逻辑(Failover)。

  • 7.故障转移,分为以下步骤:
    1)选举头领(leader)
    1-1)首先向其他sentinel发送纪元编号(为本sentinel自身当前的纪元自增1,同时记录为故障master的纪元),如果其他纪元收到了这条消息,如果比自己的大,则更新为此纪元,否则忽略;
    1-2)统计这个故障master下的所有sentinel投票结果,如果已经有其他sentinel发表意见了,则选取最高得票者,投他一票,如果没有,投自己一票。此时,如果最高得票者的得票数超过半数+1,则本次选举完成,否则进入下个纪元。引入纪元(epoch)这个参数是因为,选举不是每次都成功的,比如所有sentinel都选举了自己,这种情况下需要重新启动下一轮投票。
    2)由leader从slave当中选择一个作为新的master。
    2-1) 过滤掉Down掉的slave,判断slave的Info回复时长和与master的连接状态,太差的slave不考虑。
    2-2) 对符合条件的slave排序以选出最优,依据分别是slave的优先级,从原master复制的数据量大小,runid。

    1. 向选出来的slave发送 slaveof no one来提升该slave为master。确定该服务提升为master之后,给其他slave发送slaveof命令。
sentinel内部状态发生变化时,会调用sentinelEvent方法广播状态,我们可以使用redis-cli连接sentinel,用subscribe来查看sentinel的内部运行状态,可以订阅多个状态,用空格隔开,本机测试:
localhost:Debug jjchen$ redis-cli -p 26379
redis> subscribe +tilt -tilt
Reading messages... (press Ctrl-c to quit)
1. "subscribe"
2. "+tilt"
3. (integer) 1

1. "subscribe"
2. "-tilt"
3. (integer) 2

1. "message"
2. "+tilt"
3. "#tilt mode entered"

1. "message"
2. "-tilt"
3. "#tilt mode exited"

1. "message"
2. "+tilt"
3. "#tilt mode entered"
    原文作者:msrpp
    原文地址: https://www.jianshu.com/p/581a69bc8b93
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞