Sentinel实现Redis高可用性的一个解决方案
首先我们来理解什么是高可用性
高可用性通常来描述一个系统的平均无故障时间,即服务器正常对外提供服务的时间,通过减少服务器的停工时间
Sentinel(哨岗、哨兵)是Redis的高可用性的解决方案之一。
由一个Sentinel实例组成的Sentinel系统可以监视
整体来说是由一个或者多个Sentinel实例去组成的Sentinel系统,该系统可以监视多个任意多个主从服务器(一旦发现主服务器宕机了,就要在其属下的从服务器进行选举,充当主服务器),当被监视的主服务器进入下线状态,要从其从服务器选举出一台升级为新的主服务器,代替原来的主服务器继续处理命令请求。
图示是一个Sentinel系统监视服务器的栗子
-
Sentinel:就是哨兵,不止一个,因为如果哨兵挂了也是要有替补的
-
master:主服务器
-
Server:从服务器
-
哨兵要对所有主从服务器进行监视
假设此时如果master下线了,哨兵就会知道该情况发生,然后进行计时,当主服务器的下线时间超过用户设定的下线时长上限时,Sentinel系统就会对原来的主服务器做故障转移的操作
- Sentinel系统首先会挑选随便一个server升级成主服务器
- Sentinel系统将剩余的从服务器发送指令,然他们成为新的主服务器的从服务器,并进行复制工作(前面提到的从与主要保持一致),当所有的从服务器都开始复制新的主服务器时,故障转移操作就完毕了,等待复制完成就好
- Sentinel对断开的原主服务器也会保持一个监视状态,如果原主服务器成功连接上,但超时了,会成为新主服务器的从服务器(也就是降级了),然后对新主服务器进行复制
下面就学习一下,Sentinel系统对主服务器执行故障转移的整个过程
启动并且初始化Sentinel
其实Sentinel就是一个Redis服务器
下面的命令可以启动sentinel服务器(具体怎么搭建,回看前一篇的单机搭建Sentinel)
redis-sentinel /sentinel配置文件
redis-server /sentinel配置文件 --sentinel
当一个sentinel启动时,需要下面的几个步骤
- 初始化哨兵服务器
- 将普通Redis服务器使用的代码替换出Sentinel专用代码
- 初始化sentinel状态
- 根据配置文件,初始化Sentinel的监视主服务器列表
- 创建连向主服务器的网络连接
初始化Sentinel服务器
Sentinel本质上只是一个运行在特殊模式下的Redis服务器,所以它的初始化步骤和普通的Redis服务器差不多(回看前面的服务器),但要注意的是:Sentinel的初始化过程与普通的Redis服务器并不是完全一致,比如Sentinel不需要恢复数据,所以不需要去进行载入RDB文件或者AOF文件
下面看看Sentinel模式下的Redis可以用哪些命令
功能 | 使用情况 |
---|---|
数据库和键值对方面的命令 | 不使用 |
事务命令 | 不使用 |
脚本命令 | 不使用 |
持久化命令,RDB与AOF | 不使用 |
主从复制命令 | Sentinel服务器内部会使用,但连接上Sentinel的客户端不可以使用 |
发布与订阅命令 | 订阅的4个命令都可以被Sentinel服务器内部和其客户端使用,但发布的命令(PUBLISH)只可以在Sentinel内部使用 |
文件事件处理器(前面提到过,这个处理器负责接收命令请求和处理命令回复) | Sentinel内部使用,但关联的文件事件处理器与普通的Redis不同 |
时间事件处理器(执行ServerCron函数) | Sentinel的内部使用,时间事件的处理器仍然是去执行ServerCron函数,但该模式下会调用sentinelTime函数,这个函数包含了Sentinel要执行的定时操作 |
使用Sentinel的专用代码
现在已经初始化一台Sentinel服务器了,但里面的一些结构和代码是普通的Redis服务器的,此时要将替换成Sentinel专用的结构和代码
比如前面提到为什么Sentinel有一些命令不能用,就是因为其把redisCommandTable(服务器状态里面的命令表属性)替换成了sentinelcmds(Sentinel专用的命令)。
这也是为什么在Sentinel模式下,并不能执行一些键值对操作命令,因为压根没有载入这些命令,载入的命令只有下面的7个
- info(查看服务器状态)
- ping(访问)
- sentinel(哨兵的命令,其实这个对应的是哨兵配置文件里面的参数,客户端可以进行修改)
- subscribe(订阅)
- unsubscribe(退订)
- psubscribe(订阅指定格式的频道)
- punsubscribe(退订指定格式的频道)
初始化Sentinel状态
初始化Sentinel服务器后,有了自己的RedisServer状态,然后将一些原来的代码替换成Sentinel专用的代码后,接下来就是要初始化Sentinel状态(实际只是创建这个实例),这个状态其实是SentinelState结构
注意:服务器状态还是保存在RedisServer结构中,而Sentinel状态则保存在sentinelState结构中
结构里面的属性如下所示
struct sentinelState(
unit64_t current_epoch; //记录当前纪元,用于实现故障转移的
//保存了监视的所有主服务器的字典
//字典的键就是主服务器的名字
//而字典的值是一个指向sentinelRedisInstance(实例)结构的指针
dict *master;
//是否进入了TILT模式
int tilt;
//目前正在执行脚本的数量
int running_scripts;
//进入TILT模式的时间
mstime_t previous_time;
//最后一次执行时间处理器的时间
mstime_t previous_time;
//一个FIFO队列,包含了所有需要执行的用户脚本
list *scripts_queue;
)sentinel;
初始化Sentinel状态的masters属性
初始化Sentinel状态后,就开始给里面的属性赋值了,下面就是载入配置文件,然后去初始化master属性
Sentinel状态中的masters字典记录了所有被Sentinel监视的主服务器的相关信息
- 字典的键是被监视主服务器的名字(就是配置选项里面的mymaster)
- 而字典的值则是被监视的主服务器对应的sentinelRedisInstance结构
每一个SentinelRedisInstance结构都代表着一个被Sentinel监视的一个Redis服务器实例,不一定是主服务器,也可以是从服务器,甚至是另一个Sentinel,SentinelRedisInstance里面的属性很多,下面只介绍SentinelRedisInstance为主服务器时候的属性
typedef struct sentinelRedisInstance(
//标识值,记录了实例的类型,以及实例的当前状态
int flags;
//实例的名字(在配置文件那里载入)
//如果是从服务器或者另一个sentinel,名字是由sentinel自己设置的
//格式为:ip:port,
char *name;
//实例的运行ID
char *runid;
//配置纪元,也是用来实现故障转移的
unit64_t config_epoch;
//实例的地址
sentinelAddr *addr;
//实例无响应杜少毫秒之后被判断为主观下线(主观下线是指,哨兵自己认定监视的主服务器挂了)
//对应日志里面的就是+sdown(subjectively down)
//对应的是配置文件里面的sentinel down-after-millseconds参数
mstime_t down_after_period;
//判断这个实例为客观下线所需要的投票数量
//对应的就是配置文件里面的sentinel monitor <master-name> <ip> <port> <quorum>
//的quorum参数,也就是指至少多少个哨兵认为这台服务器是主观下线,就会变成客观下线
int paraller_syncs;
//刷新故障迁移状态的最大时限
//对应配置未见里面的sentinel failover-timeout <master-name> <ms>选项的值
//也就是后面ms的值
//改属性是指:主服务器被认为客观下线,会被踢掉,然从服务器替代成为新的主服务器
//然后其余的从服务器要对新的主服务器进行复制(主从复制)
//但这个复制时间是可以设置限制的,超过这个时间限制就判定无效,需要重来
mstime_t failover_timeout;
//....
)
可以看到下面的slave名字为:ip地址:端口号
addr属性
从上面,可以看到这个属性是指向一个sentinelAddr结构的指针,这个结构保存着实例的IP地址和端口号
typedef struct sentinelAddr(
char *ip;
int port;
)sentinelAddr;
初始化后的SentinelState结构大概如下所示
创建连接上主服务器的网络连接
经过上面的步骤,已经完善了SentinelState信息,下面可以进行连接了
此时的Sentinel会成为主服务器的客户端(如果主服务器有密码的话,配置文件那里也要去设置),可以向主服务器发送命令,并从命令回复中获取相关的信息
Sentinel会创建两个连向主服务器的异步网络连接,一个是命令连接,一个是订阅连接
- 命令连接:该连接专门用于向主服务器发送命令,然后接收主服务器的命令回复
- 订阅连接:这个连接专门用于订阅主服务器的**_ sentinel _ :hello**频道
为什么要用两个连接
Redis目前的发布与订阅功能中,被发送的信息是不会被保存到Redis数据库里面的(即接收信息的用户并不会保留频道发布的信息),如果在信息发送时,假如接收信息的客户端不在线或者断线,这个客户端就会丢失这条信息了,甚至断线了的话,前面的信息也会消失掉,所以Sentinel用了一个专门的连接去进行监听频道信息,这是为了不丢失任何sentinel_:hello频道里的任何信息(不太理解这句话)
另一方面,除了订阅频道外,Sentinel还必须向主服务器发送命令,以此来与主服务器进行通信,所以Sentinel还必须向主服务器创建命令连接
因为一个Sentinel需要与多个实例创建多个网络连接,所以Sentinel使用的是异步连接
异步连接是指一个服务器可以同时去响应多个请求,而同步连接是指一个服务器与第一个请求建立连接后,并且开始进行通信,此时第二个请求之后都会被阻塞。