环境说明: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将会已下线的主服务器进行下线操作,下线操作分为以下三个操作:
- 在已下线的主服务的所有从服务器中,挑选出一个从服务器,将其转换为主服务器;
- 将已下线的主服务的所有从服务器,复制新的主服务器;
- 设置已经下线的主服务为从服务,并复制新的主服务,当下线的主服务重新上线后,就会复制新的主服务了;
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命令,将他设置为从服务器;