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_ipSentinel的ip地址
S_portSentinel的端口号
S_runidSentinel的运行ID
S_epochSentinel 的当前配置纪元
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收集到足够多的主观下线投票之后,它会将主服务器判断为客观下线,并发起一次针对主服务器的故障转移操作。

相关推荐