redis中的sentinel.c文件

在 Redis 中,sentinel.c 文件是 Sentinel 模块的实现文件。Sentinel 是 Redis 的高可用性解决方案,用于监控和管理 Redis 服务器的状态,以确保 Redis 在面临故障时能够自动进行故障转移,保证系统的可用性。
以下是 sentinel.c 文件的主要作用:

  1. Sentinel 运行时环境: 提供 Sentinel 模块的主要运行时环境,包括 Sentinel 运行状态、配置项、事件处理等。
  2. Redis Sentinel 服务器监控: 实现 Sentinel 对 Redis 服务器的监控功能,定期检查 Redis 服务器的健康状况,包括检测主服务器和从服务器是否在线,是否有网络分区等。
  3. 故障检测和故障转移: Sentinel 负责检测主服务器的故障,并在需要时启动故障转移过程。这包括选举新的主服务器、将从服务器提升为主服务器、更新配置等。
  4. Sentinel 间通信: 实现 Sentinel 之间的通信协议,允许它们在监控 Redis 服务器状态时进行协调和通信。
  5. Sentinel 事件处理: 处理 Sentinel 生成的事件,包括故障发现、故障转移状态变更等。这些事件可以记录日志、触发通知、执行脚本等。
  6. Sentinel 定时任务: Sentinel 通过定时任务来定期执行监控和管理操作,例如定期检查 Redis 服务器的状态、定期执行故障转移等。

总体而言,sentinel.c 文件是 Sentinel 模块的核心部分,实现了监控、故障检测、故障转移等关键功能,确保 Redis 在面临各种故障情况时能够保持高可用性。

sentinel的各种宏定义
#define SRI_MASTER  (1<<0) /* 标识实例是主节点 */
#define SRI_SLAVE   (1<<1) /* 标识实例是从节点 */
#define SRI_SENTINEL (1<<2)/* 标识实例是哨兵 */
#define SRI_S_DOWN (1<<3)   /* Subjectively down (no quorum). *//* 标识实例主观下线(没有达到法定人数) */
#define SRI_O_DOWN (1<<4)   /* Objectively down (confirmed by others). */ /* 标识实例客观下线(被其他实例确认) */
#define SRI_MASTER_DOWN (1<<5) /* A Sentinel with this flag set thinks that
                                   its master is down. *//* 标识哨兵实例认为其主节点已下线 */
#define SRI_FAILOVER_IN_PROGRESS (1<<6) /* Failover is in progress for
                                           this master. *//* 标识故障转移正在进行中 */
#define SRI_PROMOTED (1<<7)            /* Slave selected for promotion. */ /* 标识从节点被选为主节点进行升级 */
#define SRI_RECONF_SENT (1<<8)     /* SLAVEOF <newmaster> sent. *//* 标识已发送 SLAVEOF <newmaster> 命令 */
#define SRI_RECONF_INPROG (1<<9)   /* Slave synchronization in progress. */ /* 标识从节点同步正在进行中 */
#define SRI_RECONF_DONE (1<<10)     /* Slave synchronized with new master. *//* 标识从节点已完成同步 */
#define SRI_FORCE_FAILOVER (1<<11)  /* Force failover with master up. *//* 标识强制进行故障转移,即使主节点正常 */
#define SRI_SCRIPT_KILL_SENT (1<<12) /* SCRIPT KILL already sent on -BUSY *//* 表示 Redis 实例是否已经被 Sentinel 发送了终止脚本的信号*/

这些是关于一个sentinel在监控一个redis服务器时的各种状态的宏定义。

#define SENTINEL_INFO_PERIOD 10000 // Sentinel 信息刷新的周期
#define SENTINEL_PING_PERIOD 1000// Sentinel 发送 PING 命令的周期
#define SENTINEL_ASK_PERIOD 1000// Sentinel 发送 ASK 命令的周期
#define SENTINEL_PUBLISH_PERIOD 2000// Sentinel 发布消息的周期
#define SENTINEL_DEFAULT_DOWN_AFTER 30000 // 主观下线的默认超时时间
#define SENTINEL_HELLO_CHANNEL "__sentinel__:hello"// Sentinel 之间用于交换信息的频道
#define SENTINEL_TILT_TRIGGER 2000 // 触发 Sentinel TILT 模式的时间阈值
#define SENTINEL_TILT_PERIOD (SENTINEL_PING_PERIOD*30)// TILT 模式的持续时间
#define SENTINEL_DEFAULT_SLAVE_PRIORITY 100 从节点的默认优先级
#define SENTINEL_SLAVE_RECONF_TIMEOUT 10000// 从节点重新配置的超时时间
#define SENTINEL_DEFAULT_PARALLEL_SYNCS 1 // 同时进行同步的从节点数量
#define SENTINEL_MIN_LINK_RECONNECT_PERIOD 15000// 最小的链接重连周期
#define SENTINEL_DEFAULT_FAILOVER_TIMEOUT (60*3*1000)// 故障转移的默认超时时间
#define SENTINEL_MAX_PENDING_COMMANDS 100// 最大挂起的命令数
#define SENTINEL_ELECTION_TIMEOUT 10000// Sentinel 选举的超时时间
#define SENTINEL_MAX_DESYNC 1000// Sentinel 主从节点之间最大的时钟不同步
#define SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG 1// 默认情况下是否禁止使用 CONFIG REWRITE 命令

这些用于表示sentinel节点发送信息的各种配置。

#define SENTINEL_FAILOVER_STATE_NONE 0  /* No failover in progress. *///没有故障转移正在进行。
#define SENTINEL_FAILOVER_STATE_WAIT_START 1  /* Wait for failover_start_time*///等待故障转移的开始,即等待 failover_start_time。
#define SENTINEL_FAILOVER_STATE_SELECT_SLAVE 2 /* Select slave to promote *///选择要升级为主节点的从节点。
#define SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE 3 /* Slave -> Master */// 从节点升级为主节点。
#define SENTINEL_FAILOVER_STATE_WAIT_PROMOTION 4 /* Wait slave to change role *///等待从节点成功升级为主节点。
#define SENTINEL_FAILOVER_STATE_RECONF_SLAVES 5 /* SLAVEOF newmaster *///更新其他从节点的配置,使其成为新的主节点的从节点。
#define SENTINEL_FAILOVER_STATE_UPDATE_CONFIG 6 /* Monitor promoted slave. */// 监视新升级的主节点。

#define SENTINEL_MASTER_LINK_STATUS_UP 0//主节点链接处于正常状态。
#define SENTINEL_MASTER_LINK_STATUS_DOWN 1//主节点链接处于断开状态。

用于表示 Sentinel 中故障转移机器的不同状态以及主节点链接的状态。

typedef struct instanceLink {
    int refcount;          /* Number of sentinelRedisInstance owners. */// SentinelRedisInstance 拥有者的引用计数
    int disconnected;      /* Non-zero if we need to reconnect cc or pc. */// 非零表示需要重新连接 cc 或 pc
    int pending_commands;  /* Number of commands sent waiting for a reply. */// 等待回复的命令数量
    redisAsyncContext *cc; /* Hiredis context for commands. */// 用于命令的 hiredis 连接
    redisAsyncContext *pc; /* Hiredis context for Pub / Sub. */ // 用于 Pub/Sub 的 hiredis 连接
    mstime_t cc_conn_time; /* cc connection time. */ // 客户端 连接的时间
    mstime_t pc_conn_time; /* pc connection time. */ // public连接的时间
    mstime_t pc_last_activity; /* Last time we received any message. */ // 上一次接收到任何消息的时间
    mstime_t last_avail_time; /* Last time the instance replied to ping with
                                 a reply we consider valid. */// 上一次实例用有效回复响应 ping 的时间
    mstime_t act_ping_time;   /* Time at which the last pending ping (no pong
                                 received after it) was sent. This field is
                                 set to 0 when a pong is received, and set again
                                 to the current time if the value is 0 and a new
                                 ping is sent. *///最后一个待处理 ping 的时间(未收到 pong)
    mstime_t last_ping_time;  /* Time at which we sent the last ping. This is
                                 only used to avoid sending too many pings
                                 during failure. Idle time is computed using
                                 the act_ping_time field. */// 上一次发送 ping 的时间
    mstime_t last_pong_time;  /* Last time the instance replied to ping,// 上一次回复 ping 的时间
                                 whatever the reply was. That's used to check
                                 if the link is idle and must be reconnected. */
    mstime_t last_reconn_time;  /* Last reconnection attempt performed when
                                   the link was down. */// 上一次在链接断开时尝试重新连接的时间                     
} instanceLink;

该结构体用于表示 Sentinel Redis Instance 之间的链接。当同一组Sentinels 监视多个主节点时,我们可能有多个实例共享相同的连接,以减少传出连接的数量。这个结构体保存了对 SentinelRedisInstance 的引用计数,以及与该实例相关的 hiredis 连接、失败检测所需的字段等信息。

typedef struct sentinelRedisInstance {
    int flags;      /* See SRI_... defines */// 标志,见 SRI_... 定义
    char *name;     /* Master name from the point of view of this sentinel. */// 该 Sentinel 视角下的主节点名称
    char *runid;    /* Run ID of this instance, or unique ID if is a Sentinel.*/// 该实例的运行 ID,或者如果是 Sentinel 则为唯一 ID
    uint64_t config_epoch;  /* Configuration epoch. */ // 配置纪元
    sentinelAddr *addr; /* Master host. */// 主节点的地址信息
    instanceLink *link; /* Link to the instance, may be shared for Sentinels. */ // 与实例的链接,可以在 Sentinels 之间共享
    mstime_t last_pub_time;   /* Last time we sent hello via Pub/Sub. */ // 上一次通过 Pub/Sub 发送 hello 的时间
    mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time
                                 we received a hello from this Sentinel
                                 via Pub/Sub. */// 仅在 SRI_SENTINEL 标志被设置时使用。上一次从该 Sentinel 通过 Pub/Sub 接收到 hello 的时间
    mstime_t last_master_down_reply_time; /* Time of last reply to
                                             SENTINEL is-master-down command. */// 上一次回复 SENTINEL is-master-down 命令的时间
    mstime_t s_down_since_time; /* Subjectively down since time. */// 主观下线的时间
    mstime_t o_down_since_time; /* Objectively down since time. */// 客观下线的时间
    mstime_t down_after_period; /* Consider it down after that period. */ // 在该时间段之后将其视为下线
    mstime_t info_refresh;  /* Time at which we received INFO output from it. */// 收到该实例的 INFO 输出的时间
    dict *renamed_commands;     /* Commands renamed in this instance:
                                   Sentinel will use the alternative commands
                                   mapped on this table to send things like
                                   SLAVEOF, CONFING, INFO, ... */// 在该实例中重命名的命令,Sentinel 将使用映射在此表中的备用命令发送 SLAVEOF、CONFING、INFO 等

    /* Role and the first time we observed it.
     * This is useful in order to delay replacing what the instance reports
     * with our own configuration. We need to always wait some time in order
     * to give a chance to the leader to report the new configuration before
     * we do silly things. */
    /* 角色和第一次观察到它的时间。
     * 这对于延迟替换实例报告的配置信息很有用。
     * 我们需要总是等待一段时间,以便在我们执行愚蠢的操作之前给 Leader 报告新配置的机会。
     */
    int role_reported;
    mstime_t role_reported_time;
    mstime_t slave_conf_change_time; /* Last time slave master addr changed. */// 从节点 master 地址最后一次改变的时间

    /* Master specific. *//* Master 特有信息。*/
    dict *sentinels;    /* Other sentinels monitoring the same master. */ // 监视同一主节点的其他 Sentinel
    dict *slaves;       /* Slaves for this master instance. */ // 该主节点实例的从节点
    unsigned int quorum;/* Number of sentinels that need to agree on failure. */// 在失败时需要同意的 Sentinel 数量
    int parallel_syncs; /* How many slaves to reconfigure at same time. */ // 同时重新配置的从节点数量
    char *auth_pass;    /* Password to use for AUTH against master & replica. */ // 对主节点和从节点进行 AUTH 验证的密码
    char *auth_user;    /* Username for ACLs AUTH against master & replica. */// 对主节点和从节点进行 ACLs AUTH 验证的用户名

    /* Slave specific. *//* 从节点特有信息。*/
    mstime_t master_link_down_time; /* Slave replication link down time. */// 从节点复制链接断开的时间
    int slave_priority; /* Slave priority according to its INFO output. */// 根据其 INFO 输出的从节点优先级
    mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */ // 发送 SLAVE OF <new> 的时间
    struct sentinelRedisInstance *master; /* Master instance if it's slave. */// 如果是从节点,这是其主节点实例
    char *slave_master_host;    /* Master host as reported by INFO */ // 由 INFO 报告的主节点地址
    int slave_master_port;      /* Master port as reported by INFO */ // 由 INFO 报告的主节点端口
    int slave_master_link_status; /* Master link status as reported by INFO */// 由 INFO 报告的主节点链接状态
    unsigned long long slave_repl_offset; /* Slave replication offset. */// 从节点的复制偏移量
    /* Failover */
    char *leader;       /* If this is a master instance, this is the runid of
                           the Sentinel that should perform the failover. If
                           this is a Sentinel, this is the runid of the Sentinel
                           that this Sentinel voted as leader. */  // 如果这是主节点实例,这是执行故障转移的 Sentinel 的运行 ID
    uint64_t leader_epoch; /* Epoch of the 'leader' field. */// 'leader' 字段的纪元
    uint64_t failover_epoch; /* Epoch of the currently started failover. */// 当前开始的故障转移的纪元
    int failover_state; /* See SENTINEL_FAILOVER_STATE_* defines. */// 见 SENTINEL_FAILOVER_STATE_* 定义
    mstime_t failover_state_change_time;// 故障转移状态变化的时间
    mstime_t failover_start_time;   /* Last failover attempt start time. */// 上一次故障转移尝试的开始时间
    mstime_t failover_timeout;      /* Max time to refresh failover state. */// 刷新故障转移状态的最大时间
    mstime_t failover_delay_logged; /* For what failover_start_time value we
                                       logged the failover delay. */// 记录故障转移延迟的故障转移_start_time 值
    struct sentinelRedisInstance *promoted_slave; /* Promoted slave instance. */// 升级为主节点的从节点实例
    /* Scripts executed to notify admin or reconfigure clients: when they
     * are set to NULL no script is executed. */
    /* 通知管理员或重新配置客户端的脚本:当它们设置为 NULL 时,不执行脚本。*/
    char *notification_script;
    char *client_reconfig_script;
    sds info; /* cached INFO output */// 缓存的 INFO 输出
} sentinelRedisInstance;

这是一个 Redis Sentinel 实例对象的定义,其中包含了用于监控 Redis 实例的各种信息。

相关的重要的函数

void sentinelIsRunning(void) {
    int j;
    /*
    这部分代码检查配置文件是否存在以及是否可写。如果配置文件不存在,或者不可写,Sentinel 将记录一条警告日志并退出。
    */
    if (server.configfile == NULL) {
        serverLog(LL_WARNING,
            "Sentinel started without a config file. Exiting...");
        exit(1);
    } else if (access(server.configfile,W_OK) == -1) {
        serverLog(LL_WARNING,
            "Sentinel config file %s is not writable: %s. Exiting...",
            server.configfile,strerror(errno));
        exit(1);
    }

    /* If this Sentinel has yet no ID set in the configuration file, we
     * pick a random one and persist the config on disk. From now on this
     * will be this Sentinel ID across restarts. */
    /*
    如果 Sentinel 配置中尚未设置 Sentinel ID,它将生成一个随机的 ID,并将其持久化到配置文件中。生成 ID 使用 getRandomHexChars 函数。
    */
    for (j = 0; j < CONFIG_RUN_ID_SIZE; j++)
        if (sentinel.myid[j] != 0) break;

    if (j == CONFIG_RUN_ID_SIZE) {
        /* Pick ID and persist the config. */
        getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);
        sentinelFlushConfig();
    }

    /* Log its ID to make debugging of issues simpler. */
    //这部分代码用于记录 Sentinel 的 ID,以便在调试问题时更容易追踪。
    serverLog(LL_WARNING,"Sentinel ID is %s", sentinel.myid);

    /* We want to generate a +monitor event for every configured master
     * at startup. */
    //Sentinel 在启动时会为每个配置的主服务器生成一个 +monitor 事件。这有助于确保 Sentinel 在启动时对每个主服务器进行监视。
    sentinelGenerateInitialMonitorEvents();
}

这段代码在 Sentinel 启动时调用,用于为每个配置的主节点生成一个 +monitor 事件。
这些事件在运行时通过 SENTINEL MONITOR 命令添加要监视的主节点时,也会生成。
*/
void sentinelGenerateInitialMonitorEvents(void) {
    dictIterator *di;
    dictEntry *de;
    /*使用 dictGetIterator 遍历 Sentinel 中所有配置的主节点。*/
    di = dictGetIterator(sentinel.masters);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *ri = dictGetVal(de);
        /*对于每个主节点,使用 sentinelEvent 函数生成一个 +monitor 事件。这个事件会记录在日志中,
        提供有关主节点监控的信息,包括节点名称、运行 ID、地址、端口和所需的 quorum 数。*/
        sentinelEvent(LL_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);
    }
    dictReleaseIterator(di);
}

这段代码是在 Sentinel 模式下,当服务器启动、加载配置且准备好正常运行时调用的函数 sentinelIsRunning。

//用于表示 Sentinel 模块中的脚本执行任务。
typedef struct sentinelScriptJob {
    int flags;              /* Script job flags: SENTINEL_SCRIPT_* */ // 脚本任务标志:SENTINEL_SCRIPT_*
    int retry_num;          /* Number of times we tried to execute it. */// 尝试执行脚本的次数
    char **argv;            /* Arguments to call the script. */  // 调用脚本的参数
    mstime_t start_time;    /* Script execution time if the script is running,
                               otherwise 0 if we are allowed to retry the
                               execution at any time. If the script is not
                               running and it's not 0, it means: do not run
                               before the specified time. */
                               // 如果脚本正在运行,则表示脚本执行时间;否则,如果我们允许在任何时间重试执行,则为0。
                                // 如果脚本没有运行且不为0,则表示:在指定的时间之前不要运行。
    pid_t pid;              /* Script execution pid. */// 脚本执行的进程ID
} sentinelScriptJob;

在sentinel运行的脚本的结构体。

//一个用于调度执行脚本的函数
#define SENTINEL_SCRIPT_MAX_ARGS 16//预处理指令,定义了脚本执行时允许的最大参数数量。
void sentinelScheduleScriptExecution(char *path, ...) {
    va_list ap;
    //创建一个字符指针数组 argv,用于存储脚本执行的参数,数组长度为预定义的最大参数数量加一。
    char *argv[SENTINEL_SCRIPT_MAX_ARGS+1];
    int argc = 1;
    //创建一个指向 sentinelScriptJob 结构体的指针 sj,用于表示脚本执行的工作。
    sentinelScriptJob *sj;

    va_start(ap, path);
    //使用循环遍历可变参数列表,将参数复制到 argv 数组中,同时检查是否达到最大参数数量。循环内部还会为每个参数调用 sdsnew 复制字符串。
    while(argc < SENTINEL_SCRIPT_MAX_ARGS) {
        argv[argc] = va_arg(ap,char*);
        if (!argv[argc]) break;
        argv[argc] = sdsnew(argv[argc]); /* Copy the string. */
        argc++;
    }
    va_end(ap);
    argv[0] = sdsnew(path);
//使用 zmalloc 分配内存以存储 sentinelScriptJob 结构体,并将指针赋给 sj。
//初始化 sentinelScriptJob 结构体
    sj = zmalloc(sizeof(*sj));
    sj->flags = SENTINEL_SCRIPT_NONE;
    sj->retry_num = 0;
    sj->argv = zmalloc(sizeof(char*)*(argc+1));
    sj->start_time = 0;
    sj->pid = 0;
    memcpy(sj->argv,argv,sizeof(char*)*(argc+1));
//将新创建的 sentinelScriptJob 结构体添加到 Sentinel 的脚本队列。
    listAddNodeTail(sentinel.scripts_queue,sj);

    /* Remove the oldest non running script if we already hit the limit. */
    /*如果脚本队列的长度超过了预定义的最大队列长度 SENTINEL_SCRIPT_MAX_QUEUE,则会删除最早的非运行中脚本,释放相关资源。*/
    if (listLength(sentinel.scripts_queue) > SENTINEL_SCRIPT_MAX_QUEUE) {
        listNode *ln;
        listIter li;

        listRewind(sentinel.scripts_queue,&li);
        while ((ln = listNext(&li)) != NULL) {
            sj = ln->value;

            if (sj->flags & SENTINEL_SCRIPT_RUNNING) continue;
            /* The first node is the oldest as we add on tail. */
            listDelNode(sentinel.scripts_queue,ln);
            sentinelReleaseScriptJob(sj);
            break;
        }
        //使用 serverAssert 断言检查队列长度是否小于或等于最大队列长度,确保程序的正确性。
        serverAssert(listLength(sentinel.scripts_queue) <=
                    SENTINEL_SCRIPT_MAX_QUEUE);
    }
}

使用队列来存储要运行的脚本。

void sentinelRunPendingScripts(void) {
    listNode *ln;
    listIter li;
    mstime_t now = mstime();

    /* Find jobs that are not running and run them, from the top to the
     * tail of the queue, so we run older jobs first. */
    listRewind(sentinel.scripts_queue,&li);
    //重置链表迭代器,将其指向脚本队列的头部。
    while (sentinel.running_scripts < SENTINEL_SCRIPT_MAX_RUNNING &&
           (ln = listNext(&li)) != NULL)
    {
        //获取当前节点中的 sentinelScriptJob 结构体。
        sentinelScriptJob *sj = ln->value;
        pid_t pid;

        /* Skip if already running. */
        //跳过已经在运行的脚本,继续下一个节点的处理。
        if (sj->flags & SENTINEL_SCRIPT_RUNNING) continue;

        /* Skip if it's a retry, but not enough time has elapsed. */
        //跳过重试的脚本,如果当前时间还未到达允许重试的时间。
        if (sj->start_time && sj->start_time > now) continue;
        //设置脚本标志为正在运行。
        sj->flags |= SENTINEL_SCRIPT_RUNNING;
        sj->start_time = mstime();
        sj->retry_num++;
        //创建子进程。
        pid = fork();

        if (pid == -1) {
            /* Parent (fork error).
             * We report fork errors as signal 99, in order to unify the
             * reporting with other kind of errors. */
            //如果 fork 出错,报告错误事件,并将脚本标志设置为非运行状态。
            sentinelEvent(LL_WARNING,"-script-error",NULL,
                          "%s %d %d", sj->argv[0], 99, 0);
            sj->flags &= ~SENTINEL_SCRIPT_RUNNING;
            sj->pid = 0;
        } else if (pid == 0) {
            /* Child */
            //如果是子进程,执行脚本。
            execve(sj->argv[0],sj->argv,environ);
            /* If we are here an error occurred. */
            _exit(2); /* Don't retry execution. */
        } else {
            //如果是父进程,增加运行中脚本的计数,并记录子进程的PID。
            sentinel.running_scripts++;
            sj->pid = pid;
            sentinelEvent(LL_DEBUG,"+script-child",NULL,"%ld",(long)pid);
        }
    }
}

使用fork()函数创造子进程来运行脚本。

int sentinelTryConnectionSharing(sentinelRedisInstance *ri) {
    //使用 serverAssert 断言确保传入的实例是一个 Sentinel 实例。
    serverAssert(ri->flags & SRI_SENTINEL);
    dictIterator *di;
    dictEntry *de;
    //如果传入的 Sentinel 实例的运行 ID 为 NULL,则无法标识该实例,返回 C_ERR 表示尝试连接共享失败。
    if (ri->runid == NULL) return C_ERR; /* No way to identify it. */
    //如果传入的 Sentinel 实例的连接引用计数大于1,表示已经在共享连接,返回 C_ERR 表示尝试连接共享失败。
    if (ri->link->refcount > 1) return C_ERR; /* Already shared. */
    //获取主节点字典的迭代器。
    di = dictGetIterator(sentinel.masters);
    while((de = dictNext(di)) != NULL) {
        //获取当前迭代的主节点实例。
        sentinelRedisInstance *master = dictGetVal(de), *match;
        /* We want to share with the same physical Sentinel referenced
         * in other masters, so skip our master. */
        /*如果当前主节点是传入 Sentinel 实例的主节点,跳过,因为我们要共享与其他主节点关联的连接。*/
        if (master == ri->master) continue;
        /*在当前主节点的 Sentinel 列表中查找具有相同运行 ID 的 Sentinel 实例。*/
        match = getSentinelRedisInstanceByAddrAndRunID(master->sentinels,
                                                       NULL,0,ri->runid);
        /*如果找不到匹配的 Sentinel 实例,继续下一次迭代。*/
        if (match == NULL) continue; /* No match. */
        /*如果找到的匹配实例竟然是传入的 Sentinel 实例,这是不应该发生的,为了安全起见,跳过这个匹配。*/
        if (match == ri) continue; /* Should never happen but... safer. */

        /* We identified a matching Sentinel, great! Let's free our link
         * and use the one of the matching Sentinel. */
        /*释放传入 Sentinel 实例的连接,因为我们将共享其他 Sentinel 实例的连接。*/
        releaseInstanceLink(ri->link,NULL);
        //将传入 Sentinel 实例的连接指针设置为匹配 Sentinel 实例的连接。
        ri->link = match->link;
        //增加匹配 Sentinel 实例连接的引用计数,表示多了一个共享该连接的实例。
        match->link->refcount++;
        //释放主节点字典的迭代器。
        dictReleaseIterator(di);
        return C_OK;
    }
    dictReleaseIterator(di);
    return C_ERR;
}

使用此函数将多个sentinel的连接共享

int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip, int port) {
    sentinelAddr *oldaddr, *newaddr;
    sentinelAddr **slaves = NULL;
    int numslaves = 0, j;
    dictIterator *di;
    dictEntry *de;
    /*创建新地址对象,如果创建失败则返回 C_ERR。*/
    newaddr = createSentinelAddr(ip,port);
    if (newaddr == NULL) return C_ERR;

    /* Make a list of slaves to add back after the reset.
     * Don't include the one having the address we are switching to. */
    /*遍历主服务器实例的从服务器,将需要添加回主服务器实例的从服务器地址存储到 slaves 数组中。*/
    di = dictGetIterator(master->slaves);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *slave = dictGetVal(de);

        if (sentinelAddrIsEqual(slave->addr,newaddr)) continue;
        slaves = zrealloc(slaves,sizeof(sentinelAddr*)*(numslaves+1));
        slaves[numslaves++] = createSentinelAddr(slave->addr->ip,
                                                 slave->addr->port);
    }
    dictReleaseIterator(di);

    /* If we are switching to a different address, include the old address
     * as a slave as well, so that we'll be able to sense / reconfigure
     * the old master. */
    /*如果新的地址与旧的地址不相同,将旧的地址也添加到 slaves 数组中。*/
    if (!sentinelAddrIsEqual(newaddr,master->addr)) {
        slaves = zrealloc(slaves,sizeof(sentinelAddr*)*(numslaves+1));
        slaves[numslaves++] = createSentinelAddr(master->addr->ip,
                                                 master->addr->port);
    }

    /* Reset and switch address. */
    /*调用 sentinelResetMaster 函数重置主服务器实例,并切换主服务器实例的地址为新的地址。*/
    sentinelResetMaster(master,SENTINEL_RESET_NO_SENTINELS);
    oldaddr = master->addr;
    master->addr = newaddr;
    master->o_down_since_time = 0;
    master->s_down_since_time = 0;

    /* Add slaves back. */
    /*遍历 slaves 数组,为每个从服务器地址创建从服务器实例,并添加到主服务器实例的从服务器列表中。*/
    for (j = 0; j < numslaves; j++) {
        sentinelRedisInstance *slave;

        slave = createSentinelRedisInstance(NULL,SRI_SLAVE,slaves[j]->ip,
                    slaves[j]->port, master->quorum, master);
        releaseSentinelAddr(slaves[j]);
        if (slave) sentinelEvent(LL_NOTICE,"+slave",slave,"%@");
    }
    zfree(slaves);

    /* Release the old address at the end so we are safe even if the function
     * gets the master->addr->ip and master->addr->port as arguments. */
    /*释放旧的地址,并刷新 Sentinel 配置。返回 C_OK 表示操作成功。*/
    releaseSentinelAddr(oldaddr);
    sentinelFlushConfig();
    return C_OK;
}

这段代码实现了 sentinelResetMasterAndChangeAddress 函数,主要用于处理 +switch-master 事件。“+switch-master” 事件指的是发生主节点切换的事件。这种切换通常发生在以下情况下:

  1. 故障转移(Failover): 当 Sentinel 监测到主节点不可达时,它可能会启动故障转移流程,将一个从节点提升为新的主节点,以保持服务的可用性。
  2. 手动主节点切换: 管理员手动触发的主节点切换,例如为了进行维护或升级。

当发生主节点切换时,Sentinel 会发布一个名为 “+switch-master” 的事件,通知其他 Sentinel 和客户端发生了主节点的切换。在切换过程中,新的主节点会接管服务,而其他 Sentinel 和客户端需要更新他们的配置以连接到新的主节点。
函数的目的是重置指定的主节点实例,同时更改其 IP 地址和端口(地址可以改变,但实例的名字保持不变)。这个操作主要是为了处理 “+switch-master” 事件,确保 Sentinel 正确地适应主节点切换。如果给定的地址无法解析,函数会返回错误(C_ERR),否则返回成功(C_OK)。

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;
    /*如果当前实例是主节点(ri->flags & SRI_MASTER 为真),则将 master 设置为当前实例 ri;
    否则,将 master 设置为 ri 的主节点。*/
    sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ? ri : ri->master;
    sentinelAddr *master_addr = sentinelGetCurrentMasterAddress(master);
    /*如果与这个 Sentinel 实例相关的连接已经断开,则返回 C_ERR 表示出现错误。*/
    if (ri->link->disconnected) return C_ERR;

    /* Use the specified announce address if specified, otherwise try to
     * obtain our own IP address. */
    /*
    如果配置中指定了 announce_ip,则使用该地址,否则尝试获取当前连接的本地 IP 地址。
    如果配置中指定了 announce_port,则使用该端口,否则根据 TLS 配置选择端口。
    */
    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;
    }
    if (sentinel.announce_port) announce_port = sentinel.announce_port;
    else if (server.tls_replication && server.tls_port) announce_port = server.tls_port;
    else announce_port = server.port;

    /* Format and send the Hello message. */
    /*使用 snprintf 函数将 "Hello" 消息的内容格式化为指定的格式。*/
    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);
    /*使用 redisAsyncCommand 函数向连接发送异步命令,命令是通过 PUBLISH 发布 "Hello" 消息到指定的频道 SENTINEL_HELLO_CHANNEL。
sentinelPublishReplyCallback 是异步命令完成时的回调函数,ri 作为私有数据传递给这个回调函数。*/
    retval = redisAsyncCommand(ri->link->cc,
        sentinelPublishReplyCallback, ri, "%s %s %s",
        sentinelInstanceMapCommand(ri,"PUBLISH"),
        SENTINEL_HELLO_CHANNEL,payload);
    /*如果发送异步命令成功,增加连接的待处理命令计数,并返回 C_OK 表示成功;否则,返回 C_ERR 表示出现错误。*/
    if (retval != C_OK) return C_ERR;
    ri->link->pending_commands++;
    return C_OK;
}

这段注释描述的是一个函数,该函数用于通过 Pub/Sub 向指定的 Redis 实例发送一个 “Hello” 消息,目的是广播当前主节点的配置信息,并同时宣告这个 Sentinel 的存在。这个 “Hello” 消息的发送是为了在 Sentinel 集群中宣告当前 Sentinel 实例的存在,同时提供有关主节点的配置信息。其他 Sentinel 实例通过订阅相应的频道来接收这些消息,从而了解整个集群的状态。这有助于实现 Sentinel 之间的协调和故障检测。

void sentinelReconnectInstance(sentinelRedisInstance *ri) {
    /*首先,代码检查实例连接的状态。如果连接没有断开(link->disconnected == 0),则说明不需要重新连接,直接返回。*/
    if (ri->link->disconnected == 0) return;
    /*如果连接的端口号为 0,表示地址无效,同样直接返回。*/
    if (ri->addr->port == 0) return; /* port == 0 means invalid address. */
    /*获取实例连接的信息,计算当前时间。*/
    instanceLink *link = ri->link;
    mstime_t now = mstime();
    /*检查是否距离上次重连时间超过了 SENTINEL_PING_PERIOD(以毫秒为单位)。如果未超过,表示不需要进行重新连接。*/
    if (now - ri->link->last_reconn_time < SENTINEL_PING_PERIOD) return;
    ri->link->last_reconn_time = now;

    /* Commands connection. */
    /*如果 Commands 连接(link->cc)不存在,通过 redisAsyncConnectBind 创建一个连接。
    这是 Sentinel 与其他 Redis 实例进行命令通信的连接。*/
    if (link->cc == NULL) {
        link->cc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
        if (!link->cc->err && server.tls_replication &&
                (instanceLinkNegotiateTLS(link->cc) == C_ERR)) {
            sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #Failed to initialize TLS");
            instanceLinkCloseConnection(link,link->cc);
        } else if (link->cc->err) {//如果创建连接时出现错误,记录错误信息,并关闭连接。
            sentinelEvent(LL_DEBUG,"-cmd-link-reconnection",ri,"%@ #%s",
                link->cc->errstr);
            instanceLinkCloseConnection(link,link->cc);
        } else {//如果连接创建成功,设置连接的回调函数、认证信息、客户端名,并发送一个 PING 以确保连接正常。
            link->pending_commands = 0;
            link->cc_conn_time = mstime();
            link->cc->data = link;
            redisAeAttach(server.el,link->cc);
            redisAsyncSetConnectCallback(link->cc,
                    sentinelLinkEstablishedCallback);
            redisAsyncSetDisconnectCallback(link->cc,
                    sentinelDisconnectCallback);
            sentinelSendAuthIfNeeded(ri,link->cc);
            sentinelSetClientName(ri,link->cc,"cmd");

            /* Send a PING ASAP when reconnecting. */
            sentinelSendPing(ri);
        }
    }
    /* Pub / Sub */
    /*如果实例是主或从节点,并且 Pub / Sub 连接(link->pc)不存在,通过 redisAsyncConnectBind 创建 Pub / Sub 连接。
    这是 Sentinel 与其他 Redis 实例进行发布订阅通信的连接。*/
    if ((ri->flags & (SRI_MASTER|SRI_SLAVE)) && link->pc == NULL) {
        // 创建 Pub / Sub 连接
        link->pc = redisAsyncConnectBind(ri->addr->ip,ri->addr->port,NET_FIRST_BIND_ADDR);
        if (!link->pc->err && server.tls_replication &&
                (instanceLinkNegotiateTLS(link->pc) == C_ERR)) {
            sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #Failed to initialize TLS");
        } else if (link->pc->err) {//如果创建连接时出现错误,记录错误信息,并关闭连接。
            sentinelEvent(LL_DEBUG,"-pubsub-link-reconnection",ri,"%@ #%s",
                link->pc->errstr);
            instanceLinkCloseConnection(link,link->pc);
        } else {//如果连接创建成功,设置连接的回调函数、认证信息、客户端名,并通过 Pub / Sub 连接订阅 Sentinel Hello 消息。
            int retval;

            link->pc_conn_time = mstime();
            link->pc->data = link;
            redisAeAttach(server.el,link->pc);
            redisAsyncSetConnectCallback(link->pc,
                    sentinelLinkEstablishedCallback);
            redisAsyncSetDisconnectCallback(link->pc,
                    sentinelDisconnectCallback);
            sentinelSendAuthIfNeeded(ri,link->pc);
            sentinelSetClientName(ri,link->pc,"pubsub");
            /* Now we subscribe to the Sentinels "Hello" channel. */
            /*这段代码的作用是在 Pub/Sub 连接上向 Sentinels "Hello" 频道发送 SUBSCRIBE 命令,以便接收其他 Sentinel 实例发送的 Hello 消息。*/
            retval = redisAsyncCommand(link->pc,
                sentinelReceiveHelloMessages, ri, "%s %s",
                sentinelInstanceMapCommand(ri,"SUBSCRIBE"),
                SENTINEL_HELLO_CHANNEL);
            if (retval != C_OK) {
                /* If we can't subscribe, the Pub/Sub connection is useless
                 * and we can simply disconnect it and try again. */
                /*如果 retval 不等于 C_OK,表示发送 SUBSCRIBE 命令失败,此时 Pub/Sub 连接对于接收 Hello 消息无效。
                    在这种情况下,将 Pub/Sub 连接关闭,并在需要时尝试重新连接。*/
                instanceLinkCloseConnection(link,link->pc);
                return;
            }
        }
    }
    /* Clear the disconnected status only if we have both the connections
     * (or just the commands connection if this is a sentinel instance). */
    /*如果成功创建了 Commands 连接,或者 Sentinel 实例并且成功创建了 Pub / Sub 连接,则将连接的断开状态标记为未断开。*/
    if (link->cc && (ri->flags & SRI_SENTINEL || link->pc))
        link->disconnected = 0;
}

用于重新建立与 Redis 实例之间的异步连接的函数,主要用于 Sentinel 与其他 Redis 实例之间的连接。

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;
    /*如果当前实例是主节点(ri->flags & SRI_MASTER 为真),则将 master 设置为当前实例 ri;
    否则,将 master 设置为 ri 的主节点。*/
    sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ? ri : ri->master;
    sentinelAddr *master_addr = sentinelGetCurrentMasterAddress(master);
    /*如果与这个 Sentinel 实例相关的连接已经断开,则返回 C_ERR 表示出现错误。*/
    if (ri->link->disconnected) return C_ERR;

    /* Use the specified announce address if specified, otherwise try to
     * obtain our own IP address. */
    /*
    如果配置中指定了 announce_ip,则使用该地址,否则尝试获取当前连接的本地 IP 地址。
    如果配置中指定了 announce_port,则使用该端口,否则根据 TLS 配置选择端口。
    */
    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;
    }
    if (sentinel.announce_port) announce_port = sentinel.announce_port;
    else if (server.tls_replication && server.tls_port) announce_port = server.tls_port;
    else announce_port = server.port;

    /* Format and send the Hello message. */
    /*使用 snprintf 函数将 "Hello" 消息的内容格式化为指定的格式。*/
    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);
    /*使用 redisAsyncCommand 函数向连接发送异步命令,命令是通过 PUBLISH 发布 "Hello" 消息到指定的频道 SENTINEL_HELLO_CHANNEL。
sentinelPublishReplyCallback 是异步命令完成时的回调函数,ri 作为私有数据传递给这个回调函数。*/
    retval = redisAsyncCommand(ri->link->cc,
        sentinelPublishReplyCallback, ri, "%s %s %s",
        sentinelInstanceMapCommand(ri,"PUBLISH"),
        SENTINEL_HELLO_CHANNEL,payload);
    /*如果发送异步命令成功,增加连接的待处理命令计数,并返回 C_OK 表示成功;否则,返回 C_ERR 表示出现错误。*/
    if (retval != C_OK) return C_ERR;
    ri->link->pending_commands++;
    return C_OK;
}

这个 “Hello” 消息的发送是为了在 Sentinel 集群中宣告当前 Sentinel 实例的存在,同时提供有关主节点的配置信息。其他 Sentinel 实例通过订阅相应的频道来接收这些消息,从而了解整个集群的状态。这有助于实现 Sentinel 之间的协调和故障检测。

void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
    //获取当前时间和设置周期性命令的时间间隔
    mstime_t now = mstime();
    mstime_t info_period, ping_period;
    int retval;

    /* Return ASAP if we have already a PING or INFO already pending, or
     * in the case the instance is not properly connected. */
    //检查连接状态和待处理命令数
    //如果连接已断开或者待处理命令数达到阈值,就直接返回,避免发送过多命令。
    if (ri->link->disconnected) return;

    /* For INFO, PING, PUBLISH that are not critical commands to send we
     * also have a limit of SENTINEL_MAX_PENDING_COMMANDS. We don't
     * want to use a lot of memory just because a link is not working
     * properly (note that anyway there is a redundant protection about this,
     * that is, the link will be disconnected and reconnected if a long
     * timeout condition is detected. */
    if (ri->link->pending_commands >=
        SENTINEL_MAX_PENDING_COMMANDS * ri->link->refcount) return;

    /* If this is a slave of a master in O_DOWN condition we start sending
     * it INFO every second, instead of the usual SENTINEL_INFO_PERIOD
     * period. In this state we want to closely monitor slaves in case they
     * are turned into masters by another Sentinel, or by the sysadmin.
     *
     * Similarly we monitor the INFO output more often if the slave reports
     * to be disconnected from the master, so that we can have a fresh
     * disconnection time figure. */
    /*
    如果实例是从节点并且主节点处于 O_DOWN 或 FAILOVER_IN_PROGRESS 状态,或者从节点上报主节点连接断开,
    将 INFO 命令发送周期设置为 1000 毫秒,否则使用配置中的 SENTINEL_INFO_PERIOD。
    */
    if ((ri->flags & SRI_SLAVE) &&
        ((ri->master->flags & (SRI_O_DOWN|SRI_FAILOVER_IN_PROGRESS)) ||
         (ri->master_link_down_time != 0)))
    {
        info_period = 1000;
    } else {
        info_period = SENTINEL_INFO_PERIOD;
    }

    /* We ping instances every time the last received pong is older than
     * the configured 'down-after-milliseconds' time, but every second
     * anyway if 'down-after-milliseconds' is greater than 1 second. */
    /*
    将 PING 命令的发送周期设置为 down-after-milliseconds 配置的时间,但不超过 SENTINEL_PING_PERIOD。
    如果 down-after-milliseconds 大于 1 秒,则将 PING 命令的发送周期设置为 1 秒。
    */
    ping_period = ri->down_after_period;
    if (ping_period > SENTINEL_PING_PERIOD) ping_period = SENTINEL_PING_PERIOD;

    /* Send INFO to masters and slaves, not sentinels. */
    /*发送 INFO 命令给主节点和从节点(而不是 Sentinel)*/
    /*
    如果当前实例不是 Sentinel,并且 INFO 命令的刷新时间为 0(即未刷新过),
    或者距离上次刷新 INFO 命令的时间超过了 INFO 命令的发送周期,则通过异步命令发送 INFO 命令。
        sentinelInfoReplyCallback 是异步命令完成时的回调函数。  
    */
    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++;
    }

    /* Send PING to all the three kinds of instances. */
    //发送 PING 命令给所有类型的实例
    /*
    如果距离上一次收到 PONG 命令的时间超过了配置的 down-after-milliseconds 时间,
    并且距离上一次发送 PING 命令的时间超过了 PING 命令发送周期的一半,
    则通过 sentinelSendPing 函数发送 PING 命令。
    */
    if ((now - ri->link->last_pong_time) > ping_period &&
               (now - ri->link->last_ping_time) > ping_period/2) {
        sentinelSendPing(ri);
    }

    /* PUBLISH hello messages to all the three kinds of instances. */
    //发送 PUBLISH 命令(Hello 消息)给所有类型的实例
    if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
        sentinelSendHello(ri);
    }
}

这段代码的作用是在一定条件下周期性地向指定的主节点或从节点实例发送 PING、INFO 和 PUBLISH 命令,以保持与这些实例的通信并更新状态信息。这有助于 Sentinel 实时监测 Redis 集群的状态,并采取适当的措施来处理可能的故障情况。

#define SENTINEL_ISQR_OK 0/* 仲裁可达 */
#define SENTINEL_ISQR_NOQUORUM (1<<0)/* 仲裁不可用,缺少足够的 Sentinel */
#define SENTINEL_ISQR_NOAUTH (1<<1)/* 仲裁不可用,缺少足够的授权 Sentinel */
int sentinelIsQuorumReachable(sentinelRedisInstance *master, int *usableptr) {
    dictIterator *di;
    dictEntry *de;
    /* Sentinel 实例的数量,初始为1用于计算自身。 */
    int usable = 1; /* Number of usable Sentinels. Init to 1 to count myself. */
    int result = SENTINEL_ISQR_OK;/* 返回的结果,默认为仲裁可达。 */
    int voters = dictSize(master->sentinels)+1; /* Known Sentinels + myself. *//* 已知的 Sentinel 数量 + 1(自身)。 */
    // 遍历主服务器的 Sentinel 列表
    di = dictGetIterator(master->sentinels);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *ri = dictGetVal(de);
        // 如果 Sentinel 实例处于 S_DOWN 或 O_DOWN 状态,则跳过
        if (ri->flags & (SRI_S_DOWN|SRI_O_DOWN)) continue;
        // 可用 Sentinel 实例数量加一
        usable++;
    }
    dictReleaseIterator(di);
    // 如果可用 Sentinel 实例数量小于仲裁所需数量,设置标志位 SENTINEL_ISQR_NOQUORUM
    if (usable < (int)master->quorum) result |= SENTINEL_ISQR_NOQUORUM;
    // 如果可用 Sentinel 实例数量小于总投票者数量的一半加一,设置标志位 SENTINEL_ISQR_NOAUTH
    if (usable < voters/2+1) result |= SENTINEL_ISQR_NOAUTH;
     // 如果提供了 usableptr 指针,将可用 Sentinel 实例数量保存到 usableptr 中
    if (usableptr) *usableptr = usable;
    return result;
}

在sentinel中进行仲裁必须提供足够的实例存在。

void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
    mstime_t elapsed = 0;//声明并初始化一个表示时间间隔的变量 elapsed,单位是毫秒。
//检查 Redis 实例的链接状态。如果存在活动的 ping 时间戳(act_ping_time),则计算距离最后一次 ping 的时间间隔;
//否则,如果链接处于断开状态,则计算距离最后一次可用时间的时间间隔。
    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;

    /* Check if we are in need for a reconnection of one of the
     * links, because we are detecting low activity.
     *
     * 1) Check if the command link seems connected, was connected not less
     *    than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have a
     *    pending ping for more than half the timeout. */
    /*
    下面的注释和代码是为了检测是否需要重新连接 Redis 实例的命令链接(command link)或发布/订阅链接(pubsub link)。具体而言:
    对于命令链接,如果距离链接建立时间已经过去一定的时间(SENTINEL_MIN_LINK_RECONNECT_PERIOD),且存在挂起的 ping 请求,
    而且 ping 请求的延迟超过下线时间的一半,并且最后一次收到 pong 的时间也超过下线时间的一半,那么关闭链接。
    */
    if (ri->link->cc &&
        (mstime() - ri->link->cc_conn_time) >
        SENTINEL_MIN_LINK_RECONNECT_PERIOD &&
        ri->link->act_ping_time != 0 && /* There is a pending ping... */
        /* The pending ping is delayed, and we did not receive
         * 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);
    }

    /* 2) Check if the pubsub link seems connected, was connected not less
     *    than SENTINEL_MIN_LINK_RECONNECT_PERIOD, but still we have no
     *    activity in the Pub/Sub channel for more than
     *    SENTINEL_PUBLISH_PERIOD * 3.
     */
    /*对于发布/订阅链接,如果距离链接建立时间已经过去一定的时间,且在过去的时间里没有活动(即 Pub/Sub 频道的活动),那么关闭链接。*/
    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);
    }

    /* Update the SDOWN flag. We believe the instance is SDOWN if:
     *
     * 1) It is not replying.
     * 2) We believe it is a master, it reports to be a slave for enough time
     *    to meet the down_after_period, plus enough time to get two times
     *    INFO report from the instance. */
    /*更新 Sentinel 对实例的主观下线(SDOWN)标记。*/
    /*
    如果 Redis 实例不回复(ping 时间间隔超过 down_after_period),或者 Sentinel 认为它是主节点但它报告自己是从节点,
    并且在足够的时间内(down_after_period + SENTINEL_INFO_PERIOD * 2)没有再次报告为主节点,那么 Sentinel 认为实例主观下线。
    */
    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);
        }
    }
}

这段代码说明了如何进行实例主观下线的判断。

char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char *req_runid, uint64_t *leader_epoch) {
    /*检查请求的纪元是否大于当前纪元。如果是,表示发生了新的纪元,更新当前纪元为请求的纪元,并刷新配置。*/
    if (req_epoch > sentinel.current_epoch) {
        sentinel.current_epoch = req_epoch;
        sentinelFlushConfig();
        /*生成并发送关于新纪元的事件通知。*/
        sentinelEvent(LL_WARNING,"+new-epoch",master,"%llu",
            (unsigned long long) sentinel.current_epoch);
    }
/*检查当前主节点的领导者纪元是否小于请求的纪元,且当前纪元小于等于请求的纪元。如果是,表示可以进行新一轮的投票。*/
    if (master->leader_epoch < req_epoch && sentinel.current_epoch <= req_epoch)
    {
        /*释放当前主节点的领导者运行 ID。*/
        sdsfree(master->leader);
        /*设置当前主节点的领导者运行 ID 为请求的运行 ID。*/
        master->leader = sdsnew(req_runid);
        /*设置当前主节点的领导者纪元为当前纪元。*/
        master->leader_epoch = sentinel.current_epoch;
        /*刷新 Sentinel 配置。*/
        sentinelFlushConfig();
        /*生成并发送关于投票的事件通知。*/
        sentinelEvent(LL_WARNING,"+vote-for-leader",master,"%s %llu",
            master->leader, (unsigned long long) master->leader_epoch);
        /* If we did not voted for ourselves, set the master failover start
         * time to now, in order to force a delay before we can start a
         * failover for the same master. */
        /*如果当前 Sentinel 没有投票给自己,将当前主节点的故障转移开始时间设置为当前时间加上一个随机值,
        以强制在同一主节点进行故障转移之前等待一段时间。*/
        /*
        故障转移是 Sentinel 集群中的一种机制,用于在主节点失效时选举新的主节点,
        确保系统的可用性。当主节点因为故障或其他原因不可用时,其他 Sentinel 实例会发起故障转移流程,
        选举新的主节点,并将其他从节点切换到新的主节点,以保持集群的正常运行。

        在分布式系统中,故障转移需要经过一系列步骤,例如选举、切换从节点等。
        由于 Sentinel 实例之间可能存在通信延迟和网络不稳定性,如果多个 Sentinel 实例在短时间内同时发起故障转移,
        可能导致竞争和冲突,降低系统的可控性和稳定性。

        为了避免这种情况,引入随机性和延迟是一种常见的做法。通过设置故障转移开始时间的随机延迟,
        可以确保不同 Sentinel 实例在不同的时间启动故障转移,减少竞争,提高系统的可用性和可控性。

        总的来说,故障转移是为了保证分布式系统在主节点失效时能够迅速、稳定地选举新的主节点,
        而引入随机性和延迟则是为了减少竞争和冲突,提高系统的稳定性。
        */
        if (strcasecmp(master->leader,sentinel.myid))
            master->failover_start_time = mstime()+rand()%SENTINEL_MAX_DESYNC;
    }
    /*将主节点的领导者纪元写入传入的指针。*/
    *leader_epoch = master->leader_epoch;
    /*如果当前主节点有领导者运行 ID,则返回其拷贝,否则返回 NULL。*/
    return master->leader ? sdsnew(master->leader) : NULL;
}

struct sentinelLeader {
    char *runid;//这是一个指向字符数组的指针,用于存储 Sentinel 实例的运行 ID。运行 ID 通常是唯一标识一个 Sentinel 实例的字符串。
    unsigned long votes;//这是一个表示 Sentinel 实例获得的选票数量的无符号长整型变量。
};

该函数用于进行选举投票,其中每个 Sentinel 可以投票给另一个 Sentinel。函数会检查是否已经为指定的 req_epoch 或更大的纪元投过票,如果是,则返回先前的投票;否则,为指定的 req_runid 进行新的投票。如果没有可用的投票(即没有符合条件的投票),函数返回 NULL;否则,返回当前 Sentinel 所投的 Sentinel 的运行 ID,并将 leader_epoch(领导者的纪元)填充为投票的纪元。这提供了 Sentinel 集群中 Sentinel 之间协同一致的投票机制。

char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
    /*创建一个字典 counters 用于存储每个运行 ID 的选票计数。*/
    dict *counters;
    dictIterator *di;
    dictEntry *de;
    unsigned int voters = 0, voters_quorum;
    char *myvote;
    char *winner = NULL;
    uint64_t leader_epoch;
    uint64_t max_votes = 0;

    serverAssert(master->flags & (SRI_O_DOWN|SRI_FAILOVER_IN_PROGRESS));
    counters = dictCreate(&leaderVotesDictType,NULL);
    /*计算投票者总数 voters,包括当前 Sentinel 实例和其他所有连接到主节点的 Sentinel 实例。*/
    voters = dictSize(master->sentinels)+1; /* All the other sentinels and me.*/

    /* Count other sentinels votes */
    /*遍历主节点的所有连接的 Sentinel 实例,检查它们是否报告了相同的领导者(leader)并且领导者的选举轮次(epoch)与当前 epoch 一致。
    如果一致,将相应领导者的运行 ID 增加到 counters 中。*/
    di = dictGetIterator(master->sentinels);
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *ri = dictGetVal(de);
        if (ri->leader != NULL && ri->leader_epoch == sentinel.current_epoch)
            sentinelLeaderIncr(counters,ri->leader);
    }
    dictReleaseIterator(di);

    /* Check what's the winner. For the winner to win, it needs two conditions:
     * 1) Absolute majority between voters (50% + 1).
     * 2) And anyway at least master->quorum votes. */
    /*
    遍历 counters 字典,找到得票最多的领导者,即满足绝对多数(50% + 1)并且至少获得 master->quorum 票的领导者。将该领导者的运行 ID 赋给 winner。
    */
    di = dictGetIterator(counters);
    while((de = dictNext(di)) != NULL) {
        uint64_t votes = dictGetUnsignedIntegerVal(de);

        if (votes > max_votes) {
            max_votes = votes;
            winner = dictGetKey(de);
        }
    }
    dictReleaseIterator(di);

    /* Count this Sentinel vote:
     * if this Sentinel did not voted yet, either vote for the most
     * common voted sentinel, or for itself if no vote exists at all. */
    /*如果当前 Sentinel 实例尚未投票,根据之前的选举结果投票给得票最多的领导者或者自己。更新 counters 字典中相应的计数。*/
    if (winner)
        myvote = sentinelVoteLeader(master,epoch,winner,&leader_epoch);
    else
        myvote = sentinelVoteLeader(master,epoch,sentinel.myid,&leader_epoch);
    /*检查当前 Sentinel 实例是否成功投票,并且投票的选举轮次与当前选举轮次一致。*/
    if (myvote && leader_epoch == epoch) {
        /*如果投票成功,则调用 sentinelLeaderIncr 函数,为当前 Sentinel 实例得票。该函数会增加 counters 字典中相应运行 ID 的计数。*/
        uint64_t votes = sentinelLeaderIncr(counters,myvote);
        /*如果当前 Sentinel 实例的得票数更多,更新最高票数。
        将当前 Sentinel 实例的运行 ID 设为当前的领导者。*/
        if (votes > max_votes) {
            max_votes = votes;
            winner = myvote;
        }
    }

    /*根据投票结果和投票要求,判断是否选出了领导者。如果获得票数未达到绝对多数或未达到主节点配置的 quorum 要求,将 winner 设为 NULL。*/
    voters_quorum = voters/2+1;
    if (winner && (max_votes < voters_quorum || max_votes < master->quorum))
        winner = NULL;
    /*最终返回 winner,即当前时刻的领导者的运行 ID。*/
    winner = winner ? sdsnew(winner) : NULL;
    sdsfree(myvote);
    dictRelease(counters);
    return winner;
}

这段代码的主要作用是在 Sentinel 集群中进行领导者选举,并确定特定时刻的领导者(leader)。在 Sentinel 集群中,领导者负责主节点的监控和管理,而领导者选举是确保 Sentinel 集群中各个 Sentinel 实例达成一致的关键机制。

int compareSlavesForPromotion(const void *a, const void *b) {
    sentinelRedisInstance **sa = (sentinelRedisInstance **)a,
                          **sb = (sentinelRedisInstance **)b;
    char *sa_runid, *sb_runid;
    /*如果两个从节点的优先级不同,直接按照优先级进行比较。较低优先级的从节点排在前面。*/
    if ((*sa)->slave_priority != (*sb)->slave_priority)
        return (*sa)->slave_priority - (*sb)->slave_priority;

    /* If priority is the same, select the slave with greater replication
     * offset (processed more data from the master). */
    /*如果优先级相同,比较两个从节点的复制偏移量。较大偏移量的从节点排在前面。*/
    if ((*sa)->slave_repl_offset > (*sb)->slave_repl_offset) {
        return -1; /* a < b */
    } else if ((*sa)->slave_repl_offset < (*sb)->slave_repl_offset) {
        return 1; /* a > b */
    }

    /* If the replication offset is the same select the slave with that has
     * the lexicographically smaller runid. Note that we try to handle runid
     * == NULL as there are old Redis versions that don't publish runid in
     * INFO. A NULL runid is considered bigger than any other runid. */
    /*获取两个从节点的 runid(Redis节点的唯一标识符)。*/
    sa_runid = (*sa)->runid;
    sb_runid = (*sb)->runid;
    /*如果两个从节点的 runid 都为 NULL,返回 0,表示 runid 相等。*/
    if (sa_runid == NULL && sb_runid == NULL) return 0;
    /*如果从节点 a 的 runid 为 NULL,而 b 的不是,返回 1,表示 b 排在前面。*/
    else if (sa_runid == NULL) return 1;  /* a > b */
    /*如果从节点 b 的 runid 为 NULL,而 a 的不是,返回 -1,表示 a 排在前面。*/
    else if (sb_runid == NULL) return -1; /* a < b */
    /*如果两个从节点的 runid 都不为 NULL,则使用字典序忽略大小写比较它们,返回比较的结果。*/
    return strcasecmp(sa_runid, sb_runid);
}

//该函数的目的是根据一系列条件选择最适合升级为主节点的从节点,确保被选中的从节点在各个方面都是相对较好的选择。
sentinelRedisInstance *sentinelSelectSlave(sentinelRedisInstance *master) {
    /*通过 zmalloc 分配一个大小为从节点数量的数组,用于存储候选从节点的指针*/
    sentinelRedisInstance **instance =
        zmalloc(sizeof(instance[0])*dictSize(master->slaves));
    /*用于存储最终选择的从节点。*/
    sentinelRedisInstance *selected = NULL;
    /*记录符合条件的从节点的数量。*/
    int instances = 0;
    /*用于迭代从节点的字典。*/
    dictIterator *di;
    dictEntry *de;
    /*计算最大主节点宕机时间,用于过滤掉宕机时间超过一定阈值的从节点。*/
    mstime_t max_master_down_time = 0;
    /*如果主节点处于 S_DOWN(从观点下线)状态,则增加主节点的宕机时间。*/
    if (master->flags & SRI_S_DOWN)
        max_master_down_time += mstime() - master->s_down_since_time;
    max_master_down_time += master->down_after_period * 10;

    /*获取从节点字典的迭代器。*/
    di = dictGetIterator(master->slaves);
    /*
    如果从节点处于 S_DOWN 或 O_DOWN 状态,跳过。
    如果从节点的链接已经断开,跳过。
    如果从节点距离上次可用时间超过 5 倍的 PING 周期,跳过。
    如果从节点的优先级为 0,跳过。
    根据主节点的状态,计算 INFO 数据的有效性时间。
    如果从节点的 INFO 数据刷新时间超过 INFO 数据有效性时间,跳过。
    如果从节点的 master_link_down_time 超过最大主节点宕机时间,跳过。
    */
    while((de = dictNext(di)) != NULL) {//遍历从节点字典。
        sentinelRedisInstance *slave = dictGetVal(de);//获取当前迭代的从节点。
        mstime_t info_validity_time;

        if (slave->flags & (SRI_S_DOWN|SRI_O_DOWN)) continue;
        if (slave->link->disconnected) continue;
        if (mstime() - slave->link->last_avail_time > SENTINEL_PING_PERIOD*5) continue;
        if (slave->slave_priority == 0) continue;

        /* If the master is in SDOWN state we get INFO for slaves every second.
         * Otherwise we get it with the usual period so we need to account for
         * a larger delay. */
        if (master->flags & SRI_S_DOWN)
            info_validity_time = SENTINEL_PING_PERIOD*5;
        else
            info_validity_time = SENTINEL_INFO_PERIOD*3;
        if (mstime() - slave->info_refresh > info_validity_time) continue;
        if (slave->master_link_down_time > max_master_down_time) continue;
        instance[instances++] = slave;/*将符合条件的从节点加入候选数组。*/
    }
    dictReleaseIterator(di);
    if (instances) {
        /*对候选从节点数组进行排序,使用 compareSlavesForPromotion 函数定义的比较规则。*/
        qsort(instance,instances,sizeof(sentinelRedisInstance*),
            compareSlavesForPromotion);
        selected = instance[0];//选择排序后的第一个从节点作为最终选择。
    }
    zfree(instance);
    return selected;
}

在进行故障转移时从从节点中选择较好的节点成为主节点。

void sentinelHandleRedisInstance(sentinelRedisInstance *ri) {
    /* ========== MONITORING HALF ============ */
    /* Every kind of instance */
    sentinelReconnectInstance(ri);/* 用于检测并重新建立与实例的连接。*/
    sentinelSendPeriodicCommands(ri);/*用于向实例定期发送命令,以便检测其是否健康。*/

    /*行动半部分:下面的操作涉及根据监控结果采取行动,包括检测实例是否下线、启动故障切换等。*/
    /* ============== ACTING HALF ============= */
    /* We don't proceed with the acting half if we are in TILT mode.
     * TILT happens when we find something odd with the time, like a
     * sudden change in the clock. */
    /*如果 Sentinel 处于 TILT 模式,表示发现时间上的异常,比如时钟的突然变化,则暂停执行行动半部分的操作。
    TILT 模式在一段时间后自动退出,并记录一条退出 TILT 模式的日志。*/
    if (sentinel.tilt) {
        if (mstime()-sentinel.tilt_start_time < SENTINEL_TILT_PERIOD) return;
        sentinel.tilt = 0;
        sentinelEvent(LL_WARNING,"-tilt",NULL,"#tilt mode exited");
    }

    /* Every kind of instance */
    /*对于所有类型的实例,检测主观下线状态,即 Sentinel 通过自身监控认为实例可能已经下线。*/
    sentinelCheckSubjectivelyDown(ri);

    /* Masters and slaves */
    /*对于主节点和从节点,目前没有特定的操作。*/
    if (ri->flags & (SRI_MASTER|SRI_SLAVE)) {
        /* Nothing so far. */
    }

    /* Only masters */
    if (ri->flags & SRI_MASTER) {
        sentinelCheckObjectivelyDown(ri);/*检测客观下线状态,即通过与其他 Sentinel 进行协商来确定主节点是否被认为已下线。*/
        if (sentinelStartFailoverIfNeeded(ri))/*如果符合条件,则启动故障切换。*/
            sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_ASK_FORCED);/*向其他 Sentinel 发送请求,通知它们主节点的状态,可能包括强制要求它们更新状态。*/
        sentinelFailoverStateMachine(ri);/*执行故障切换状态机的操作,推进故障切换的各个阶段。*/
        sentinelAskMasterStateToOtherSentinels(ri,SENTINEL_NO_FLAGS);/*再次调用 sentinelAskMasterStateToOtherSentinels:通知其他 Sentinel 主节点的状态,不携带特定标志。*/
    }
}


/*这段代码是 Redis Sentinel 中用于执行计划操作的函数 */
/*该函数执行针对给定字典中所有 Redis 实例的计划操作,并对从节点和 Sentinel 的字典进行递归调用。*/
void sentinelHandleDictOfRedisInstances(dict *instances) {
    dictIterator *di;
    dictEntry *de;
    sentinelRedisInstance *switch_to_promoted = NULL;

    /* There are a number of things we need to perform against every master. */
    /*定义字典迭代器和字典条目,以及一个指向 sentinelRedisInstance 结构的指针 switch_to_promoted,用于标识需要切换到被提升的节点。*/
    di = dictGetIterator(instances);
    /*
    对字典中的每个 Redis 实例执行计划操作。
    如果实例是主节点,还对其从节点和 Sentinel 进行递归调用,以处理更深层次的实例结构。
    如果主节点的故障切换状态为 SENTINEL_FAILOVER_STATE_UPDATE_CONFIG,则记录需要切换到被提升的主节点。
    */
    while((de = dictNext(di)) != NULL) {
        sentinelRedisInstance *ri = dictGetVal(de);

        sentinelHandleRedisInstance(ri);
        if (ri->flags & SRI_MASTER) {
            sentinelHandleDictOfRedisInstances(ri->slaves);
            sentinelHandleDictOfRedisInstances(ri->sentinels);
            if (ri->failover_state == SENTINEL_FAILOVER_STATE_UPDATE_CONFIG) {
                switch_to_promoted = ri;
            }
        }
    }
    if (switch_to_promoted)
        /*如果存在需要切换到被提升的主节点,调用 sentinelFailoverSwitchToPromotedSlave 函数执行切换操作。*/
        sentinelFailoverSwitchToPromotedSlave(switch_to_promoted);
    dictReleaseIterator(di);
}


void sentinelCheckTiltCondition(void) {
    mstime_t now = mstime();//获取当前时间。
    mstime_t delta = now - sentinel.previous_time; // 计算与上一次检查之间的时间差

    if (delta < 0 || delta > SENTINEL_TILT_TRIGGER) {
        sentinel.tilt = 1;// 进入 TILT 模式标志设为 1
        sentinel.tilt_start_time = mstime();// 记录进入 TILT 模式的时间
        sentinelEvent(LL_WARNING,"+tilt",NULL,"#tilt mode entered");// 发送日志事件表示进入 TILT 模式
    }
    sentinel.previous_time = mstime(); // 更新上一次检查的时间
}

//这段代码是 Sentinel 中定时器的主函数,负责执行 Sentinel 的一些周期性操作。
void sentinelTimer(void) {
    sentinelCheckTiltCondition();// 检查是否需要进入 TILT 模式
    sentinelHandleDictOfRedisInstances(sentinel.masters);// 处理所有 Redis 实例的定期操作
    sentinelRunPendingScripts(); // 运行待执行的脚本
    sentinelCollectTerminatedScripts(); // 收集已终止的脚本
    sentinelKillTimedoutScripts();// 终止超时的脚本

    /* We continuously change the frequency of the Redis "timer interrupt"
     * in order to desynchronize every Sentinel from every other.
     * This non-determinism avoids that Sentinels started at the same time
     * exactly continue to stay synchronized asking to be voted at the
     * same time again and again (resulting in nobody likely winning the
     * election because of split brain voting). */

    server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;// 动态调整 Redis 的计时器频率,以减少 Sentinels 之间的同步
}

这四个的代码是核心的运行代码,来控制sentinel进行运行监控节点的状态。是最核心的代码

注意:

TILT 模式(Tilt Mode)是 Sentinel 中的一种特殊操作模式。当 Sentinel 检测到一些异常情况,可能是由于进程阻塞、系统时钟显著更改等原因导致时间检测异常时,它会进入 TILT 模式。在 TILT 模式中,Sentinel 暂时停止执行实际的操作,比如故障检测、故障转移等,以避免在异常情况下做出错误的决策。这是一种保护机制,防止 Sentinel 在不确定的情况下做出不可逆转的决策。TILT 模式的目的是等待一段时间(SENTINEL_TILT_PERIOD 毫秒),以确保系统重新稳定,并且 Sentinel 可以信任其定时器的正常运行。一旦 TILT 模式的等待时间过去,Sentinel 将结束 TILT 模式,恢复正常的操作。总体而言,TILT 模式是 Sentinel 的一项安全措施,用于应对异常情况下的不确定性,以避免做出可能导致系统问题的决策。

拆分脑(Split Brain)是分布式系统中的一种问题,通常指的是在网络分区的情况下,导致系统的不一致性。在 Sentinel 中,如果多个 Sentinel 同时请求进行投票并试图选出一个领导者,可能会导致以下问题:
缺乏一致性: 当多个 Sentinel 在相互不可达的网络分区中运行时,每个分区内的 Sentinel 可能都认为自己是领导者,从而导致系统分裂成多个部分。
多个领导者: 每个网络分区内的 Sentinel 可能都发起选举,并试图选出一个领导者。由于网络分区的存在,这些领导者可能互相不知道对方的存在。
决策冲突: 不同的领导者可能会做出不同的决策,例如执行故障转移或执行其他管理操作。这可能导致不一致的系统状态。
系统不稳定: 在网络分区恢复连接时,可能会存在多个 Sentinel 都认为自己是领导者的情况,这会导致混乱和不稳定。
为了避免这些问题,通常需要在分布式系统中引入一些机制,例如选举算法或分布式一致性协议,以确保系统能够在网络分区发生时保持一致性,并且能够在网络分区解决后正确地进行协调。在 Sentinel 中,这可能涉及到选举算法来选择一个主 Sentinel,以确保只有一个 Sentinel 负责进行故障检测和决策。

故障转移是 Sentinel 集群中的一种机制,用于在主节点失效时选举新的主节点,确保系统的可用性。当主节点因为故障或其他原因不可用时,其他 Sentinel 实例会发起故障转移流程,选举新的主节点,并将其他从节点切换到新的主节点,以保持集群的正常运行。在分布式系统中,故障转移需要经过一系列步骤,例如选举、切换从节点等。由于 Sentinel 实例之间可能存在通信延迟和网络不稳定性,如果多个 Sentinel 实例在短时间内同时发起故障转移,可能导致竞争和冲突,降低系统的可控性和稳定性。为了避免这种情况,引入随机性和延迟是一种常见的做法。通过设置故障转移开始时间的随机延迟,可以确保不同 Sentinel 实例在不同的时间启动故障转移,减少竞争,提高系统的可用性和可控性。总的来说,故障转移是为了保证分布式系统在主节点失效时能够迅速、稳定地选举新的主节点,而引入随机性和延迟则是为了减少竞争和冲突,提高系统的稳定性。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值