【Redis源码】集群之哨兵sentinel初识(十一)

前言:

说到哨兵之前,可以思考一个问题。为什么有哨兵机制?这一点其实可以从主从复制的优缺点作为考虑。主从复制其实有一个致命的弱点就是它是非高可用的,比如说当主节点出现故障时,那么此时我的写入就会出现问题,切换需要重新配,就无法做到高可用。此时其实就可以用到哨兵进制,哨兵自动发现故障,并通过选举算法重新选举新主完成转移和通知实现高可用。

参考资料:

https://redis.io/topics/sentinel

一篇看着太长分为两篇:

《集群之哨兵sentinel初识》和《集群之哨兵sentinel故障转移》。

(一)理解哨兵模式

基础结构

【Redis源码】集群之哨兵sentinel初识(十一)_第1张图片

1.哨兵本身是监听者身份,没有存储功能。在整体体系当中,一个sentinel或者一个群sentinels与redis主从体系是有监听和被监听者关系。

2.哨兵有如下一些功能: 监听、通知、自动故障转移、配置提供程序。

     1)监听:哨兵会不断检查主服务和从服务是否正常运行。

     2)通知:哨兵可以通过API通知到系统管理员或者其他程序,其中一个受监听redis实例出问题。

     3)自动转移故障:如果主服务不正常,则哨兵可以启动故障转移,在该从服务升级为主服务。将其该主服务下其他从服务重新配置主服务,并通知使用Redis服务器的应用要使用新地址。

     4) 配置提供程序: Sentinel充当客户端服务发现的授权来源, 客户端连接到Sentinels,以询问负责给定服务的当前Redis主服务器的地址。如果发生故障转移,Sentinels将报告新地址。

3.哨兵存在三种交互模式: 哨兵与主服务、哨兵与从服务、哨兵与其他哨兵。

4.哨兵体现中有三个角色: master、slave、sentinel。

 

 

(二)配置哨兵模

1.1 sentinel.conf配置说明

# 哨兵sentinel监控的redis主节点的 ip port 
# sentinel monitor    
sentinel monitor mymaster 127.0.0.1 6379 2

# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码  
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码  
# sentinel auth-pass    
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd  

# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒  
# sentinel down-after-milliseconds    
sentinel down-after-milliseconds mymaster 30000 


# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,  
# 这个数字越小,完成failover所需的时间就越长,  
# 但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。  
# 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。  
# sentinel parallel-syncs    
sentinel parallel-syncs mymaster 1 


# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:   
# 1. 同一个sentinel对同一个master两次failover之间的间隔时间。  
# 2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。  
# 3.当想要取消一个正在进行的failover所需要的时间。    
# 4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,
# 但是就不按parallel-syncs所配置的规则来了  
# 默认三分钟  
# sentinel failover-timeout    
sentinel failover-timeout mymaster 180000  


# 哨兵sentinel实例运行的端口 默认26379  
port 26379  


# SCRIPTS EXECUTION  
  
# 配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。  
# 对于脚本的运行结果有以下规则:  
# 若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10  
# 若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。  
# 如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。  
# 一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。  
  
# 通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,  
# 这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数,  
# 一个是事件的类型,一个是事件的描述。  如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个
# 路径,并且是可执行的,否则sentinel无法正常启动成功。  
# 通知脚本  
# sentinel notification-script    
sentinel notification-script mymaster /var/redis/notify.sh  
  
# 客户端重新配置主节点参数脚本  
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。  
# 以下参数将会在调用脚本时传给脚本:  
#         
# 目前总是“failover”,  
# 是“leader”或者“observer”中的一个。   
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的  
# 这个脚本应该是通用的,能被多次调用,不是针对性的。  
# sentinel client-reconfig-script    
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh  
 

 

master-name 节点名称,名称只能是[A-z 0-9]以及.-_内的字符。

quorum 当这些quorum个数sentinel哨兵认为master主节点失联,那么这时客观上认为主节点失联了 。

 

 

1.2 启动哨兵模式

如果使用的是redis-sentinel可执行文件(或者您具有指向该redis-server可执行文件的名称的符号链接),则可以使用以下命令行运行Sentinel:

redis-sentinel /path/to/sentinel.conf

否则,您可以直接使用redis-server在Sentinel模式下启动的可执行文件:

redis-server /path/to/sentinel.conf --sentinel

server.c源码中可以体现两种加载模式:

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;
}

 

(三)哨兵源码解析

 

2.1 初始化哨兵

1) server.c 哨兵初始化

int main(int argc, char **argv) {
    //...省略
    server.sentinel_mode = checkForSentinelMode(argc,argv);  //判断是否为哨兵模式
    
    //...省略
    
    if (server.sentinel_mode) {
        initSentinelConfig(); //加载端口
        initSentinel();       //初始化哨兵模式,包括命令调用和各种数据结构。
    }
    //...省略
    if (!server.sentinel_mode) { //普通模式
       //...省略
    } else {
        /*
        检测哨兵模式是否正常配置
        */
        sentinelIsRunning();  
    }
}

initSentinelConfig函数中为加载哨兵端口,默认端口为26379。initSentinel函数中初始化哨兵模式,包括命令调用和各种数据结构。

 

initSentinel函数如下:

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}
};

void initSentinel(void) {
    unsigned int j;

    /* 初始化哨兵命令 */
    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);
    }

    /* 初始化各种数据结构. */
    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;
    memset(sentinel.myid,0,sizeof(sentinel.myid));
}

 

redisCommand结构体数组为哨兵模式支持命令集。哨兵有单独的命令集,只支持ping、sentinel、subscribe、unsubscribe、psubscribe、publish、info、role、client、shutdown命令。

 

2) server.c 哨兵任务

int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
     //...省略
     run_with_period(100) {
        if (server.sentinel_mode) sentinelTimer();  //哨兵相关模式下运行任务
    }
    //...省略
}

 

serverCron为initServer函数中的注册时间回调。serverCron中调用sentinelTimer方法中执行哨兵模式中的任务。

包括执行定期操作比如PING、分析主服务和从服务的INFO命令、故障转移等等。

 

2.2 定期操作 

周期执行命令函数sentinelSendPeriodicCommands

void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) {
    mstime_t now = mstime();
    mstime_t info_period, ping_period;
    int retval;
    //。。。省略

    /* 如果这是一个处于O_DOWN状态的主服务器的从服务器,我们开始发送
        它每秒钟都有信息,而不是通常的哨兵信息期周期 */
    if ((ri->flags & SRI_SLAVE) &&
        ((ri->master->flags & (SRI_O_DOWN|SRI_FAILOVER_IN_PROGRESS)) ||
         (ri->master_link_down_time != 0)))
    {
        info_period = 1000;   //1s
    } else {
        info_period = SENTINEL_INFO_PERIOD; //10s
    }

    /* ping通过 down-after-milliseconds 参数可以配置,默认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. */
    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++;
    }

    /* 发送ping */
    if ((now - ri->link->last_pong_time) > ping_period &&
               (now - ri->link->last_ping_time) > ping_period/2) {
        sentinelSendPing(ri);
    }

    /* PUBLISH 发送hello,SENTINEL_PUBLISH_PERIOD为2000则为2s */
    if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) { 
        sentinelSendHello(ri);
    }
}

正常情况下每10秒会获取一次info主和从到。每1秒发送ping命令,ping命令down-after-milliseconds可以配置,每2秒广播一次hello msg。

 

总结:

(1)哨兵没有存储功能。

(2)哨兵服务支持命令: ping、sentinel、subscribe、unsubscribe、psubscribe、publish、info、role、client、shutdown

(3)哨兵提供功能: 监听、通知、自动故障转移、配置提供程序。

(4)哨兵有两种启动模式:配置或者命令行参数。

(5)哨兵体现中有三个角色: master、slave、sentinel。

(6)周期函数主从非O_DOWN状态下每10s发送一次info命令到主从。

(7)ping命令默认是1s发送一次,可以通过down-after-milliseconds配置。

(8)每2秒广播一次hello msg。

 

 

 

 

 

你可能感兴趣的:(C++,Redis)