redis源码浅析--十五.哨兵sentinel的设计与实现

环境说明:redis源码版本 5.0.3;我在阅读源码过程做了注释,git地址:https://gitee.com/xiaoangg/redis_annotation
如有错误欢迎指正
参考书籍:《redis的设计与实现》
raft协议 :http://thesecretlivesofdata.com/raft/
文章推荐:
redis源码阅读-一--sds简单动态字符串
redis源码阅读--二-链表
redis源码阅读--三-redis散列表的实现
redis源码浅析--四-redis跳跃表的实现
redis源码浅析--五-整数集合的实现
redis源码浅析--六-压缩列表
redis源码浅析--七-redisObject对象(下)(内存回收、共享)
redis源码浅析--八-数据库的实现
redis源码浅析--九-RDB持久化
redis源码浅析--十-AOF(append only file)持久化
redis源码浅析--十一.事件(上)文件事件
redis源码浅析--十一.事件(下)时间事件
redis源码浅析--十二.单机数据库的实现-客户端
redis源码浅析--十三.单机数据库的实现-服务端 - 时间事件
redis源码浅析--十三.单机数据库的实现-服务端 - redis服务器的初始化
redis源码浅析--十四.多机数据库的实现(一)--新老版本复制功能的区别与实现原理
redis源码浅析--十四.多机数据库的实现(二)--复制的实现SLAVEOF、PSYNY
redis源码浅析--十五.哨兵sentinel的设计与实现
redis源码浅析--十六.cluster集群的设计与实现
redis源码浅析--十七.发布与订阅的实现
redis源码浅析--十八.事务的实现
redis源码浅析--十九.排序的实现
redis源码浅析--二十.BIT MAP的实现
redis源码浅析--二十一.慢查询日志的实现
redis源码浅析--二十二.监视器的实现

推荐阅读
  哨兵的的搭建及相关概念:https://blog.csdn.net/qq_16399991/article/details/99968357

1. sentinel启动并初始化

sentinel本质是一个特殊模式的redis的服务器,代码的入口一样位于server.c/main函数;


int main(int argc, char **argv) {
    // ...............
    //如果是sentinel模式 ,则初始化sentinel配置信息
    /* We need to init sentinel right now as parsing the configuration file
     * in sentinel mode will have the effect of populating the sentinel
     * data structures with master nodes to monitor. */
    if (server.sentinel_mode) {
        initSentinelConfig(); 
        initSentinel();
    }
    //..................
}

不过,sentient的工作方式和普通redis服务器的执行的工作不同,所以初始化过程和普通redis并不完全相同;

1.1 使用sentinel专用的代码

  1.1.1 sertinet.c/initSentinelConfig()函数将会覆盖普通redis服务器的一些默认配置:

/**
 * 此函数用于设置Sentinel的默认值
 * 覆盖普通redis config默认值。
 */ 
/* This function overwrites a few normal Redis config default with Sentinel
 * specific defaults. */
void initSentinelConfig(void) {
    //使用26379作为sentinel的默认端口
    server.port = REDIS_SENTINEL_PORT; 
    server.protected_mode = 0; /* Sentinel must be exposed. */
}

普通redis服务器会使用server.c/redisCommandTable 作为服务器的命令表;sentinel则会使用sentinel.c/sentinelcmds作为sentinel的命令表;

//sentinel 模块初始化
/* Perform the Sentinel mode initialization. */
void initSentinel(void) {
    unsigned int j;

    // 清空常用命令;只添加sentinel 命令
    /* Remove usual Redis commands from the command table, then just add
     * the SENTINEL command. */
    dictEmpty(server.commands,NULL);
    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);
    }

    //......
    //初始化各种数据结构
    //......
}


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},
    {"role",sentinelRoleCommand,1,"l",0,NULL,0,0,0,0,0},
    {"client",clientCommand,-2,"rs",0,NULL,0,0,0,0,0},
    {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0},
    {"auth",authCommand,2,"sltF",0,NULL,0,0,0,0,0}
};

1.1.2 初始化sentinel状态

接下来服务器会初始化一个sentinel.c/sentinelState,用于保存所有与sentinel功能有关的状态;


/**
 * sentinel 状态 
*/
/* Main state. */
struct sentinelState {
    char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */
    uint64_t current_epoch;         /* Current epoch. */ //当前纪元用户实现故障转移

    /**
     * 当前哨兵监听的所有master字典
     * key是实例名,value是指向sentinelRedisInstance 结构的指针
     */ 
    dict *masters;      /* Dictionary of master sentinelRedisInstances.
                           Key is the instance name, value is the
                           sentinelRedisInstance structure pointer. */

    //TODO 什么是TITL模式????
    //是否进入了TILT模式                                          
    int tilt;           /* Are we in TILT mode? */
    //目前正在执行的脚本数量
    int running_scripts;    /* Number of scripts in execution right now. */
    //进入tilt模式的时间
    mstime_t tilt_start_time;       /* When TITL started. */
    //最后一次执行处理器的时间
    mstime_t previous_time;         /* Last time we ran the time handler. */
    //FIFO队列,包含所有需要执行的用户脚本
    list *scripts_queue;            /* Queue of user scripts to execute. */
    char *announce_ip;  /* IP addr that is gossiped to other sentinels if
                           not NULL. */
    int announce_port;  /* Port that is gossiped to other sentinels if
                           non zero. */
    unsigned long simfailure_flags; /* Failures simulation. */
    int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script
                                  paths at runtime? */
} sentinel;



//sentinel 模块初始化
/* Perform the Sentinel mode initialization. */
void initSentinel(void) {
    unsigned int j;
    //...............
    // 清空常用命令;只添加sentinel 命令
    //..............

    //初始化各种数据结构
    /* Initialize various data structures. */
    sentinel.current_epoch = 0;
    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));
}

1.1.2 初始化sentinel状态的master属性

sentinelState状态中的master属性记录所有被sentinel监视的所有相关信息;

  • sentinelState.masters的结构是hash;
  • 字典键的被监视主服务的名字;
  • 字典值则是 sentinel.c/sentinelRedisInstance结构体;

sentinel.c/sentinelRedisInstance结构属性如下:

/**
 * sentinelRedisInstance结构记录了,被sentinel监听的实例的结构
 * 该结构的初始化是根据sentinel启动时的指定的配置项来初始化的;
 */
typedef struct sentinelRedisInstance {
    //标志值,记录实例的类型,以及实例当前的状态
    int flags;      /* See SRI_... defines */

    /**
     * 实例的名称
     * 主服务的名称是有用户在配置文件中配置的
     * 从服务的名字由sentinel的自动设置;格式是IP:port 
    */
    char *name;     /* Master name from the point of view of this sentinel. */

    //实例的运行ID
    char *runid;    /* Run ID of this instance, or unique ID if is a Sentinel.*/
    
    //配置纪元, 用于实例故障转移
    uint64_t config_epoch;  /* Configuration epoch. */
    
    //实例的地址; (ip 和端口)
    sentinelAddr *addr; /* Master host. */

    //.........................
    
    //实例多少毫毛无响应,会被判断为主观下线
    mstime_t down_after_period; /* Consider it down after that period. */
  
    /**
     * SENTINEL monitor <master-name> <ip> <port> <quorum> 配置中的quorum参数
     * 判断这个实例客观下线,需要多少个sentinel同意;
     */ 
    unsigned int quorum;/* Number of sentinels that need to agree on failure. */

    /**
     * SENTINEL parallel-sync <master-name> <num> 配置中num的值
     * 执行故障转移时,允许多少个从服务同时对主服务器进行同步操作
     */ 
    int parallel_syncs; /* How many slaves to reconfigure at same time. */
    char *auth_pass;    /* Password to use for AUTH against master & slaves. */

    /**
     * SENTINEL failover-timeout <master-name> <ms> 配置项中的ms值
     * 刷新故障转移状态的最大时限
     */ 
    mstime_t failover_timeout;      /* Max time to refresh failover state. */
    mstime_t failover_delay_logged; /* For what failover_start_time value we
    
    //............

sentinelState->master属性的初始化是根据sentinel启动时的指定的配置项来初始化的;

例如启动时指定了配置项:

#sentinel-1节点需要监控127.0.0.1: 6379这个主节点;
#2代表判断主节点失败至少需要2个Sentinel节点同意;
#mymaster是主节点的别名
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 30000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 180000

那么sentinelRedisInstance的name属性被设置为mymaster;
down-after-milliseconds属性设置为30000;
parallel-syncs设置为1;
failover-timeout设置为180000;

加载配置项的入口位于 server.c/main > sentine.c/sentinelIsRunning

1.1.3 创建与主服务的网络连接

初始化完成后,sentinel会与被监控的master建立连接,sentinel将会成为master的客户端;

sentinel会创建两个连接向master的异步网络连接:

  • 一个是命令连接,用于向master发送命令并接受回复;
  • 一个是订阅连接,用于订阅master的__sentinel__:hello频道;

为什么要有两个连接

目前发布和订阅的功能,被发送的信息都不会保存在服务器里面;如果发送时接受信息的客户端不在线,那么客户端就会丢失这条数据;为了不丢失信息,所以sentinel必须创建一个订阅连接来接受消息;

创建入口:
server.c/main -> server.c/serverCron() -> sentinel.c/sentinelTimer ->  sentinel.c/sentinelHandleDictOfRedisInstances() -> sentinel.c/sentinelHandleRedisInstance

 2. 获取主服务器&从服务器信息

sentinel默认每10s一次评率向 master & slale发送info命令;

/**
 * 定期向指定的slave或master发送PING、INFO, 发布到消息到 “订阅”的channel
 */
/* Send periodic PING, INFO, and PUBLISH to the Hello channel to
 * the specified master or slave instance. */
void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
    //..................
    /* Send INFO to masters and slaves, not sentinels. */
    //向master和slave发送info命令
    if ((ri->flags & SRI_SENTINEL) == 0 &&
        (ri->info_refresh == 0 ||
        (now - ri->info_refresh) > info_period))
    {
        retval = redisAsyncCommand(ri->link->cc,
            sentinelInfoReplyCallback, ri, "%s",
            sentinelInstanceMapCommand(ri,"INFO"));
        if (retval == C_OK) ri->link->pending_commands++;
    }

    //...........
}

通过解析maser返回Info命令信息,获取master的当先信息;

  • 一方面获取master的信息,包括run_id、角色、地址等;
  • 另一方面获取master下所有slave服务器信息;根据这些返回信息,slave无需提供从服务器信息,就可以自动发现从服务器;

通过解析slave返回的salave的信息,主要提取以下信息。然后根据这些信息,更新从服务的实例信息;

  • 从服务的run_id
  • 主服务器的ip地址以及端口号
  • 主从服务器的连接状态
  • 从服务器优先级
  • 从服务的偏移量

贴上解析info命令返回的代码:

/* 处理master 返回的info命令返回的信息*/
/* Process the INFO output from masters. */
void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
    sds *lines;
    int numlines, j;
    int role = 0;

    /* cache full INFO output for instance */
    sdsfree(ri->info);
    ri->info = sdsnew(info);

    /* The following fields must be reset to a given value in the case they
     * are not found at all in the INFO output. */
    ri->master_link_down_time = 0;

    /* Process line by line. */
    // 一行行的解析到所需要的信息
    lines = sdssplitlen(info,strlen(info),"\r\n",2,&numlines);
    for (j = 0; j < numlines; j++) {
        sentinelRedisInstance *slave;
        sds l = lines[j];

        /* run_id:<40 hex chars>*/
        //解析获取run_id 并更新run_id
        if (sdslen(l) >= 47 && !memcmp(l,"run_id:",7)) {
            if (ri->runid == NULL) {
                ri->runid = sdsnewlen(l+7,40);
            } else {
                if (strncmp(ri->runid,l+7,40) != 0) {
                    sentinelEvent(LL_NOTICE,"+reboot",ri,"%@");
                    sdsfree(ri->runid);
                    ri->runid = sdsnewlen(l+7,40);
                }
            }
        }

        /* old versions: slave0:<ip>,<port>,<state>
         * new versions: slave0:ip=127.0.0.1,port=9999,... */
        if ((ri->flags & SRI_MASTER) &&
            sdslen(l) >= 7 &&
            !memcmp(l,"slave",5) && isdigit(l[5]))
        {
            char *ip, *port, *end;

            if (strstr(l,"ip=") == NULL) {
                /* Old format. */
                ip = strchr(l,':'); if (!ip) continue;
                ip++; /* Now ip points to start of ip address. */
                port = strchr(ip,','); if (!port) continue;
                *port = '\0'; /* nul term for easy access. */
                port++; /* Now port points to start of port number. */
                end = strchr(port,','); if (!end) continue;
                *end = '\0'; /* nul term for easy access. */
            } else {
                /* New format. */
                ip = strstr(l,"ip="); if (!ip) continue;
                ip += 3; /* Now ip points to start of ip address. */
                port = strstr(l,"port="); if (!port) continue;
                port += 5; /* Now port points to start of port number. */
                /* Nul term both fields for easy access. */
                end = strchr(ip,','); if (end) *end = '\0';
                end = strchr(port,','); if (end) *end = '\0';
            }

            /* Check if we already have this slave into our table,
             * otherwise add it. */
            /* 检查slave是否在监听的列表中,不存在则闯将*/
            if (sentinelRedisInstanceLookupSlave(ri,ip,atoi(port)) == NULL) {
                if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,ip,
                            atoi(port), ri->quorum, ri)) != NULL)
                {
                    sentinelEvent(LL_NOTICE,"+slave",slave,"%@");
                    sentinelFlushConfig();
                }
            }
        }

        /* master_link_down_since_seconds:<seconds> */
        if (sdslen(l) >= 32 &&
            !memcmp(l,"master_link_down_since_seconds",30))
        {
            ri->master_link_down_time = strtoll(l+31,NULL,10)*1000;
        }

        //角色信息
        /* role:<role> */
        if (!memcmp(l,"role:master",11)) role = SRI_MASTER;
        else if (!memcmp(l,"role:slave",10)) role = SRI_SLAVE;

        //如果是角色是salve
        if (role == SRI_SLAVE) {
            /* master_host:<host> */
            //master的host地址
            if (sdslen(l) >= 12 && !memcmp(l,"master_host:",12)) {
                if (ri->slave_master_host == NULL ||
                    strcasecmp(l+12,ri->slave_master_host))
                {
                    sdsfree(ri->slave_master_host);
                    ri->slave_master_host = sdsnew(l+12);
                    ri->slave_conf_change_time = mstime();
                }
            }

            /* master_port:<port> */
            //master的端口
            if (sdslen(l) >= 12 && !memcmp(l,"master_port:",12)) {
                int slave_master_port = atoi(l+12);

                if (ri->slave_master_port != slave_master_port) {
                    ri->slave_master_port = slave_master_port;
                    ri->slave_conf_change_time = mstime();
                }
            }

            /* master_link_status:<status> */
            //主从服务器的连接状态
            if (sdslen(l) >= 19 && !memcmp(l,"master_link_status:",19)) {
                ri->slave_master_link_status =
                    (strcasecmp(l+19,"up") == 0) ?
                    SENTINEL_MASTER_LINK_STATUS_UP :
                    SENTINEL_MASTER_LINK_STATUS_DOWN;
            }

            /* slave_priority:<priority> */
            //从服务器的优先级
            if (sdslen(l) >= 15 && !memcmp(l,"slave_priority:",15))
                ri->slave_priority = atoi(l+15);

            /* slave_repl_offset:<offset> */
            //从服务的复制偏移量
            if (sdslen(l) >= 18 && !memcmp(l,"slave_repl_offset:",18))
                ri->slave_repl_offset = strtoull(l+18,NULL,10);
        }
    }
    ri->info_refresh = mstime();
    sdsfreesplitres(lines,numlines);
    
    //.............
}

发送info命令入口 
server.c/main -> server.c/serverCron() -> sentinel.c/sentinelTimer ->  sentinel.c/sentinelHandleDictOfRedisInstances() -> sentinel.c/sentinelSendPeriodicCommands();

解析info返回结果入口
sentinel.c/sentinelSendPeriodicCommands -> sentinel.c/sentinelInfoReplyCallback sentinel.c/sentinelInfoReplyCallback sentinel.c/sentinelRefreshInstanceInfo

3.向主服务器和从服务器发送信息

sentinel会以默认每2秒一次的频率,向所有被监视的master、slave 发送以下格式的命令:

sentinel_ip,sentinel_port,sentinel_runid,sentinel_current_epoch,master_name,master_ip,master_port,master_config_epoch
  • sentinel_ip :sentinel的ip;
  • sentinel_port:sentinel的端口
  • sentinel_runid::sentinel的runid
  • sentinel_current_epoch:sentinel当前配置的纪元
  • master_name: master的名称
  • ·master_ip:master的ip
  • master_port:master的端口
  • master_config_epoch:master的当前配置的纪元

/* Send an "Hello" message via Pub/Sub to the specified 'ri' Redis
 * instance in order to broadcast the current configuration for this
 * master, and to advertise the existence of this Sentinel at the same time.
 *
 * The message has the following format:
 *
 * sentinel_ip,sentinel_port,sentinel_runid,current_epoch,
 * master_name,master_ip,master_port,master_config_epoch.
 *
 * Returns C_OK if the PUBLISH was queued correctly, otherwise
 * C_ERR is returned. */
/**
 * 通过Pub/Sub向指定的“ri”Redis实例发送“Hello”消息,以便广播此主机的当前配置,同时播发此Sentinel的存在。
 * 消息的格式如下:
 * "sentinel_ip,sentinel_port,sentinel_runid,current_epoch,master_name,master_ip,master_port,master_config_epoch。
 * 如果发布已正确排队,则返回C_OK,否则返回C_ERR。
 */
int sentinelSendHello(sentinelRedisInstance *ri) {
    char ip[NET_IP_STR_LEN];
    char payload[NET_IP_STR_LEN+1024];
    int retval;
    char *announce_ip;
    int announce_port;
    sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ? ri : ri->master;
    sentinelAddr *master_addr = sentinelGetCurrentMasterAddress(master);

    if (ri->link->disconnected) return C_ERR;

    /* Use the specified announce address if specified, otherwise try to
     * obtain our own IP address. */
    if (sentinel.announce_ip) {
        announce_ip = sentinel.announce_ip;
    } else {
        if (anetSockName(ri->link->cc->c.fd,ip,sizeof(ip),NULL) == -1)
            return C_ERR;
        announce_ip = ip;
    }
    announce_port = sentinel.announce_port ?
                    sentinel.announce_port : server.port;

    /* Format and send the Hello message. */
    /*格式化 发送信息 */
    snprintf(payload,sizeof(payload),
        "%s,%d,%s,%llu," /* Info about this sentinel. */
        "%s,%s,%d,%llu", /* Info about current master. */
        announce_ip, announce_port, sentinel.myid,
        (unsigned long long) sentinel.current_epoch,
        /* --- */
        master->name,master_addr->ip,master_addr->port,
        (unsigned long long) master->config_epoch);
        
    retval = redisAsyncCommand(ri->link->cc,
        sentinelPublishReplyCallback, ri, "%s %s %s",
        sentinelInstanceMapCommand(ri,"PUBLISH"),
        SENTINEL_HELLO_CHANNEL,payload);
    if (retval != C_OK) return C_ERR;
    ri->link->pending_commands++;
    return C_OK;
}

4.接受来自主服务和从服务的频道信息

当sentinel与主服务或者从服务建立连接后,Sentinel就订阅主服务或从服务器的__sentinel__::hello频道;

订阅频道会一致持续到连接断开;

这样对于监听同一个服务器的多个sentinel,一个sentinel发送的信息会被其他sentinel接受到,这些信息会被其他sentinel用于更新信息;

举个例子:
假设有三个sentinel ,s1、s2、s3监听同一个服务器,s1向服务器频道__sentinel__:hello发送了一条信息,s1、s2、s3都会受到这条消息 ;

sentinel接受到消息后,会对消息进行解析:

  • 如果是自己发送的信息,就会丢弃该信息
  • 如不是自己发送的,则是监视同一服务器的其他sentinel发送的,则会更新sentinel实例中相关信息

4.1更新sentinel字典

4.2创建连向其他sentinel的命令连接

5.检查主观下线状态

默认配置下,sentinel会每1秒频率向所有与它创建链接的实例(master,slave,sentinel)发送ping命令,通过实例返回的ping命令,判断是否在线;

ping命令的回复分两种情况:

  • 有效回复:返回 +PONG、-LOADING、-MASTERDOWN
  • 无效回复:除了上述三种的回复外,都是无效回复

当down-after-millseconds毫秒内,sentinel收到的都是无效回复; 那么sentinel就会将master标记为主观下线;

down-after-millonseconds是sentinel配置文件指定的下线标准;
这个参数不仅会应用于master,还会应用到master下属的所有从服务器;

6.检查客观下线状态

当sentinel将一个主服务器判断主观下线时,会向其他sentinel询问,看其他sentinel是否也人为他下线;

当有足够多的sentinel认为服务已经下线时,就判定服务器是客观下线,并进行故障转移;

6.1 SENTINEL is-master-down-by-addr命令

询问其他sentinel 是否认为sentinel已经下线

SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>

各参数意义如下

  • ip:sentinel判断为主观下线的服务ip
  • port:sentinel判断为主观下线的服务端口
  • current_epoch:sentinel当前配置的纪元。用于选取领头sentinel
  • runid:可以是*或者sentinel的运行id,* 代表用于检测主服务器的客观下线状态;传sentinel的运行id 则代表用于选举领头的sentinel;

6.2 接受SENTINEL is-master-down-by-addr命令 的回复

sentienl根据SENTINEL is-master-down-by-addr命令的返回,统计其他sentinel返回下线的数量;
当这一数量达到配置指定的客观下线数量时,sentinel会将服务器实例结构的flags属性的SIR_O_DOWN标记位打开,标识主服务进入客观下线状态;

7.选举sentinel的领导者

当一个主服务器被判断为下线时,监视这个服务器sentinel的会进行协商,选举主一个领头的sentinel
由这个领头的sentinel执行故障转移操作

选举领头sentinel的规则和方法

  • 所有在线的sentinel都有被选为领头sentinel的资格
  • 每次进行领头sentinel选举的时候,不论选举是否成功,所有sentinel的配置纪元的值都会+1;(配置的纪元就是个计数器)
  • 每个配置的纪元里面,所有sentinel都有一票 将某个sentinel设置为局部领头sentinel的机会,并且局部领头一旦设置,在这个配置纪元里面就不能更改了
  • 每个发现主服务器下线的sentinel,都会要求其他sentinel将自己设置为领头sentinel
  • sentinel设置局部领头sentinel的规则是先到先得;最先向目标sentinel“索要投票” 将会得到该票,后续“索票”的sentinel将会被拒绝;
  • 源sentinel收到目标sentinel的回复后,会解析回复中的leader_epoch;如果leader_epoch和源sentinel的一致,那么源sentinel会继续解析leader_runid参数;
    如果leader_runid和源sentinel的run_id一致,说明目标sentinel将源森sentinel设为了领头sentinel;
  • 如果某sentinel被半数以上sentinel设置为了领头sentinel,那么这个sentinel将会成为领头sentinel;
  • 如果给定的时间限制内,没有选举出领头sentinel,那么将在一段时间后重新选举,直到选举出为止;

8.故障转移

选举出的领头sentinel将会已下线的主服务器进行下线操作,下线操作分为以下三个操作:

  1. 在已下线的主服务的所有从服务器中,挑选出一个从服务器,将其转换为主服务器;
  2. 将已下线的主服务的所有从服务器,复制新的主服务器;
  3. 设置已经下线的主服务为从服务,并复制新的主服务,当下线的主服务重新上线后,就会复制新的主服务了;

8.1选出新的主服务

新的主服务器是如何挑选出来的:

  • 领头的sentinel会将下线的主服务的所有从服务放到一个列表里,然后按照以下规则对列表中一个个的过滤:
  • 过滤掉已经断线的从服务器;
  • 过滤掉最近5s内没有回复过sentinel 的info命令的从服务;
  • 过滤掉与主服务器连接断开时间超过(down-after-millseconds * 10)毫秒的从服务器;这样可以保证剩余从服务器的数据比较新;
  • 然后从剩余从服务器中选举优先级高的从服务器;
  • 如果服务器优先级相同,则选取复制偏移量大的(复制便宜量大,说明数据新);
  • 至此,如果还没有选举出,则对运行id进行排序,选举云运行id最小的从服务器;

选举出新的主服务后,sentinel将向新的主服务器发送slave no one ;
让后领头sentinel以每秒一次的评论向新的主服务发送info命令,用于判断“切换主服务操作”是否成功(当新的主服务器角色由salve变成master,说明已经切换成功了)

8.2修改从服务器的复制目标

新的主服务出现后,sentinel就会让所有从服务器去复制新的主服务器(通过salveof 命令实现);

8.3修改旧的主服务器的为从服务器

最后,将已经下线的主服务设置为从服务器,并复制新的主服务;

注意:因为这时旧的主服务已经下线,所以这时这个操作会保存到sentinel对应的实例结构中,等到旧的主服务重新上线后,sentinel就会向他发送slaveof命令,将他设置为从服务器;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值