zoukankan      html  css  js  c++  java
  • redis 哨兵

    哨兵作用

    哨兵(sentinel) 是一个分布式系统,是程序高可用性的一个保障。用于监视任意多个主服务器,以及这些主服务器属下的所有从服务器,当出现故障时通过投票机制选择新的master并将所有slave连接到新的master。

    监控

    不断地检查master和slave是否正常运行 master存活检测、master与slave运行情况检测。

    通知

    当被监控地服务器出现问题时,向其他(哨兵间,客户端)发送通知。

    自动故障转移

    断开master与slave连接,选取一个slave作为master,将其他slave连接到新的master,并告知客户端新的服务器地址。

    注意

    哨兵也是一台redis服务器,只是不提供数据服务,通常哨兵配置数量为单数

    启动哨兵

    配置文件

    哨兵默认的配置文件 sentinel.conf

    一般的以 sentinel_port.conf 命名 哨兵的配置文件

    配置信息

    port  26379  (端口号)
    dir  /tmp  (哨兵运行信息存储)
    monitor mymaster 127.0.0.1 6379 2
    # mymaster  (master 名字 随意)
    # 127.0.0.1 6379  (IP + 端口号)
    # 2  (哨兵个数 //2 + 1  当有 2 个哨兵认为 master 挂了 就挂了)
    down-after-milliseconds mymaster 30000 (单位 毫秒 )
    
    parallel-syncs mymaster 1 ( 新的master 一次有多少个 slave 同步,设置的越小,完成数据同步的时间越长,响应的服务器压力越小。)
    failover-timeout mymaster 180000( 3 分钟 如果没有同步完成 就判定为同步超时)
    

    启动

    配置主从结构,以 1master 2 slave为例。

    1 先启动 master 和 slave

    主从配置 参看 主从篇博客主从

    redis-server config_6379.conf
    redis-server config_6380.conf
    redis-server config_6381.conf
    

    2 启动哨兵

    redis-sentinel sentinel_26379.conf
    redis-sentinel sentinel_26380.conf
    redis-sentinel sentinel_26381.conf
    

    Sentinel 命令

    PING:PONG
    SENTINEL masters :列出所有被监视的主服务器,以及这些主服务器的当前状态。
    SENTINEL slaves :列出给定主服务器的所有从服务器,以及这些从服务器的当前状态。
    SENTINEL get-master-addr-by-name : 返回给定名字的主服务器的 IP 地址和端口号。 如果这个主服务器正在执行故障转移操作, 或者针对这个主服务器的故障转移操作已经完成, 那么这个命令返回新的主服务器的 IP 地址和端口号。
    SENTINEL reset : 重置所有名字和给定模式 pattern 相匹配的主服务器。 pattern 参数是一个 Glob 风格的模式。 重置操作清楚主服务器目前的所有状态, 包括正在执行中的故障转移, 并移除目前已经发现和关联的, 主服务器的所有从服务器和 Sentinel 。
    SENTINEL failover : 当主服务器失效时, 在不询问其他 Sentinel 意见的情况下, 强制开始一次自动故障迁移 (不过发起故障转移的 Sentinel 会向其他 Sentinel 发送一个新的配置,其他 Sentinel 会根据这个配置进行相应的更新)。

    初始化Sentinel

    初始化服务器

    从下面启动代码可以看出启动方式由函数 checkForSentinelMode 来决定,是否使用 sentinel 的模式进行一个启动, 添加的指令也是用的 sentinelcmds 的命令表

    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;
    }
    
    // 检查服务器是否以 Sentinel 模式启动
    server.sentinel_mode = checkForSentinelMode(argc,argv);
    
    // 初始化服务器
    initServerConfig();  // 在第二步介绍该函数
    
    // 如果服务器以 Sentinel 模式启动,那么进行 Sentinel 功能相关的初始化
    // 并为要监视的主服务器创建一些相应的数据结构
    if (server.sentinel_mode) {
        initSentinelConfig();
        initSentinel();
    }
    

    从源码我们可以看出哨兵的启动有两种方式

    redis-sentinel sentinel_xxx.conf
    redis-server sentinel_xxx.conf --sentinel
    

    无论哪种方式启动redis,都会执行 initServerConfig ,不同的是 Sentinel 还会 执行initSentinelConfiginitSentinel 两个初始化函数。接下来看看这两个函数都干了什么~ 。

    替换 Sentinel 的专用代码

    initSentinelConfig() 这个函数会用 Sentinel 配置的属性覆盖服务器默认的属性。

    void initSentinelConfig(void) {
        server.port = REDIS_SENTINEL_PORT;//26379
    }
    

    initSentinel() 会进行一个命令表的加载。一个主要的查询命令 INFO 也不同于普通服务器,而是使用一个特殊的版本。

    // 初始化服务器 Sentinel 服务器
    void initSentinel(void) {
        int j;
    
        // 删除 普通 Redis 服务器的命令表(该表用于普通模式)
        dictEmpty(server.commands,NULL);
    
        //  添加 sentinel 模式专用的命令。
        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);
            redisAssert(retval == DICT_OK);
        }
    
        /* 初始化 Sentinel 的状态 这是为了故障转移阶段选取 切换执行者 记录的状态 */
        sentinel.current_epoch = 0;
    
        // 保存 主服务器 信息的字典 (这里记录了监测的主服务器的信息)
        sentinel.masters = dictCreate(&instancesDictType,NULL);
    
        // 初始化 TILT 模式的相关选项
        sentinel.tilt = 0;
        sentinel.tilt_start_time = 0;
        sentinel.previous_time = mstime();
    
        // 初始化脚本相关选项
        sentinel.running_scripts = 0;
        sentinel.scripts_queue = listCreate();
    }
    
    // sentinel 的指令集合
    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},
        {"shutdown",shutdownCommand,-1,"",0,NULL,0,0,0,0,0}
    };
    

    初始化 Sentinel 状态

    在完成命令表加载之后,紧接着会进行 sentinelStatesentinelRedisInstance 结构的一个初始化。

    Sentinel 状态中的 masters 字典记录了所有被监视的主服务器信息,键为服务器名字,值为被监视主服务器对应的sentinel.c/sentinelRedisInstance结构。每个sentinelRedisInstance实例结构代表监视一个Redis服务器实例,这个实例可以是主服务器,也可以是从服务器,或者另外一个sentinel服务器。

    对于sentinelState的初始化将引发对masters字典的初始化,而masters字典的初始化是根据被该入的sentinel配置文件(sentinel_26379.conf)来进行的。主要为被监控 masterip port

    注意 这些都是有 sentinel 来维护和使用的。

    sentinelState

    struct sentinelState {
    
        // 当前纪元 用做故障转移
        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. */
    
    } sentinel;
    

    sentinelRedisInstance

    name
    实例的名字
    主服务器的名字由用户在配置文件中设置
    从服务器以及 Sentinel 的名字由 Sentinel 自动设置
    格式为 ip:port ,例如 "127.0.0.1:26379"

    runid
    实例的运行 ID

    sentinelAddr
    实例的地址

    主服务器实例特有的属性

    sentinels
    其他同样监控这个主服务器的所有 sentinel

    slaves
    如果这个实例代表的是一个主服务器
    那么这个字典保存着主服务器属下的从服务器
    字典的键是从服务器的名字,字典的值是从服务器对应的 sentinelRedisInstance 结构

    quorum
    判断这个实例为客观下线(objectively down)所需的支持投票数量

    parallel_syncs
    SENTINEL parallel-syncs 选项的值
    在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量

    auth_pass
    连接主服务器和从服务器所需的密码

    从服务器实例特有的属性

    master_link_down_time
    主从服务器连接断开的时间

    slave_priority
    从服务器优先级

    slave_reconf_sent_time
    执行故障转移操作时,从服务器发送 SLAVEOF 命令的时间

    master
    主服务器的实例(在本实例为从服务器时使用)

    slave_master_host
    INFO 命令的回复中记录的主服务器 IP

    slave_master_port
    INFO 命令的回复中记录的主服务器端口号

    slave_master_link_status
    INFO 命令的回复中记录的主从服务器连接状态

    slave_repl_offset
    从服务器的复制偏移量

    结构中的 sentinelAddr 保存着对象的 地址和端口。

    /* Address object, used to describe an ip:port pair. */
    /* 地址对象,用于保存 IP 地址和端口 */
    typedef struct sentinelAddr {
        char *ip;
        int port;
    } sentinelAddr;
    

    建立连接

    sentinel 会先去连接 sentinel masters 中的每一个 master,并在每一个 mastersentinel之间创建两个异步连接 一个 命令连接 一个 订阅链接。此时 sentinel将成为 master 的客户端它可以向主服务器发送命令,并从命令回复中获取相关信息。

    命令连接

    专门用于向主服务器发送命令,并接收命令回复。比如sentinel向主服务器发送INFO命令。

    订阅连接

    专门用于订阅主服务器的 _sentinel_:hello频道。 比如 sentinel向主,从,其它sentinel发送sentinel本身和主库信息。

    redis在发布与订阅功能中,被发送的信息都不会保存在redis服务器中,若消息到来时,需要接收的客户端不在线或者断线,那么这个客户端就会丢失这条信息。为了不丢失_sentinel_:hello频道的任何信息,sentinel必须专门的用一个订阅连接来接收该频道的信息。

    获取主服务器信息

    Sentinel 默认会以每10秒一次的频率向主服务器发送INFO命令,通过分析命令回复来获取主服务器的当前信息。Sentinel可以获取以下两方面的信息:

    1主服务器本身的信息,包括服务器run_id,role的服务器角色。

    2 主服务器对应的所有从服务器的信息(从服务器IP和端口)。

    获取从服务器信息

    Sentinel发现有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构(sentinelRedisInstance)之外,还会创建到从服务器的命令连接订阅连接

    Sentinel依然会像对待主服务器那样,每10s 发送一个INFO命令来获取从服务器的当前信息。

    run_id、role、ip、port 、master_link_status(主从服务器的连接状态)、slave_priority(从服务器的优先级)等信息。

    向主从服务器发送信息

    在默认情况下, Sentinel会以每2秒一次的频率,通过命令连接向,所有被监视的主服务器和从服务器发送以下格式的命令:

    PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
    

    这条命令向服务器的_sentinel_:hello频道发送了一条信息,信息的内容由多个参数组成:

    (1) s_开头的参数记录的是sentinel本身的信息。

    (2) m_开头的参数记录的则是主服务器的信息,如果sentinel正在监视的是主服务器,那么这些参数就是主服务器的信息,如果sentinel正在监视的是从服务器,那么这些参数记录就是从服务器正在复制的主服务器的信息。

    参数 描述
    S_ip Sentinel的ip地址
    S_port Sentinel的端口号
    S_runid Sentinel的运行ID
    S_epoch Sentinel 的当前配置纪元
    m_name 主服务器的名字
    M_ip 主服务器的IP地址
    M_port 主服务器的端口号
    M_epoch 主服务器的当前配置纪元

    例如

    "127.0.0.1,26379,e955b4c77398ef6b5f055bc7ebfd5e828dbed4fc,0,mymaster,127.0.0.1,6379,0"
    # --------------------------------解释------------------------------------------
    127.0.0.1  # sentinel ip 地址
    26379  # sentinel 端口号
    e955b4c77398ef6b5f055bc7ebfd5e828dbed4fc  # sentinel的运行 id
    0 # sentinel 当前配置纪元
    mymaster # sentinel 监控的 master name
    127.0.0.1 # master ip 地址
    6379 # master 端口号
    0 # master 当前配置纪元
    

    接收来自主从服务器的频道信息

    Sentinel与一个主服务器或者从服务器建立起订阅连接之后,Sentinel就会通过订阅连接向服务器发送 subscribe_sentinel_:hello

    对于每个与 Sentinel 连接的服务器,Sentinel既通过命令连向服务器的_sentinel_:hello频道发送信息,又通过订阅连接从服务器的_sentinel_:hello频道接收信息。

    因此当有新的Sentinel 连接进来时, 向订阅连接中发送的 subscribe_sentinel_:hello 被已有的Sentinel 接收(同时自己也会接受到来自自己的这条消息)。

    // 发送 PUBLISH 命令的间隔
    #define SENTINEL_PUBLISH_PERIOD 2000
    
    if ((now - ri->last_pub_time) > SENTINEL_PUBLISH_PERIOD) {
            /* PUBLISH hello messages to all the three kinds of instances. */
            sentinelSendHello(ri);
        }
    
    /* 接收来自主服务器和从服务器的频道信息
    当 sentinel 与一个主服务器或者从服务器建立起订阅连接之后, sentinel 就会通过订阅连接,向服务器发送以下命令:
    */
    SUBSCRIBE __sentinel__:hello
    
    /* Now we subscribe to the Sentinels "Hello" channel. */
    // 发送 SUBSCRIBE __sentinel__:hello 命令,订阅频道
    retval = redisAsyncCommand(ri->pc,
            sentinelReceiveHelloMessages, NULL, "SUBSCRIBE %s",
            SENTINEL_HELLO_CHANNEL);
    

    当一个Sentinel_sentinel_:hello频道收到一条信息时,Sentinel会对这条信息进行分析,提取出信息中 ip 、port、run_id 等8个参数,并进行以下检查:如果这条消息是自己发的,就直接忽略。如果是新进来的Sentinel , 此时Sentinel 会对 对应的主服务器实例结构进行更新,即将新加进来的 Sentinel 添加到 sentinels 字典中。

    每个Sentinel都有自己的一个sentinels字典,Sentinels字典信息保存了除自己之外的所有Sentinel信息。

    下线状态

    对于Redis的Sentinel中关于下线有两个不同的概念:(1)主观下线(Subjectively Down, 简称 Sdown) 指的是单个 Sentinel 实例对服务器做出的下线判断,此时不会进行故障转移。(2) 客观下线(Objectively Down, 简称 Odown)指的是多个 Sentinel 实例在对同一个服务器做出 Sdown 判断,此时目标sentinel会对主服务器进行故障转移。本篇具体详细介绍。

    主观下线状态

    默认的Sentinel会以每秒一次的频率向所有与它创建命令连接的实例(包括主、从、其他sentinel在内)发送PING命令,并通过实例回复来判断实例是否在线。

    合法的回复

    +pong-loading -masterdown

    无效回复

    除此之外的所有回复或者无回复都被视作无效回复。无回复指在指定的时间内没有回复就认为是无回复。

    down-after-milliseconds  # 指定的时间 未收到回复 视为无效
    

    用户设置down-after-milliseconds选项的值,不仅会被sentinel用来判断主服务器的主观下线状态,还会被用于判断主服务器下的所有从服务器,以及同样监视主服务器的其他sentinel的主观下线状态。

    -- 例如用户向sentinel设置以了下配置:
    sentinel  monitor master 127.0.0.1 6379 2
    sentinel  down-after-milliseconds master 50000
    

    这里的master是主服务器的名称, 端口默认63792代表sentinel集群中有2sentinel认为master 状态下线时,才能真正认为该master已经不可用了(也就是客观下线)。

    这50000毫秒不仅会成为sentinel判断master进入主观下线的标准,还会判断所有从库、其它sentinel进入主观下线的标准。

    当多个sentinel设置的主观下线时长可能不同

    对于多个sentinel共同监视同一个主服务器时,这些sentinel在配置文件sentinle.conf中所设置的down-after-milliseconds值也可能不同,因此当一个sentinel将主服务器判断为主观下线时,其它sentinel可能仍然会认为主服务器处于在线状态。只有全部的sentine都判断进入了主观下线状态时,才会认为主master进入了主观下线状态。

    客观下线状态

    Sentinel将一个主服务器判断为主观下线之后,为了确认这个主服务器是否真的下线了,会向同样监视这一主服务器的其它Sentinel进行询问,当有半数以上(看具体配置, 一般的是半数以上 例如sentinel monitor mymaster 127.0.0.1 6379 2 中 就为当 2 个判定下线时,就认为时客观下线了)

    master, 被确定客观下线之后sentinel 们 会选出一个 决策者 去执行故障转移操作。客观下线条件只适用于主服务器

    is-master-down-by-addr命令用来判断是否客观下线

    sentinel is-master-down-by-addr  ip  port  current_epoch  run_id
    

    sentinel当前的配置纪元 current_epoch 用于选举 决策者 sentinel, run_id可以是*或者sentinel的 运行id。

    决策者选取

    假设现在有4个sentinel 这四个sentinel 既是投票者,也是候选者(这四个必须时健康的)。

    1 不能有下面三个标记中的一个:SRI_S_DOWN|SRI_O_DOWN|SRI_DISCONNECTED

    2 ping 心跳正常

    3 优先级不能为 0(slave->slave_priority)

    4 INFO 数据不能超时

    5 主从连接断线会时间不能超时

    投票的过程很简单,每个sentinel 都将自己的ipportcurrent_epochrun_idis-master-down 发送到 hello 频道。

    sentinel 第一个获取到谁的 is-master-down 信息, 就将自己的票投给对应的sentinel

    一次过后 current_epoch 最大的,且超过了半数以上。则被选为决策者 否则再来一轮,每增加一轮 current_epoch + 1, 直到选出为止。

    故障转移

    选取候选Slave

    1 在线的

    2 响应速度快的

    3 与原 master 断开连接最短的

    4 优先原则

    优先级>offset>runid

    最终选取出 新的 master 之后向新的 master 发送

    slaveof no one  # 断开主从
    

    然后声明新的master

    slaveof ip port  # 发送新的IP 和  新的port
    

    最后将原来的 master 作为从机。当重新上线时,sentinel 会发送 salveof 命令使其成为从机。

    总结

    • sentinel只是一个运行在特殊模式下的redis服务器,它使用了和普通模式不同的命令表,以及区别与普通模式下使用的命令不同。

    • sentinel向主服务器发送INFO命令来获得主服务器属下所有从服务器的地址信息,并为这些从服务器创建相应的实例结构,以及连向这些从服务器的命令连接和订阅连接。

    • 一般情况下,sentinel以每10秒一次的频率向被监视的主服务器和从服务器发送INFO命令,当主服务器处于下线状态,或者sentinel正在对主服务器进行故障转移操作时,sentinel向从服务器发送INFO命令的频率会改为1秒一次。

    • 对于监视同一个主服务器和从服务器的多个sentinel来说,它们会以每2秒一次的频率,通过向被监视的_sentinel_:hello频道发送消息来向其他sentinel宣告自己的存在。

    • 每个sentinel也会从_sentinel_:hello中频道中接收其他sentinel发来的信息,并根据这些信息为其他sentinel创建相应的实例结构,以及命令连接。

    • sentinel只会与主服务器和从服务器创建命令连接和订阅连接,sentinelsentinel之间则只创建命令连接。

    • sentinel以每秒一次的频率向实例(包括主,从,其它sentinel)发送PING命令,并根据实例的回复来判断实例是否在线,当一个实例在指定的时长中连续向sentinel发送无效回复时,sentinel会将这个实例判断为主观下线。

    • sentinel将一个主服务器判断为主观下线时,它会向同样的监视这个主服务器的其他sentinel进行询问,看它们是否同意这个主服务器已经进入主观下线状态。

    • sentinel收集到足够多的主观下线投票之后,它会将主服务器判断为客观下线,并发起一次针对主服务器的故障转移操作。

  • 相关阅读:
    git commit之后,想撤销commit
    centOS7下ifconfig提示command not found
    图数据库 — neo4j (二)
    XQuery的 value() 方法、 exist() 方法 和 nodes() 方法
    SQL存储过程-新增和修改,参数Xml数据类型
    项目管理软件之易度1.5,禅道2.0,redmine1.2(附redmine1.2的安装)
    测试性能工具
    存储过程
    人生三支点:健康,职业,自由
    生活网站推荐
  • 原文地址:https://www.cnblogs.com/monkey-code/p/13155563.html
Copyright © 2011-2022 走看看