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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
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
相关文章
|
1月前
|
缓存 NoSQL 网络协议
【Azure Redis】因为Redis升级引发了故障转移后的问题讨论
3:对于Redis的Server Load指标,每秒创建连接数的并发值,是否有建议呢? 【答】:为了避免将缓存推到 100% 服务器负载,建议将连接创建速率保持在每秒 30 个以下。
|
3月前
|
监控 NoSQL Redis
Redis Sentinel:秒杀系统背后的可靠性保障神器!
本文详细介绍了如何在个人项目中利用 Redis 哨兵模式保障系统的可靠性与高可用性。哨兵模式通过监控主从服务器状态、自动故障转移和通知客户端等功能,确保在主服务器宕机时系统仍能正常运行。适用于读请求多于写请求的场景,如秒杀系统,能有效缓解数据库压力。同时也探讨了哨兵模式在高并发场景下的优化方法及潜在缺陷,帮助开发者更好地应用该模式。
75 7
Redis Sentinel:秒杀系统背后的可靠性保障神器!
|
2月前
|
监控 NoSQL 算法
Redis Sentinel(哨兵)详解
Redis Sentinel(哨兵)详解
142 4
|
3月前
|
存储 NoSQL Redis
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
Redis持久化、RDB和AOF方案、Redis主从集群、哨兵、分片集群、散列插槽、自动手动故障转移
SpringCloud基础7——Redis分布式缓存,RDB,AOF持久化+主从+哨兵+分片集群
|
4月前
|
运维 监控 NoSQL
【Redis】哨兵(Sentinel)原理与实战全解~炒鸡简单啊
Redis 的哨兵模式(Sentinel)是一种用于实现高可用性的机制。它通过监控主节点和从节点,并在主节点故障时自动进行切换,确保集群持续提供服务。哨兵模式包括主节点、从节点和哨兵实例,具备监控、通知、自动故障转移等功能,能显著提高系统的稳定性和可靠性。本文详细介绍了哨兵模式的组成、功能、工作机制以及其优势和局限性,并提供了单实例的安装和配置步骤,包括系统优化、安装、配置、启停管理和性能监控等。此外,还介绍了如何配置主从复制和哨兵,确保在故障时能够自动切换并恢复服务。
|
5月前
|
监控 NoSQL 算法
Redis问题之哨兵模式中的配置文件会在故障转移后发生什么变化如何解决
Redis问题之哨兵模式中的配置文件会在故障转移后发生什么变化如何解决
|
6月前
|
监控 NoSQL Java
redis哨兵架构
不过为了高可用一般都推荐至少三个哨兵节点。为什么推荐奇数个哨兵节点跟集群奇数个master节点类似。
45 0
redis哨兵架构
|
5月前
|
消息中间件 监控 NoSQL
Redis哨兵改集群
【7月更文挑战第7天】
|
5月前
|
缓存 NoSQL Redis
Redis复制、哨兵
Redis复制、哨兵
46 0
下一篇
DataWorks