由一个或多个Sentinel实例组成的Sentinel系统,监测任意多主从服务器,一旦某个主服务器下线并超过等待时间,会将其下的某个从服务器上升为主服务器,对其余从服务器发送复制命令,将它们归属到新的主服务器下的从服务器,并且会继续监视下线的服务器,等其重新上线后,将其设置为新主服务器下的从服务器。
16.1 启动并初始化Sentinel
当一个Sentinel启动时,它需要执行以下步骤:
- 初始化服务器
- 将普通的Redis服务器使用的代码替换成Sentinel专用代码
- 初始化Sentinel状态
- 根据给定的配置文件,初始化Sentinel的监视主服务器列表
- 创建连向主服务器的网络连接
16.1.1 初始化服务器
类似初始化一个普通的Redis服务器,但不需要还原数据库状态
16.1.2 使用Sentinel专用代码
将部分普通Redis服务器使用的代码替换成Sentinel专用代码。譬如设置指定端口,使用专有命令表,普通Redis服务器使用redifs.c/redisCommandTabel作为命令表,Sentinel使用sentinel.c/sentinelcmds作为服务器的命令表
16.1.3 初始化Sentinel状态
初始化一个sentinel.c/sentinelState结构,保存服务器中所有和Sentinel功能有关的状态,一般状态由redis.h/redisServer保存
struct sentinelState{ //当前纪元,用于实现故障转移 uint64_t current_epoch; //保存了所有被这个sentinel监视的主服务器 //字典的键是主服务器的名字 //字典的值指向以一个sentinelRedisInstance结构的指针 dict *masters; //是否进入了TILT模式 int tilt; //目前正在执行的脚本的数量 int running_scripts; //进入TILT模式的时间 mstime_t tile_start_time; //最后一次执行时间处理器的时间 mstime_t previous_time; //一个FIFO队列,包含了所有需要执行的用户脚本 list *scripts_queue; }sentinel;
16.1.4 初始化Sentinel状态的master属性
每个sentinelRedisInstance结构代表一个被监视的Redis服务器实例,可以是主/从服务器,也可以是另一个Sentinel服务器
typedef struct sentinelRedisInstance{ //标识值,记录了实例的类型,以及该实例的当前状态。主服务器SRI_MASTER,从服务器SRI_SLAVE int flag; //实例的名字 //主服务器的名字由用户配置 //从服务器以及Sentinel的名字由Sentinel自动设置 //格式为ip:port char *name; //从服务器,键为实例名,值为sentinelRedisInstance实例 dict slaves; //监听当前服务器的哨兵 dict sentinels; //示例的运行ID char *runid; //配置纪元,用于实现故障转移 unit64_t config_epoch; //实例的地址,保存着实例的IP地址和端口号 sentinelAddr *addr; //实例无响应多少毫秒后被判定为主观下线 mstime_t down_after_period; //判定这个实例为客观下线所需要的支持股票数量 int quorum; //在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量 int parallel_syncs; //刷新故障迁移状态的最大时限 mstime_t failover_timeout; //.... }
16.1.5 创建连向主服务器的网络连接
向监视的主服务器发送连接请求(命令连接和订阅连接),成为主服务器的客户端,目的是可以向主服务器发送命令,从回复中获取相关信息。
- 命令连接,用于向主服务器发送命令,并接收命令回复
- 订阅连接,用于订阅主服务器的_sentinel_:hello频道,目的是发现监听目标服务器的其余Sentinel
16.2 获取主服务器信息
Sentinel每十秒一次向主服务器发送INFO命令,主服务器的回复中包含了自身信息和下属从服务器的信息,据此更新/新建对应的信息
16.3 获取从服务器信息
当Sentinel发现主服务器有新的从服务器出现时,会创建到从服务器的命令连接和订阅连接。创建命令连接后,每十秒一次向从服务器发送INFO命令,获得如下信息:
- 从服务器运行ID run_id
- 从服务器的角色role
- 主服务器的IP地址master_host,以及主服务器的端口号master_port
- 主从服务器的连接状态master_link_status
- 从服务器的优先级slave_priority(用来挑选新主服务器)
- 从服务器的复制偏移量slave_repl_offset(用来挑选新主服务器)
据此对从服务器的实例进行更新
16.4 向主服务器和从服务器发送信息
默认,Sentinel每两秒一次通过命令连接向所有被监视的主服务器和从服务器的_sentinel_:hello频道发送信息
其中"s_"开头代表Sentibel本身的信息,"m_"开头即目标服务器的信息
16.5 接收来自主服务器和从服务器的频道信息
每个Sentinel通过命令连接向_sentinel_:hello频道发送信息,又通过频道连接接收信息,多个Sentinel服务器监视同一个服务器,通过频道接收的信息会共享。
当信息中记录的Sentinel信息和本身相同时,当前Sentinel会丢弃;当收到其余Sentinel发送的信息时,会更新自身存储的服务器信息
16.5.1 更新sentinels字典
每个Sentinel的服务器实例下有一个sentinels字典,保存了监听当前服务器的Sentinel信息。当收到其他Sentinel发送的信息,会根据其中的信息更新,对应服务器的信息以及更新/新建对应服务器下sentinels字典中的Sentinels信息。
Sentinel通过接收频道信息获悉其余Sentinel的存在,并通过发送频道信息暴露自己。
16.5.2 创建连向其他Sentinel的命令连接
当发现新的Sentinel时,在16.5.1操作下,还会向新Sentinel建立命令连接,而新Sentinel也会跟当前Sentinel建立命令连接,以便后续相互发送命令,进行信息交换。
16.6 检测主观下线状态
默认,Sentinel每秒一次向所有创建了命令连接的服务器(主/从服务器及其他Sentinel)发送PING命令,以此判断对方是否在线。+PONG、-LOADING、-MASTERDOWN三种回复为有效回复,其余均为无效回复。
Sentinel配置文件的down-after-milliseconds选项指定了判定为主观下线需要的时间。当一个服务器在判定时间内,均返回无效回复,则判定为主观下线,Sentinel会更新服务器实例的flags属性,打开SRI_DOWN标识。多个Sentinel的配置可能不同,导致同一服务器在不同Sentinel的主观下线状态不一致
16.7 检查客观下线状态
当Sentinel判定一个主服务器为主观下线状态后,会向其余监听的Sentinel确定该主服务器的下线状态(包含主观下线和客观下线),当收集足够的下线判断后,会将主服务器追加判定为客观下线,并对主服务器执行故障转移操作
16.7.1 发送SENTINEL is-master-down-by-addr命令
Sentinel使用:
SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>询问其他Sentinel是否同意主服务器下线
16.7.2 接收SENTINEL is-master-down-by-addr命令
其他Sentinel收到确认下线命令时,根据命令附带的参数,检查服务器是否下线,回复检查结果
16.7.3 接收SENTINEL is-master-down-by-addr命令的回复
回复包含了三个参数:
- down_state:检查结果,1代表已经下线、0代表未下线
- leader_runid:可以是*,否则是这个Sentinel选举的局部领头Sentinel的运行ID
- leader_epoch:配置纪元,一个计数器,代表当前选举的轮数,当leader_runid的值为*,则该值总为0
根据其他Sentinel的返回,统计同意服务器的下线数量,当达到配置值时,更新服务器实例的flags属性,表示服务器已经进入客观下线的状态。如果下线的是主服务器,则继续进行故障转移操作
16.8 选举领头Sentinel
当主服务器下线时,监视的Sentinels会协商选举领头的Sentinel,由领头Sentinel对下线主服务器执行故障转移,以防并发。选举的规则和方法:
- 所有在线的Sentinel都有资格
- 每次进行领头选举后,不管成功与否,都会将每个参与选举的Sentinel的配置纪元的值自增一次
- 在一个配置纪元中,所有Sentinel都有一次将某个Sentinel设置局部领头的机会,一次配置纪元中只能设置一次,做出选择后不能修改
- 每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel(通过在Sentinel is-master-down-by-addr中设置current_epoch和run_id)
- 收到命令的Sentinel在回复中告诉请求方自己选举的Sentinel的run_id和选举轮数
- 只要有一个Sentinel获取的票数大于总Sentinel的一半,则当选
- 如果在规定时间内,没有结果,则在一段时间后,开启下一轮选举
16.9 故障转移
由领头Sentinel选出一个从服务器,将其转化成主服务器,并将其余从服务器改为复制新主服务器,当下线服务器上线时,将其设置为新主服务器的从服务器。
16.9.1 选出新的主服务器
将下线主服务器的所有从服务器保存到一个列表中,按照规则筛选:
- 删除下线或断开状态的从服务器,保证剩余的服务器都在线
- 删除最近5秒内没有回复领头Sentinel的INFO命令的从服务器,保证剩下的都能跟领头Sentinel正常通信
- 删除与下线服务器断开连接太长的从服务器,保证剩下的服务器中保存的数据都是比较新的
- 根据服务器的优先级对列表中剩余的服务器进行排序,选出优先级最大的服务器
- 如果存在多个同最大优先级的服务器,按照从服务器的复制偏移量排序,选出其中复制偏移量最大的服务器
- 如果存在多个同最大偏移量的服务器,按照从服务器的运行ID排序,选出其中最小的服务器
向挑选中的服务器发送SLAVEOF no one命令,将其转换成主服务器
16.9.2 修改从服务器的复制目标
先剩余从服务器发送SLAVEOF命令,让它们转向复制新的主服务器
16.9.3 将旧的主服务器变成从服务器
当旧的主服务器重新上线,Sentinel会向它发送SLAVEOF命令