Sentinel(哨兵、哨岗)是Redis的高可用(high availability)解决方案:由一个或多个Sentinel实例组成Sentinel系统可以监视任意多个主服务器以及它们属下的所有从服务器,并在监视主服务器进行下线时,将主服务器下属的从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。
Redis的Sentinel主从服务关系图
从Redis的Sentinel主从服务关系中可以看出:
- Sentinel与Sentinel是彼此之间相互连接,且通过命令连接的方式进行通讯
- 有两个主服务(master1和master2),每个主服务有3个从服务(master1的3个从服务分别是:slave10、slave11和slave12,master2的3个从服务分别是:slave20、slave21和slave22)
- master与slave之间是复制的关系
- Sentinel系统与master和slave之间可以通过命令连接和订阅连接进行监控master和slave的状态
启动并初始化Sentinel
Sentinel本质上只是一个运行在特殊模式下的Redis服务器,所以启动Sentinel的第一步就是初始化一个普通的Redis服务器,并将普通Redis服务器使用的代码替换成Sentinel专用代码,然后再进行初始化,具体如下:
- 初始化Sentinel状态
struct sentinelState {
// sentinel id
char myid[CONFIG_RUN_ID_SIZE+1];
// 当前纪元,用于实现故障转移
uint64_t current_epoch;
// 保存了所有被这个sentinel监视的主服务器
// 字典的键是主服务的名字
// 字典的只则是一个指向sentinelRedisInstance结构的指针
dict *masters;
// 是否进入了TILT模式
int tilt;
// 目前正在执行的脚本的数量
int running_scripts;
// 进入TILT模式的时间
mstime_t tilt_start_time;
// 最后一次执行时间处理器的时间
mstime_t previous_time;
// 一个FIFO队列,包含了所有需要执行的用户脚本
list *scripts_queue;
} sentinel;
主要初始化Sentinel相关的基础信息,其中masters
属性表示该Sentinel需要监听的所有master实例,这些数据都是通过sentinel.conf
配置文件获取的
- 初始化Sentinel状态的masters属性
typedef struct sentinelRedisInstance {
// 标识值,记录了实例的类型以及该实例的当前状态
int flags;
// 实例的名字
// 主服务器的名字由用户在配置文件中的设置
// 从服务器以及Sentinel的名字由Sentinel自动设置
// 格式为ip:port,例如"127.0.0.1:26397"
char *name;
// 实例运行的ID
char *runid;
// 配置纪元,用于实现故障转移
uint64_t config_epoch;
// 实例的地址
sentinelAddr *addr;
// 实例无响应多少毫米之后才会被判断为主观下线
mstime_t down_after_period;
// 监听同一个master节点的其它sentinel
dict *sentinels;
// master的所有slave节点
dict *slaves;
// 判断这个实例客观下线所需的支持投票数量
unsigned int quorum;
// ......
// master实例
struct sentinelRedisInstance *master;
// 如果是master实例,表示执行故障转移的sentinel的runid
// 如果是sentinel,表示被投票成leader的sentinel的runid
char *leader;
// 刷新故障迁移状态的最大时限
mstime_t failover_timeout;
// ......
} sentinelRedisInstance;
sentinelState.masters
中的实例为sentinelRedisInstance
的master对象。sentinelRedisInstance.slaves
表示该master对象对应的所有slave,sentinelRedisInstance.sentinels
表示该master对象所有被监听的sentinel
3) 创建连向主服务器的网络连接
初始化Sentinel时需要创建连向被监视器主服务器的网络连接,Sentinel将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关的信息。每个Sentinel会创建两个连向主服务器的异步网络连接:
- 命令连接,这个连接专门用于向主服务器发送命令,并接收命令回复
- 订阅连接,这个连接专门用于订阅主服务器的
__sentinel__:hello
频道
获取主服务器信息
Sentinel默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送INFO命令,并通过分析INFO命令的回复来获取主服务器的当前信息。Sentinel可以获取以下两个方面的信息:
- 主服务的本身的信息:服务器
runid
和服务器的角色 - 主服务器属下的所有从服务器信息:从服务器的
ip
、port
等信息。同时将从服务器的信息更新到sentinelRedisInstance.slaves
属性中
获取从服务器信息
当Sentinel发现主服务器有新的从服务器出现时,Sentinel除了会为这个新的从服务器创建相应的实例结构之外,Sentinel还会创建连接到从服务器的命令连接和订阅连接。
接收来自主服务器和从服务器的频道信息
Sentinel会对__sentinel__:hello
频道的订阅一直持续到Sentinel与服务器的连接断开为止。当一个Sentinel收到从__sentinel__:hello
频道的一条信息,会对信息进行分析并,然后如下操作:
- 更新主从服务器的实例结构
- 更新sentinels字典
- 创建连向其它Sentinel的命令连接
检测主观下线状态
默认情况下,Sentinel以每秒一次的频率向所有其它创建了命令连接的实例(包括主服务器、从服务器、其它Sentinel在内)发送PING命令,并通过PING命令回复来判断实例是否在线
如果一个实例在down-after-milliseconds
毫米内(Sentinel配置文件中配置项),连续向Sentinel返回无效回复,那么Sentinel会在这个实例中flags
属性中打开SRI_S_DOWN
标识,以此来表示这个实例已经进入主观下线状态
检测客观下线状态
当Sentinel将一个主服务器判定为主观下线之后,为了确保这个主服务器是否真的下线,它会询问其它同样监视这一主服务器的其它Sentinel,具体步骤如下:
- 源Sentinel发送SENTINEL
is-master-down-by-addr
命令 - 目标Sentinel接收SENTINEL
is-master-down-by-addr
命令,检测主服务器是否已下线 - 源Sentinel接收SENTINEL
is-master-down-by-addr
命令的回复,通过判断同意主服务器下线Sentinel的数量是否大于quorum
来判定是否主服务器被标记为客观下线
Sentinel选举
当一个主服务器被判定为客观下线时,监视这个主服务器的所有Sentinel会进行协商,选出一个leader执行故障转移,选举规则如下:
- 所有在线的Sentinel都有被选为Leader的资格
- 无论选举是否成功,所有Sentinel的
current_epoch
的值都会自增一次 - 所有Sentinel都有一次将某个Sentinel设置为leader的机会,设置以后不能修改,设置的规则是先到先得
- Sentinel通过发送
is-master-down-by-addr
命令中的runid
参数的值(*
除外)来表示leader的runid
- 如果有某个Sentinel被半数以上的Sentinel设置成了leader,那么这个Sentinel将成为leader
- 如果在规定的时间内没有一个Sentinel被选举为leader,那么所有Sentinel将在一段时间后重新开始选举直到选出leader为止
故障转移
选举产生最为leader的Sentinel将执行对主服务器的故障转移操作,主要包含三个步骤:
- 在已下线的主服务器的所有从服务器中挑选一个从服务器将其转换为新的主服务器
- 让已下线的主服务器的所有从服务器改为复制新的主服务器
- 将已下线主服务器设置为新的主服务器的从服务器,当这个久的主服务器重新上线时,它就会成为新的主服务器的从服务器