第十六章 Sentinel

由一个或多个Sentinel实例组成的Sentinel系统,监测任意多主从服务器,一旦某个主服务器下线并超过等待时间,会将其下的某个从服务器上升为主服务器,对其余从服务器发送复制命令,将它们归属到新的主服务器下的从服务器,并且会继续监视下线的服务器,等其重新上线后,将其设置为新主服务器下的从服务器。

16.1 启动并初始化Sentinel

  当一个Sentinel启动时,它需要执行以下步骤:

  1. 初始化服务器
  2. 将普通的Redis服务器使用的代码替换成Sentinel专用代码
  3. 初始化Sentinel状态
  4. 根据给定的配置文件,初始化Sentinel的监视主服务器列表
  5. 创建连向主服务器的网络连接

  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命令的回复

  回复包含了三个参数:

  1. down_state:检查结果,1代表已经下线、0代表未下线
  2. leader_runid:可以是*,否则是这个Sentinel选举的局部领头Sentinel的运行ID
  3. 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命令

 

posted @ 2021-03-02 17:39  walker993  阅读(38)  评论(0编辑  收藏  举报