目录:
- Sentinel是什么?有什么用途?
- Sentinel环境搭建?
- Sentinel的工作流程?如何实现故障检测及故障转移?
1. Sentinel是什么?有什么用途?
Sentinel是Redis的高可用性(high availability)
解决方案:
将一个或多个Redis服务器配置成Sentinel哨兵节点,使其成为业务节点的客户端,持续监视主节点及其属下的从节点的状态。如果主机异常下线,则自动进行故障转移,使Redis持续对外提供不间断的读写服务。
2. Sentinel 环境搭建:
启动Sentinel服务器:
./redis-sentinel sentinel.conf
修改 sentinel.conf
配置文件: (关键配置项:sentinel monitor
)
# port 26379
port 26379
# sentinel monitor <master-name> <ip> <redis-port> <quorum>
# sentinel monitor <监控主机名> <监控主机IP> <监控主机端口号> <判定客观下线所需选票数>
sentinel monitor mymaster 127.0.0.1 6379 2
# sentinel authpass <master-name> password
# 设置连接主机的登录密码,例如:
sentinel auth-pass mymaster 10086
# sentinel down-after-milliseconds <mastername> <milliseconds>
# 时间内主机没有回复(例如没有回复PING),则判定主机已下线。例如:
sentinel down-after-milliseconds mymaster 30000
# sentinel failover-timeout <master-name> <milliseconds>
# 在failover故障转移开始后,如果规定时间内还没有完成此次故障转移,则Sentinel认为此次failover失败了,例如:
sentinel failover-timeout mymaster 180000
关于 Sentinel 部署的原则:
- 至少部署三个Sentinel实例,才能使系统足够的健壮(robust);
- 三个Sentinel实例应该被放在不同的计算机主机之上,保证被监控Redis主服务器宕机时不会使Sentinel服务器同时宕机(所有Sentinel实例与Redis主机部署在同一台电脑上就是一个错误的部署方式);
- 在
sentinel.conf
配置文件中只需指出要监控的主服务器即可,无需指出从服务器,Sentinel会自动通过INFO
命令发现从机并与它们建立网络连接;
sentinel monitor mymaster 127.0.0.1 8001 2
# 配置文件中指出主服务器即可,无需指出从服务器信息
- 一个经验:
sentinel.conf
配置文件中默认的主机名为mymaster
,不要轻易修改它,因为后面的多数配置项都使用的是mymaster
这个名字,如果改变则后面所有的配置项都需要修改,非常麻烦。
且sentinel monitor mymaster
这个配置必须放在配置文件的前部,如果放在后部,其他配置项会不认识mymaster
这个服务器而报错,例如:
# 因修改了 sentinel monitor 中指定的主机名字或者将其放在了文件靠后的位置,导致其他配置项找不到主机名而报错:
>>> 'sentinel config-epoch mymaster 0'
No such master with specified name.
Sentinel基础部署方式举例(basic setup):
3. Sentinel的工作流程:
Sentinel的工作流程如下:
- Sentinel服务器启动(初始化及创建连接);
- Sentinel与主服务器周期性通信获取主从机的信息并进行故障检测
(INFO/10s、PUBLISH/2s、PING/1s)
; - Sentinel发起并完成故障转移(选举领头Sentinel、选出新主机、主从切换)。
3.1 Sentinel服务器启动(初始化并创建连接):
3.1.1 使用Sentinel专用代码:
如果一个Redis服务器被配置成为了Sentinel模式,那么它所运行的代码与普通的Redis服务器是不相同的。
这是因为Sentinel服务器与普通Redis服务器在功能上有很大区别,例如Sentinel服务器无需支持SET、GET等客户端请求(不支持命令表),也不支持RDB和AOF持久化,所以Sentinel在启动阶段的第一步就是将一部分普通Redis服务器使用的代码替换成Sentinel专用代码。
3.1.2 Sentinel维护监听节点信息:(sentinelRedisInstance)
Sentinel服务器上需要维护其所监听节点的信息,根据 sentinel.conf
配置文件中的配置信息,创建所监听服务器的结构体。
一个Sentinel服务器上需要每个它所监听的节点创建一个 sentinelRedisInstance
结构(包括监听的主节点及其属下的从节点)。
sentinel.c/sentinelRedisInstance
结构体的内容如下:
typedef struct sentinelRedisInstance {
int flags; //标识实例类型及状态,如:SRI_MASTER; SRI_SLAVE; SRI_S_DOWN; SRI_O_DOWN;
char *name; //主服务器名字,在sentinel.conf中配置,格式为ip:port,如“127.0.0.1:9001
char *runid; //运行ID
uint64t config_epoch; //纪元
sentinelAddr *addr; //实例的网络地址,包含IP和端口号
mstime_t down_after_period; //判断主观下线所需超时时间,由down_after_milliseconds配置
int quorum; //判断客观下线所需票数,由<quorum>配置
mstimet failover_timeout; //故障迁移最大时限,由failover-timeout配置
dict *slaves; //slaves for this master instance. 这个主机属下的从机
//当此结构体表示的是一个从机时,以下三个成员用于表示通过执行 INF0 命令所获得的信息:
char *slave_master_host; /* Master host as reported by INFO */
int slave_master_port; /* Master port as reported by INFO */
int slave_master_link_status; /* Master link status as reported by INFO */
//通过PUBLISH消息,Sentinel可以知道正在监听这台主机的其他Sentinel服务器
dict *sentinels; /* Other sentinels monitoring the save master. */
} sentinelRedisInstance;
typedef struct sentinelAddr {
char *ip;
int port;
} sentinelAddr;
3.1.3 Sentinel创建连向监听节点的网络连接:
Sentinel将会成为监听节点的客户端,向监听节点发送命令并从回复中获取主机状态信息。
Sentinel与监听主机之间的连接有两种:一个是命令连接,一个是订阅连接。
3.2 Sentinel与主服务器周期性通信(INFO、PUBLISH、PING):
3.2.1 INFO命令:Sentinel获取主服务器及其属下从服务器的状态信息(10s)
Sentinel默认以每十秒一次的频率,向被监视主机发送 INFO
命令,以获取主机及其属下从机的状态信息。
在主机上执行 INFO
命令所获取的从机信息如下:
127.0.0.1:8001> INFO
#Replication
role:master
connected_slaves:2
slaveO:ip=127.0.0.1,port=8002,state=online,offset=889980,1a8=1 <=== 从机信息
slave1:ip=127.0.0.1,port=8003, state=online,offset-889994,1ag=1 <=== 从机信息
naster_ replid:0e32576c470081417e98b91eb5cbf93aa1e38b3b
naster_replid2:0000000000000000000000000000000000000000
master_repl_offset:889994
second_repl__offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:889994
在 sentinelRedisInstance
结构中保存的信息如下:
typedef struct sentinelRedisInstance [
dict slaves; //slaves for this master instance.这个主机属下的从机
//当此结构体表示的是一个从机时,以下三个成员用于表示通过执行 INF0 命令所获得的信息:
char *slave_master_host; //master host as reported by INFO
int slave_master_port; //master port as reported by INFO
int slave_master_link_status; //master link status as reported by INFO
} sentinelKedisInstance;
3.2.2 PUBLISH命令: Sentinel向主从服务器发送消息(2s)
PUBLISH __sentinel__:hello "<s_ip>,<s_port><s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
Sentinel默认以每两秒一次的频率,向所监听的主服务器及从服务器发送 PUBLISH
命令。
其中,“s_” 开头表示Sentinel本身信息; “m_” 开头表示主机信息。
Sentinel 通过订阅连接,从 __sentinel__:hello
接收信息,通过发布频道信息声明自己的存在,通过接收频道消息获知其他Sentinel的存在。以此达到监视同一个主服务器的Sentinel可以自动发现
的效果。(Sentinel之间只创建命令连接不创建订阅连接)
typedef struct sentinelRedisInstance {
dict *sentinels; //Other sentinels monitoring the save master.
} sentinelRedisInstance;
3.2.3 PING命令: Sentinel故障检测(1s)
Sentinel默认会以每一秒一次的频率向所有与它连接的实例发送 PING
命令,包括 主服务器、从服务器、其他Sentinel
,若在 down-after-milliseconds
时间内目标主机连续向Sentinel返回无效回复,或超时未回复,则Sentinel判定目标主机进入主观下线状态。
具体故障检测的过程见下文。
3.3 Sentinel的故障检测与故障转移:
3.3.1 故障检测:
故障检测是通过PING命令实现:
(1) 主观下线:(PING -> down-after-milliseconds)
Sentinel 默认以每一秒一次的频率向主服务器发送 PING 命令,如果目标主机在 down-after-milliseconds
毫秒内,连续向Sentinel返回无效回复,或超时未回复,则Sentinel更新目标主机所对应的实例状态为:sentinelRedisInstance->flags = SRI_S_DOWN
,表明目标主机已经进入 主观下线
状态。
typedef struct sentinelRedisInstance {
int flags; //SRI_MASTER/ SRI_SLAVE/ SRI_S_DOWN/ SRI_O_DOWN
mstime_t down_after_period; // = down-after-milliseconds
} sentinelRedisInstance;
注:
down-after-milliseconds
是sentinel.conf
文件中的配置项,表示判断目标主机进入主观下线的条件:
# sentinel down-after-milliseconds <master-name> <milliseconds>
# Number of milliseconds the master (or any attached replica or sentinel) should
# be unreachable in order to consider it in S_DOWN state.
sentinel down-after-milliseconds mymaster 30000
sentinel.conf
中配置的down-after-milliseconds
不仅会被用作Sentinel判断主服务器主观下线的条件,还会成为判断此主机属下的所有从服务的主观下线条件;- 多个Sentinel设置的
down-after-milliseconds
的值可能不同。
(2) 客观下线: (is-master-down-by-addr)
① 发送 is-master-down-by-addr 命令:
当某个Sentinel判断一个主服务器主观下线后,就会发送命令向其他Sentinel询问此主机是否真的下线。
例如:
# SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
SENTINEL is-master-dowm-by-addr 127.0.0.1 8001 0 *
其中,< ip > 和 < port > 用于指示主观下线的主机IP和端口号;
< current_epoch > 和 < runid > 用于后续的选举领头Sentinel,如果is-master-down-by-addr
命令只是用于询问,则二者的值为"0" 和 “*”。
② 接收并回复 is-master-down-by-addr 命令:
当Sentinel收到 is-master-down-by-addr
命令后,解析并回复(解析IP和端口号得到目标主机信息),如果Sentinel维护的sentinelRedisInstance
实例表示目标主机已下线,则回复:
# SENTINEL is-master-down-by-addr <down_state> <leader_runid> <leadere_epoch>
SENTINEL is-master-down-by-addr 1 * 0
回复内容的3个字段:
<down_state> : 1 表示 下线,0 表示 未下线
<leader_runid> : 用于选举领头Sentinel
<leader_epoch> : 用于选举领头Sentinel
③ 接收 is-master-down-by-addr 命令的回复:
如果判断目标主机已下线的Sentinel数量超过 < quorum > 阈值,则Sentinel 更新目标主机状态为客观下线:
sentinelRedisInstance->flags = SRI_O_DOWN;
3.3.2 故障转移:
当Sentinel检测到某主机已进入 客观下线
状态,则发起对目标主机的故障转移(哨兵模式下的故障转移是由Sentinel发起的,而集群模式下的故障转移则是由下线主机的从机发起的),步骤如下:
(1) 选举领头Sentinel:
在确认主服务器已进入下线状态之后
,Sentinel会再次
发送is-master-down-by-addr
命令,这次会携带自己的运行ID和纪元值,要求收到此命令的Sentinel投票给自己:
SENTINEL is-master-down-by-addr 1 e93f9e39fm9ef9emd93k93kdk9d 0
Sentinel收到投票请求后,如果在本次纪元内还有投票机会,则回复 is-master-down-by-addr
,将回复中 < runid > 置为收到的一个投票请求的Sentinel 的运行ID。
获得选票超半数的Sentinel会成为领头Sentinel。
(2) Sentinel从下线主机属下的从机中选出一个升级为新的主机:
过滤规则:
① 删除下线状态从机 > ② 删除5秒内未回复Sentinel的INFO消息的从机 >
③ 删除与断开时长超down-after-milliseconds*10的从机 >
④ 从机优先级排序(选最高) > ⑤ 复制偏移量(offset)(选最大)>⑥运行ID(选最小)
待选出新的主机后,领头Sentinel向被选出的从机发出SLAVEOF no one
命令,并以每秒一次的频率向其发送INFO
命令查看其是否已从slave切换为master(正常情况下INFO
命令的发送频率是每一秒一次)。
(3) Sentinel向下线主机属下的其他所有从机发送新的复制命令,让它们成为新主机的从机;
在新的主机完成从slave到master的切换后,领头Sentinel向其他slave发送SLAVEOF
命令,使它们成为新主机的从机。
(4) Sentinel继续监视下线主机,当其重新上线时,使其成为新主机的从机。
参考内容:
《Redis设计与实现》第三部分 第16章 Sentinel
Redis官方文档: https://redis.io/topics/sentinel