Redis源码解析 哨兵机制

初始化

其实每一个哨兵节点就是一个运行在特殊模式下的redis服务器,所以一个哨兵的启动其实与一个正常服务器的启动差别不大,区别它们的标志就是去判断server.sentinel_mode
而server.sentinel_mode的判断则是由checkForSentinelMode决定的

int main(){
	server.sentinel_mode = checkForSentinelMode(argc,argv);

}
......................

int checkForSentinelMode(int argc, char **argv) {
    int j;
 
    if (strstr(argv[0],"redis-sentinel") != NULL) return 1;
    for (j = 1; j < argc; j++)
        if (!strcmp(argv[j],"--sentinel")) return 1;
    return 0;
}

我们可以看到其实非常简单,就是两个判断条件,这也是为什么我们执行哨兵的时候可以用两种方法启动

redis-sentinel sentinel_xxx.conf
redis-server sentinel_xxx.conf --sentinel
这个时候就可以开始我们的初始化了,与一般的redis服务器一样,都会执行initServerConfig这个初始化函数,这个我们就不说了.不同的是sentinel还会去执行initSentinelConfig和initSentinel这两个初始化函数来初始化.

初始化配置

// 这个函数会用 Sentinel 所属的属性覆盖服务器默认的属性
void initSentinelConfig(void) {
    server.port = REDIS_SENTINEL_PORT;
    server.protected_mode = 0; /* Sentinel must be exposed. */
}

/* Perform the Sentinel mode initialization. */
// 以 Sentinel 模式初始化服务器
void initSentinel(void) {
    /* Initialize various data structures. */
    
    /* 初始化 Sentinel 的状态 */
    // 初始化纪元 用于故障转移
    sentinel.current_epoch = 0;
    // 初始化保存主服务器信息的字典
    sentinel.masters = dictCreate(&instancesDictType);
    // 初始化 TILT 模式的相关选项
    sentinel.tilt = 0;
    sentinel.tilt_start_time = 0;
    sentinel.previous_time = mstime();
    // 初始化脚本相关选项
    sentinel.running_scripts = 0;
    sentinel.scripts_queue = listCreate();
    // 初始化IP和port
    sentinel.announce_ip = NULL;
    sentinel.announce_port = 0;
    sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;
    sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG;
    //初始化密码和用户
    sentinel.sentinel_auth_pass = NULL;
    sentinel.sentinel_auth_user = NULL;
    sentinel.resolve_hostnames = SENTINEL_DEFAULT_RESOLVE_HOSTNAMES;
    sentinel.announce_hostnames = SENTINEL_DEFAULT_ANNOUNCE_HOSTNAMES;
    memset(sentinel.myid,0,sizeof(sentinel.myid));
    server.sentinel_config = NULL;
}




    // 初始化 TILT 模式的相关选项
    sentinel.tilt = 0;
    sentinel.tilt_start_time = 0;
    sentinel.previous_time = mstime();

    // 初始化脚本相关选项
    sentinel.running_scripts = 0;
    sentinel.scripts_queue = listCreate();
}

sentinelState

TITL:因为sentinel依赖本机时间驱动,如果系统时间出问题,或者因为进程阻塞导致的时间函数延迟调用。这时再去参与集群逻辑会出现不正确的决策。因此如果当前时间和上一次执行时间差为负值或者超过2s,该节点会进入TILT模式。

/* Main state. */
struct sentinelState {
    char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */
// 当前纪元 用做故障转移
uint64_t current_epoch;         /* Current epoch. */
// 保存了所有被这个 sentinel 监视的主服务器
// 字典的键是主服务器的名字
// 字典的值则是一个指向 sentinelRedisInstance 结构的指针,可以是主服务器,从服务器或者其他sentinel节点
    dict *masters;      /* Dictionary of master sentinelRedisInstances. Key is the instance name, value is the sentinelRedisInstance structure pointer. */
// 是否进入了 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? */
    char *sentinel_auth_pass;  /* Password to use for AUTH against other sentinel */
    char *sentinel_auth_user;  /* Username for ACLs AUTH against other sentinel. */
    int resolve_hostnames;     /* Support use of hostnames, assuming DNS is well configured. */
    int announce_hostnames; // Announce hostnames instead of IPs when we have them. 
} sentinel;

其中 masters 字典指针中的每个值都对应着一个Sentinel 检测的主实例。

sentinelRedisInstance

代表一个被Sentinel监视的Redis服务器实例,这个实例可以是主服务器、从服务器,或者另外一个Sentinel

typedef struct sentinelRedisInstance {
// 标识值,记录了实例的类型,以及该实例的当前状态 这很重要 
    // sentinel中状态转移是重点中的重点
int flags;      /* See SRI_... defines */
// 实例的名字
// 主服务器的名字由用户在配置文件中设置
// 从服务器以及 Sentinel 的名字由 Sentinel 自动设置
// 格式为 ip:port ,例如 "127.0.0.1:26379"
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. */

sentinelAddr *addr; /* Master host. */
/* 连接状态的结构体 */
instanceLink *link; /* Link to the instance, may be shared for Sentinels. */
 // 最后一次向频道发送问候信息的时间
 // 只在当前实例为 sentinel 时使用
    mstime_t last_pub_time;   /* Last time we sent hello via Pub/Sub. */
    // 最后一次接收到这个 sentinel 发来的问候信息的时间
    // 只在当前实例为 sentinel 时使用
mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time we received a hello from this Sentinel via Pub/Sub. */
// 最后一次回复 SENTINEL is-master-down-by-addr 命令的时间
// 只在当前实例为 sentinel 时使用
mstime_t last_master_down_reply_time; /* Time of last reply toSENTINEL is-master-down command.*/
// 实例被判断为 SDOWN 状态的时间
mstime_t s_down_since_time; /* Subjectively down since time. */
// 实例被判断为 ODOWN 状态的时间
mstime_t o_down_since_time; /* Objectively down since time. */
// SENTINEL down-after-milliseconds 选项所设定的值
// 实例无响应多少毫秒之后才会被判断为主观下线(subjectively down)
    mstime_t down_after_period; /* Consider it down after that period. */
    mstime_t master_reboot_down_after_period; /* Consider master down after that period. */
mstime_t master_reboot_since_time; /* master reboot time since time. */
// 从实例获取 INFO 命令的回复的时间
    mstime_t info_refresh;  /* Time at which we received INFO output from it. */
    dict *renamed_commands;     /* Commands renamed in this instance: Sentinel will use the alternative commands mapped on this table to send things like SLAVEOF, CONFIG, 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. */
    // 实例的角色
    int role_reported;
mstime_t role_reported_time;
// 最后一次从服务器的主服务器地址变更的时间
    mstime_t slave_conf_change_time; /* Last time slave master addr changed. */
/* Master specific. */
//监控该master实例的其他sentinel结点字典
dict *sentinels;    /* Other sentinels monitoring the same master. */
    // 如果这个实例代表的是一个主服务器
    // 那么这个字典保存着主服务器属下的从服务器
// 字典的键是从服务器的名字,字典的值是从服务器对应的 sentinelRedisInstance 结构    
dict *slaves;       /* Slaves for this master instance. */	

// SENTINEL monitor     选项中的 quorum 参数 
//判断这个实例为客观下线(objectively down)所需的支持投票数量
unsigned int quorum;/* Number of sentinels that need to agree on failure. */
// SENTINEL parallel-syncs   选项的值
// 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
int parallel_syncs; /* How many slaves to reconfigure at same time. */
// 连接主服务器和从服务器所需的密码
    char *auth_pass;    /* Password to use for AUTH against master & replica. */
    char *auth_user;    /* Username for ACLs AUTH against master & replica. */
/* Slave specific. */
// 主从服务器连接断开的时间
mstime_t master_link_down_time; /* Slave replication link down time. */
// 从服务器优先级
    int slave_priority; /* Slave priority according to its INFO output. */
int replica_announced; /* Replica announcing according to its INFO output. */
// 执行故障转移操作时,从服务器发送 SLAVEOF  命令的时间
mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF  */
// 主服务器的实例(在本实例为从服务器时使用)
struct sentinelRedisInstance *master; /* Master instance if it's slave. */
// INFO 命令的回复中记录的主服务器 IP
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 */
// 从服务器的复制偏移量
    unsigned long long slave_repl_offset; /* Slave replication offset. */
/* Failover 故障转移相关属性*/
// 如果这是一个主服务器实例,那么 leader 将是负责进行故障转移的 Sentinel 的运行 ID 。
// 如果这是一个 Sentinel 实例,那么 leader 就是被选举出来的领头 Sentinel 。
// 这个域只在 Sentinel 实例的 flags 属性的 SRI_MASTER_DOWN 标志处于打开状态时才有效。
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. */
// 领头的纪元
uint64_t leader_epoch; /* Epoch of the 'leader' field. */
// 当前执行中的故障转移的纪元
uint64_t failover_epoch; /* Epoch of the currently started failover. */
// 故障转移操作的当前状态
    int failover_state; /* See SENTINEL_FAILOVER_STATE_* defines. */
    // 状态改变的时间
mstime_t failover_state_change_time;
// 最后一次进行故障迁移的时间
mstime_t failover_start_time;   /* Last failover attempt start time. */
// SENTINEL failover-timeout   选项的值
// 刷新故障迁移状态的最大时限
    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. */
// 指向被提升为新主服务器的从服务器的指针
    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. */
    char *notification_script;
    char *client_reconfig_script;
    sds info; /* cached INFO output */
} sentinelRedisInstance;

sentinelAddr

addr属性:sentinelRedisInstance.addr属性是一个指sentinel.c/sentinelAddr结构的指针,这个结构保存着实例的IP地址和端口号:

typedef struct sentinelAddr 
{ 
char *ip; 
int port; 
} sentinelAddr;

sentinelTimer

这个函数是一个时间在serverCron中调用的函数,这是sentinel的一个最重要的函数,是除了上面说的功能以外其他所有函数的集合.我们在这一篇文章中先简单的介绍下它.

    run_with_period(100) { 
        if (server.sentinel_mode) sentinelTimer();
    }

我们可以看到在serverCron中其100ms调用一次.

void sentinelTimer(void) {

    // 记录本次 sentinel 调用的事件,
    // 并判断是否需要进入 TITL 模式 还会更新最后一次操作时间
    sentinelCheckTiltCondition();

    // 执行定期操作
    // 1. 先其他实例发送PING命令、分析主服务器和从服务器的INFO命令
    // 2. 向主服务器发送PUBLISH,检测其他sentinel
    // 3. 进行故障转移的全部操作
    // 4. 接收其他sentinel发来的PING和sentinel信息
    sentinelHandleDictOfRedisInstances(sentinel.masters);

    // 运行等待执行的脚本
    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). */
     // 用一个巧妙的操作降低选举sentinel时重新选举的概率
    server.hz = REDIS_DEFAULT_HZ + rand() % REDIS_DEFAULT_HZ;
}

你可能感兴趣的:(redis,数据库,java)