Sentinel
Sentinel是Redis高可用性的解决方案,由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个主服务器,和主服务器下属的所有从服务器,当监视的主服务器处于下线状态时,会从从服务器中选举出新的主服务器,代替主服务器处理命令请求;
故障转移操作分为三步:
1). Sentinel系统会从从服务器中选举出一个从服务器,让其成为新的主服务器;
2). Sentinel系统会向其他从服务器发送复制指令,让他们成为新的主服务器的从服务器,至此故障转移操作执行完毕;
3). Sentinel会继续监视已经下线的主服务器,当其重新上线时,将它设置为新的主服务器的从服务器;
1. 启动并初始化Sentinel
启动Sentinel服务器的命令:
redis-sentinel /path/to/your/sentinel.conf
redis-server /path/to/your/sentinel.conf --sentinel
启动一个sentinel,需要执行以下步骤:
1). 初始化服务器;
2). 将redis服务器使用的代码替换为sentinel专用代码;
3). 初始化sentinel状态;
4). 根据sentinel配置文件,初始化sentinel哨兵监视的主服务器列表;
5). 创建连向主服务器的网络连接;
1.1 初始化服务器
Sentinel本质上是一个运行在特殊模式下的Redis服务器,所以启动Sentinel的第一步,是初始化一个普通的Redis服务器;但是由于Sentinel并不使用数据库,所以初始化Sentinel时不会载入RDB文件或者AOF文件;
1.2 替换Redis中的代码
将一部分普通Redis服务器使用的代码替换为Sentinel的专用代码;
PING、SENTINEL、INFO、SUBSCRIBE、UNSUBSCRIBE、PSUBSCRIBE、PUNSUBSCRIBE七个命令是客户端可以执行的全部命令;
1.3 初始化Sentinel状态
在完成代码替换之后,服务器会初始化一个sentinelState结构,这个结构保存了服务器中所有和sentinel功能有关的状态;
1 struct sentinelState{ 2 uint64_t current_epoch; # 当前纪元,用于实现故障转移; 3 dict *masters; 4 # 保存sentinel监视的所有主服务器; 5 # 字典的键是主服务器的名字; 6 # 字典的值是一个指向sentinelRedisInstance结构的指针; 7 int tilt; # 是否进入tilt模式; 8 int running_scripts; # 目前正在执行的脚本数量; 9 mstime_t tilt_start_time; # 进入tilt模式的时间; 10 mstime_t previous_time; # 最后一次执行时间处理器的时间; 11 list *scripts_queue; # FIFO队列,包含所有需要执行的用户脚本; 12 }sentinel;
1.4 根据配置文件,初始化sentinel监视的主服务器列表
sentinel状态中的masters属性记录sentinel监视的主服务器的相关信息,字典的键是主服务器的名字,字典的值是主服务器对应的sentinelRedisInstance结构;每个结构都对应一个被Sentinel监视的Redis服务器的实例,这个实例可以是主服务器、从服务器或sentinel;
1.5 创建连向主服务器的网络连接
Sentinel会创建两个连向主服务器的异步网络连接:
一个是命令连接,该连接专门向主服务器发送命令,并接收命令回复;
一个是订阅连接,专门用于订阅主服务器的_sentinel_:hello频道;
2. 获取主服务器信息
Sentinel默认会每隔10s,通过命令连接向被监视的主服务器发送INFO命令,并且分析命令回复来获取主服务器的当前信息;
通过分析主服务器返回的INFO命令,Sentinel可以获取以下两方面的信息:
1). 一方面是主服务器的信息,包括主服务器的运行ID,主服务器的角色;
2). 一方面是主服务器的所有下属从服务器,包括从服务器的IP地址、端口号;如此,Sentinel则无需用户提供从服务器信息;
Sentinel获得主服务器信息后,会对主服务器的sentinelRedisInstance结构进行更新;Sentinel获得从服务器信息后,会更新主服务器实例结构中的slaves字典,字典中记录主服务器的所有下属从服务器;
3. 获取从服务器信息
Sentinel从主服务器的命令回复中,获得从服务器的信息后,会创建连向从服务器的命令连接和订阅连接;命令连接创建完毕后,Sentinel默认会以每10s一次的频率向从服务器发送INFO命令,并且会根据得到的命令回复,更新从服务器的实例结构;
命令回复信息包括:从服务器的运行ID、从服务器的角色、主服务器的IP地址、主服务器的端口号、主从之间的连接状态、从服务器的优先级、从服务器的复制偏移量;
4. 向主服务器和从服务器发送信息
在默认情况下,Sentinel会以每2秒一次的频率,通过命令连接向所有被监视主服务器和从服务器发送以下格式的命令:
PUBLISH _sentinel_:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
这条命令会发送向服务器的_sentinel_:hello频道;
5. 接收来自主服务器和从服务器的频道信息
当Sentinel与主服务器或从服务器建立起订阅连接之后,Sentinel会通过订阅连接,向服务器发送以下命令:
SUBSCRIBE _sentinel_:hello
该命令表示Sentinel将会订阅该频道,直到Sentinel与服务器断开连接为止;
Sentinel会通过命令连接向_sentinel_:hello频道发送信息,也会通过订阅连接从频道接收信息;当多个sentinel监视一个主服务器时,一个Sentinel发送的信息会被其他Sentinel接收到,这些信息会被用于更新其他Sentinel对于发送信息Sentinel的认知,也会更新其他Sentinel对于被监视服务器的认知;
5.1 更新sentinels字典
主服务器的实例结构中会使用sentinels字典记录监视当前主服务器的所有Sentinel哨兵,字典的键为sentinel的名字(ip:port),字典的值为sentinel的实例结构;
当其他sentinel收到PUBLISH消息后,会从中提取出两方面的参数:一方面是主服务器的IP地址、端口号、配置纪元、名字;根据提取的主服务器的信息,sentinel会在自己的masters字典中找到主服务器的实例结构;另一方面是源sentinel的IP地址、端口号、运行ID、配置纪元,检查主服务器的sentinels字典,更新源sentinel的所有相关信息;
5.2 创建连向其他sentinel的命令连接
当sentinel通过频道发现一个新的sentinel时,会为新的sentinel创建相应的实例结构,同时还会创建连向新的sentinel的命令连接,反之,新的sentinel也会创建连向当前sentinel的命令连接;因此,最终监视同一个主服务器的所有sentinel会相互连接成一个网络;各个sentinel之间可以使用命令连接进行信息交换;
6. 检查主观下线状态
默认情况下,Sentinel会每隔一秒向所有与其创建命令连接的实例发送PING命令,并且通过实例返回的PING命令回复判断实例是否在线;
实例对PING命令的回复主要分为两种情况:
有效回复:+PONG、-LOADING、-MASTERDOWN三种回复其中一种;
无效回复:三种回复之外的其他回复;
Sentinel配置文件中的down-after-milliseconds选项指定了sentinel判断实例进入主观下线所需的时间,如果在时间内,没有接收到有效回复,则sentinel会修改对应的实例结构,打开flags属性的SRI_S_DOWN标识,表示判断该实例进入主观下线状态;
7. 检查客观下线状态
当Sentinel将一个主服务器判断为主观下线后,会向该主服务器的其他Sentinel发送请求,询问其他Sentinel的意见,当一定数量的Sentinel判断主服务器处于下线状态,则Sentinel会将主服务器标记为客观下线状态;
7.1 发送SENTINEL is-master-down-by-addr命令
SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>
ip : 主服务器的IP地址;
port : 主服务器的端口号;
current_epoch : Sentinel当前的配置纪元,用于选举领头的Sentinel;
runid : *表示仅仅检测主服务器的客观下线状态;sentinel的运行ID则用于选举领头的Sentinel;
7.2 接收SENTINEL is-master-down-by-addr命令
当其他Sentinel接收到该命令后,会提取出主服务器的IP地址和端口号,检测主服务器的下线状态,并且向源Sentinel发送包含三个参数的Multi Bulk作为命令回复;
<down_state> : Sentinel对于主服务器的检查结果,1代表主服务器已下线,0代表主服务器未下线;
<leader_runid> : 可以是*或者目标Sentinel的局部领头Sentinel的runid;如果是前者,则表示仅仅检测主服务器的下线状态;如果是后者,则用于选举领头的Sentinel;
<leader_epoch> : 目标Sentinel的局部领头Sentinel的配置纪元;如果leader_runid的值为*,则leader_epoch的值为0;
7.3 接收SENTINEL is-master-down-by-addr命令的回复
源sentinel接收到回复命令后,会统计已经判断服务器处于下线状态的其他Sentinel的数量,如果达到配置喷配置的数量,则Sentinel会将主服务器实例结构的flags属性的SRI_O_DOWN标识打开,表示主服务器已经进入客观下线状态;
8. 选举领头Sentinel
选举规则:
1). 在一个配置纪元内,每个Sentinel只有一次机会将某个Sentinel设置为领头Sentinel,并且一旦领头Sentinel设置,在配置纪元内则不能再更改;
2). 每个发现主服务器进入客观下线的Sentinel都会要求其他Sentinel将自己设置为局部领头Sentinel;
3). 当源Sentinel向目标Sentinel发送SENTINEL is-master-down-by-addr命令,并且命令中的runid参数不是*而是源Sentinel的runid时,表示源Sentinel要求目标Sentinel将自己设置为局部领头Sentinel;
4). 目标Sentinel会根据先到先得的原则,将第一个接收的请求的Sentinel设置为局部领头Sentinel,其余Sentinel发送的命令请求会被拒绝;
5). 目标Sentinel会向源Sentinel发送命令回复,回复中包含局部领头Sentinel的运行ID和配置纪元;
6). 源Sentinel接收到命令回复后,会判断配置纪元是否相同;如果相同,则检查runid与自己的runid是否相同,如果相同,则表示自己被设置为局部领头Sentinel;
7). 如果源Sentinel被半数以上的Sentinel设置为局部领头Sentinel,则该Sentinel会成为领头Sentinel;
8). 如果一个配置纪元内没有选出,则会进入下一个纪元,继续进行选举;
9. 故障转移
9.1 选出新的主服务器
根据以下规则,选出新的主服务器,向其发送SLAVEOF no one命令,转换其角色;
1). 删除列表中已经处于下线或断线状态的从服务器;
2). 删除列表中最近5秒内没有回复领头Sentinel的INFO命令的从服务器;
3). 删除所有与下线主服务器连接断开超过down-after-milliseconds * 10ms的从服务器;
4). 选举优先级最高的作为主服务器;
5). 选举复制偏移量最大的从服务器;
6). 选举runid最小的从服务器;
发送SLAVEOF no one命令后,领头Sentinel会以每秒一次的频率,向升级的从服务器发送INFO命令,观察从服务器角色的变化,如果从slave变为master,则领头Sentinel知道从服务器已经成功升级为主服务器;
9.2 修改从服务器的复制目标
通过向其他从服务器发送SLAVEOF命令,让其他从服务器去复制新的主服务器;
9.3 将旧的主服务器变为从服务器
当旧的主服务器重新上线时,Sentinel会向它发送SLAVEOF命令,让其成为新主服务器的从服务器;
浙公网安备 33010602011771号