redis哨兵

database

哨兵作用

哨兵(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收集到足够多的主观下线投票之后,它会将主服务器判断为客观下线,并发起一次针对主服务器的故障转移操作。

以上是 redis哨兵 的全部内容, 来源链接: utcz.com/z/534211.html

回到顶部