前言:
各位看官大家好,这个主题内容比较长然后接着上一章就拆成了两个部分。那么我们接着上一章内容开始说。上一章中我们说到哨兵定时器sentinelTimer它们作用。sentinelTimer方法中执行哨兵模式中的任务。包括执行定期操作比如PING、分析主服务和从服务的INFO命令、故障转移等等。那么这一章我们就先从sentinelTimer开始说起。
(一) 基本结构
1.1 sentinelTimer 定时程序
图中为sentinelTimer调用链路,须线部分为调用aeCreateTimeEvent注册serverCron事件。
sentinel.c 中sentinelTimer方法:
void sentinelTimer(void) {
//检测是否需要开启sentinel TILT模式
sentinelCheckTiltCondition();
//对哈希表中的每个服务器实例执行调度任务
sentinelHandleDictOfRedisInstances(sentinel.masters);
//执行脚本命令,
sentinelRunPendingScripts();
//清理已经执行完脚本的进程,
sentinelCollectTerminatedScripts();
//kill执行时间超时的脚本
sentinelKillTimedoutScripts();
/*
* 为了防止多个哨兵同时选举时故意错开定时程序执行的时间。
*/
server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;
}
1.2 哨兵结构介绍
基本数据结构
struct sentinelState {
char myid[CONFIG_RUN_ID_SIZE+1]; /* 哨兵ID */
uint64_t current_epoch; /* Current epoch. */
dict *masters; /* 存储哨兵监听服务器的信息 对应一个
sentinelRedisInstance 结构体指针 */
int tilt; /* 判断 TILT 模式 */
int running_scripts; /* 当前正在执行的脚本数。 */
mstime_t tilt_start_time; /* TITL 开始时间. */
mstime_t previous_time; /* 上次处理程序运行时间. */
list *scripts_queue; /* 要执行的用户脚本队列. */
char *announce_ip; /* IP 地址(gossip协议握手地址) */
int announce_port; /* 端口 (gossip协议端口) */
unsigned long simfailure_flags; /* 故障模拟状态. */
} sentinel;
每一个哨兵都有一个sentinel结构,里面维护着多个主机连接。每个主机连接信息都维护着一个sentinelRedisInstance,通过这个结构维护着所有主机连接的关系。
sentinelRedisInstance结构信息:
typedef struct sentinelRedisInstance {
int flags; /* 记录哨兵类型以及实力当前状态 */
char *name; /* 哨兵名称格式为ip:port ,例如"127.0.0.1:26379" */
char *runid; /* 哨兵运行ID.*/
uint64_t config_epoch; /* 配置纪元,用于故障转移. */
sentinelAddr *addr; /* 实例地址. */
//...省略
mstime_t s_down_since_time; /* 主观下线标记时间. */
mstime_t o_down_since_time; /* 客观下线标记时间. */
mstime_t down_after_period; /* 实例无响应多少毫秒之后才会被判断为主观下线(subjectively down ),SENTINEL down-after-milliseconds 选项设定的值 */
//。。。省略
/* Master specific. */
dict *sentinels; /* 其他sentinels. */
dict *slaves; /* 这个master的slave */
unsigned int quorum;/* 判断这个实例客观下线(objectively down )所需的支持投票数量,SENTINEL monitor <master-name> <IP> <port> <quorum> 选项中的quorum 参数 */
int parallel_syncs; /* 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量,SENTINEL parallel-syncs <master-name> <number> 选项的值. */
char *auth_pass; /* Password to use for AUTH against master & slaves. */
//。。。省略
mstime_t failover_start_time; /* 上次故障转移尝试开始时间. */
mstime_t failover_timeout; /* 刷新故障迁移状态的最大时间限制. SENTINEL failover-timeout <master-name> <ms> 选项的值*/
//。。。省略
} sentinelRedisInstance;
1.3 sentinel建立网络连接
创建与被监听的master网络连接后,sentinel会成功master的客户端,它会向master发送命令。
并从master的响应中获取master的信息。对于每个被监听者的master,sentinel会向创建两个异步的网络连接。
该连接通过sentinelReconnectInstance函数创建,一个链接为commands链接。另外一个链接为Pub / Sub 连接。订阅发布会创建一个__sentinel__:hello的通道。
1.4 sentinel命令集
# 重置名字匹配正则表达式的所有master状态信息,清除之前存储的状态信息和slaves信息。PS:节点只要加入过sentinel,信息就会保存而不会自动清除
sentinel reset <pattern>
# 用于改变关于master的配置,例如 sentinel set mymaster down-after-milliseconds 1000 ,此命令修改了当节点第一次失去连接到判定其下线所经过的时间
sentinel set <name> <option> <value>
# 告诉sentinel去监听新的master
sentinel monitor <name> <ip> <port> <quorum>
# 命令sentinel放弃对某个master的监听
sentinel remove <name>
# 这个参数设置集群从判断节点挂掉,到执行故障转移操作(即重新选举master节点)的时间
sentinel failover-timeout mymaster 10000
# 获取哨兵监视某个sentinel的信息
sentinel sentinels <master-name>
# 获取sentinel监视的某个master的slaves信息
sentinel slaves <master-name>
# 获取sentinel 监视的某个 master信息
sentinel master <name>
# 获取sentinel监视所有的master信息
sentinel masters
# 询问该sentinel,该 ip,port的master是否为down状态,
# 如果该sentinel为tilt模式,会不理会这个询问,不去判断
# 该master是否为主观下线状态,直接回复正常状态。
sentinel is-master-down-by-addr <ip> <port> <current-epoch> <runid>
# 根据master名字获取到master的ip和port
sentinel get-master-addr-by-name <master-name>
# 将sentinel 状态信息写入到配置文件当中
setinel flushconfig
# 检查可投票同意master on failure的sentinel+1的个数以及相关状态
# (可用的投票个数是否大于master 的quorum,需要quorum个同意master on failure)
setinel ckquorum <name>
(二)发现故障
2.1如何确定故障
提及到确认故障,哨兵中确认故障有两种形式分为对应两种状态SRI_S_DOWN(主观下线)和SRI_O_DOWN(客观下线)。
1) 主观下线
主观下线会涉及到一个方法sentinelCheckSubjectivelyDown,图中会主观下线链路
void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
mstime_t elapsed = 0;
if (ri->link->act_ping_time)
elapsed = mstime() - ri->link->act_ping_time;
else if (ri->link->disconnected)
elapsed = mstime() - ri->link->last_avail_time;
/* 检测command 连接是否被关闭 */
if (ri->link->cc &&
(mstime() - ri->link->cc_conn_time) >
SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
ri->link->act_ping_time != 0 && /* Ther is a pending ping... */
/* The pending ping is delayed, and we did not received
* error replies as well. */
(mstime() - ri->link->act_ping_time) > (ri->down_after_period/2) &&
(mstime() - ri->link->last_pong_time) > (ri->down_after_period/2))
{
instanceLinkCloseConnection(ri->link,ri->link->cc);
}
/* 检测pubsub连接是否需要被关闭
*/
if (ri->link->pc &&
(mstime() - ri->link->pc_conn_time) >
SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
(mstime() - ri->link->pc_last_activity) > (SENTINEL_PUBLISH_PERIOD*3))
{
instanceLinkCloseConnection(ri->link,ri->link->pc);
}
/* 更新SRI_S_DOWN状态
*/
if (elapsed > ri->down_after_period ||
(ri->flags & SRI_MASTER &&
ri->role_reported == SRI_SLAVE &&
mstime() - ri->role_reported_time >
(ri->down_after_period+SENTINEL_INFO_PERIOD*2)))
{
/* Is subjectively down */
if ((ri->flags & SRI_S_DOWN) == 0) {
sentinelEvent(LL_WARNING,"+sdown",ri,"%@");
ri->s_down_since_time = mstime();
ri->flags |= SRI_S_DOWN;
}
} else {
/* Is subjectively up */
if (ri->flags & SRI_S_DOWN) {
sentinelEvent(LL_WARNING,"-sdown",ri,"%@");
ri->flags &= ~(SRI_S_DOWN|SRI_SCRIPT_KILL_SENT);
}
}
}
- 检测command 连接是否被关闭;
2.检测pubsub连接是否需要被关闭 ;
3.更新SRI_S_DOWN状态状态,更新状态有如下两个规则:
3.1 超过ri->down_after_period,代表超过响应时间,及ping无响应请求。该时间默认走的时SENTINEL_DEFAULT_DOWN_AFTER宏为30s。
3.2 SLAVE上报连续时间间隔要大于ri->down_after_period+SENTINEL_INFO_PERIOD2,及(30s + 10s2),如果超过这个时间代表slave长时间连续不到master,所以视为主观下线。
2) 客观下线
说到客观下线是,我们要思考一个问题。当一台master服务已经掉线,并且已经维护自己的状态为SRI_S_DOWN。由于在哨兵集群中,ri->down_after_period值可能不一样。判断master下线的时间间隔可能不一样。所以必须去询问sentinel节点这台master服务是否下线。
void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
//。。。省略
/* Only masters */
if (ri->flags & SRI_MASTER) {
sentinelCheckObjectivelyDown(ri);
if (sentinelStartFailoverIfNeeded(ri))
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);
sentinelFailoverStateMachine(ri);
sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);
}
}
在上面代码中可以看到客观下线只能master中使用。然后看一下sentinelAskMasterStateToOtherSentinels方法,该方法检测master主观下线后去询问其他sentinel。
void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int flags) {
dictIterator *di;
dictEntry *de;
di = dictGetIterator(master->sentinels);
while((de = dictNext(di)) != NULL) {
sentinelRedisInstance *ri = dictGetVal(de);
mstime_t elapsed = mstime() - ri->last_master_down_reply_time;
char port[32];
int retval;
/* If the master state from other sentinel is too old, we clear it. */
if (elapsed > SENTINEL_ASK_PERIOD*5) {
ri->flags &= ~SRI_MASTER_DOWN;
sdsfree(ri->leader);
ri->leader = NULL;
}
/* 满足下列情况才可以询问其他哨兵:
*
* 1) 主观下线是否在进行
* 2) Sentinel是否连接
* 3) 我们没有在哨兵询问期内收到信息,1秒内. */
if ((master->flags & SRI_S_DOWN) == 0) continue;
if (ri->link->disconnected) continue;
if (!(flags & SENTINEL_ASK_FORCED) &&
mstime() - ri->last_master_down_reply_time < SENTINEL_ASK_PERIOD)
continue;
/* Ask */
ll2string(port,sizeof(port),master->addr->port);
retval = redisAsyncCommand(ri->link->cc,
sentinelReceiveIsMasterDownReply, ri,
"SENTINEL is-master-down-by-addr %s %s %llu %s",
master->addr->ip, port,
sentinel.current_epoch,
(master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
sentinel.myid : "*");
if (retval == C_OK) ri->link->pending_commands++;
}
dictReleaseIterator(di);
}
通过遍历维护的master->sentinels结构向其他sentinel节点发送命令:SENTINEL is-master-down-by-addr
命令格式如下:
SENTINEL is-master-down-by-addr <current_epoch> <leader_id>
命令询问其他sentinel是否同意主服务器已下线。
接受SENTINEL is-master-down-by-addr命令返回状态:
<down_state> <leader_runid> <leader_epoch>
down_state:为1代表主服务器已下线,0表示主服务器未下线。
leader_runid:领头sentinal id。
leader_epoch:领头sentinel当前投票纪元。
(三)故障转移
3.1 故障状态
当某个主节点进行故障转移时,该主节点的的故障转移状态,master->failover_state,依次会经历6个状态:
状态宏:
SENTINEL_FAILOVER_STATE_NONE 0 /*没有故障转移在进行*/
//以下为经历的6个状态
SENTINEL_FAILOVER_STATE_WAIT_START 1 /* sentinel接手故障转移*/
SENTINEL_FAILOVER_STATE_SELECT_SLAVE 2 /* 选择slave成为master*/
SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE 3 /* 发送slaveof no one给新master */
SENTINEL_FAILOVER_STATE_WAIT_PROMOTION 4 /* 等待新master升级完成,超时终止故障转移*/
SENTINEL_FAILOVER_STATE_RECONF_SLAVES 5 /* 新master升级完成后,让slaves复制新master */
SENTINEL_FAILOVER_STATE_UPDATE_CONFIG 6 /* 监视新master */
3.2 状态机
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); //sentinel接手故障转移
break;
case SENTINEL_FAILOVER_STATE_SELECT_SLAVE:
sentinelFailoverSelectSlave(ri); //选择slave成为master
break;
case SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE:
sentinelFailoverSendSlaveOfNoOne(ri); //发送slaveof no one给新master
break;
case SENTINEL_FAILOVER_STATE_WAIT_PROMOTION:
sentinelFailoverWaitPromotion(ri); //等待新master升级完成,超时终止故障转移
break;
case SENTINEL_FAILOVER_STATE_RECONF_SLAVES:
sentinelFailoverReconfNextSlave(ri); //新master升级完成后,让slaves复制新master
break;
}
}
状态机变化过程:
总结
1)一个哨兵结构中可以维护多个主机,包括master,slave,sentinel。
2)确定故障分为:主观下线和客观下线两种。
3)主观下线:为一段时间内ping返回无效,探测所有节点都是一致的,则为主观下线。主观下线的时间是可以配置的,以master配置维度为准。
4)客观下线:客观下线只针对于master节点,且需要master为主观下线,并通过其他sentinel节点发送SENTINEL is-master-down-by-addr询问其他节点master下线问题,达成共识的一个状态。
5) 哨兵会创建两个链接一个commands链接,一个订阅发布链接。