【Redis源码】集群之哨兵sentinel故障转移 (十二)

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: 【Redis源码】集群之哨兵sentinel故障转移 (十二)

前言:

各位看官大家好,这个主题内容比较长然后接着上一章就拆成了两个部分。那么我们接着上一章内容开始说。上一章中我们说到哨兵定时器sentinelTimer它们作用。sentinelTimer方法中执行哨兵模式中的任务。包括执行定期操作比如PING、分析主服务和从服务的INFO命令、故障转移等等。那么这一章我们就先从sentinelTimer开始说起。

(一) 基本结构

1.1 sentinelTimer 定时程序

图中为sentinelTimer调用链路,须线部分为调用aeCreateTimeEvent注册serverCron事件。

sentinel.c 中sentinelTimer方法:

voidsentinelTimer(void) {
   //检测是否需要开启sentinel TILT模式
   sentinelCheckTiltCondition();
   //对哈希表中的每个服务器实例执行调度任务
   sentinelHandleDictOfRedisInstances(sentinel.masters);
   //执行脚本命令,
   sentinelRunPendingScripts();
   //清理已经执行完脚本的进程,
   sentinelCollectTerminatedScripts();
   //kill执行时间超时的脚本
   sentinelKillTimedoutScripts();
   /*
   * 为了防止多个哨兵同时选举时故意错开定时程序执行的时间。
   */

   server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;
}

1.2 哨兵结构介绍

基本数据结构

structsentinelState {
   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协议端口) */
   unsignedlong simfailure_flags; /* 故障模拟状态. */
} sentinel;

每一个哨兵都有一个sentinel结构,里面维护着多个主机连接。每个主机连接信息都维护着一个sentinelRedisInstance,通过这个结构维护着所有主机连接的关系。

sentinelRedisInstance结构信息:
typedefstructsentinelRedisInstance {
   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 */
   unsignedint quorum;/* 判断这个实例客观下线(objectively down )所需的支持投票数量,SENTINEL monitor     选项中的quorum 参数 */
   int parallel_syncs; /* 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量,SENTINEL parallel-syncs   选项的值. */
   char *auth_pass;    /* Password to use for AUTH against master & slaves. */

   //。。。省略
   mstime_t failover_start_time;   /* 上次故障转移尝试开始时间. */
   mstime_t failover_timeout;      /* 刷新故障迁移状态的最大时间限制. SENTINEL failover-timeout   选项的值*/
   
   //。。。省略
} 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

# 用于改变关于master的配置,例如 sentinel set mymaster down-after-milliseconds 1000 ,此命令修改了当节点第一次失去连接到判定其下线所经过的时间
sentinel set  

# 告诉sentinel去监听新的master
sentinel monitor      

# 命令sentinel放弃对某个master的监听
sentinel remove  

# 这个参数设置集群从判断节点挂掉,到执行故障转移操作(即重新选举master节点)的时间
sentinel failover-timeout mymaster 10000

# 获取哨兵监视某个sentinel的信息
sentinel sentinels

# 获取sentinel监视的某个master的slaves信息
sentinel slaves

# 获取sentinel 监视的某个 master信息
sentinel master

# 获取sentinel监视所有的master信息
sentinel masters

# 询问该sentinel,该 ip,port的master是否为down状态,
# 如果该sentinel为tilt模式,会不理会这个询问,不去判断
# 该master是否为主观下线状态,直接回复正常状态。
sentinel is-master-down-by-addr    

# 根据master名字获取到master的ip和port
sentinel get-master-addr-by-name

# 将sentinel 状态信息写入到配置文件当中
setinel flushconfig

# 检查可投票同意master on failure的sentinel+1的个数以及相关状态
# (可用的投票个数是否大于master 的quorum,需要quorum个同意master on failure)
setinel ckquorum

(二)发现故障

2.1如何确定故障

提及到确认故障,哨兵中确认故障有两种形式分为对应两种状态SRI_S_DOWN(主观下线)和SRI_O_DOWN(客观下线)。

1) 主观下线

主观下线会涉及到一个方法sentinelCheckSubjectivelyDown,图中会主观下线链路

voidsentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
   mstime_t elapsed = 0;

   if (ri->link->act_ping_time)
       elapsed = mstime() - ri->link->act_ping_time;
   elseif (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);
       }
   }
}

  1. 检测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服务是否下线。

voidsentinelHandleRedisInstance(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。

voidsentinelAskMasterStateToOtherSentinels(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  

命令询问其他sentinel是否同意主服务器已下线。

接受SENTINEL is-master-down-by-addr命令返回状态:

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 状态机

voidsentinelFailoverStateMachine(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链接,一个订阅发布链接。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3月前
|
编解码 算法 定位技术
GEE时序——利用sentinel-2(哨兵-2)数据进行地表物候学分析(时间序列平滑法估算和非平滑算法代码)
GEE时序——利用sentinel-2(哨兵-2)数据进行地表物候学分析(时间序列平滑法估算和非平滑算法代码)
83 3
|
2月前
|
缓存 运维 NoSQL
【Redis故障排查】「连接失败问题排查和解决」带你总体分析和整理Redis的问题故障实战开发指南及方案
【Redis故障排查】「连接失败问题排查和解决」带你总体分析和整理Redis的问题故障实战开发指南及方案
557 0
|
3月前
|
存储 编解码 人工智能
GEE数据集——哨兵2号Sentinel-2 云概率数据集
GEE数据集——哨兵2号Sentinel-2 云概率数据集
133 2
|
4月前
|
监控 NoSQL 程序员
Redis 高可用篇:你管这叫 Sentinel 哨兵集群原理
Redis 高可用篇:你管这叫 Sentinel 哨兵集群原理
77 5
|
4月前
|
NoSQL Linux Redis
Redis 6.X Sentinel 哨兵集群搭建
Redis 6.X Sentinel 哨兵集群搭建
32 5
|
5月前
|
定位技术
哨兵2号Sentinel-2分幅条带介绍与MGRS网格矢量文件获取
哨兵2号Sentinel-2分幅条带介绍与MGRS网格矢量文件获取
|
5月前
|
NoSQL Java Redis
SpringBoot2.0整合Redis高可用之Sentinel哨兵
本篇博文分享的是一主二从三哨兵模式。至于为什么用三个哨兵,同第一段。本文是模拟环境,都是一个服务器上面。
79 0
|
5月前
|
监控 NoSQL Redis
Redis - 主从复制那些事与高可用sentinel
Redis - 主从复制那些事与高可用sentinel
38 0
|
监控 NoSQL Redis
|
NoSQL 数据可视化 Redis
Redis高可用Sentinel哨兵模式环境搭建
Redis高可用Sentinel哨兵模式环境搭建
432 0