《Redis设计与实现》阅读笔记11-Sentinel(哨兵)

15 哨兵

哨兵系统由一个或多个哨兵实例组成,可以监视任意多个主服务器及其对应的所有从服务器,并在监视的主服务器下线的时候从其对应的从服务器中选出一个作为新的主服务器,然后让剩余的从服务器去复制新的主服务器,并在旧的主服务器上线以后让其成为新的主服务器的从服务器。

15.1 启动并初始化哨兵

启动一个 Sentinel 可以使用命令:

$ redis-sentinel /path/to/your/sentinel.conf

或者命令:

$ redis-server /path/to/your/sentinel.conf --sentinel

这两个命令的效果完全相同。

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

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

与普通的redis服务器的初始化不同,哨兵服务器有很多步骤不需要执行

功能使用情况
数据库和键值对方面的命令, 比如 SET 、 DEL 、 FLUSHDB 。不使用。
事务命令, 比如 MULTI 和 WATCH 。不使用。
脚本命令,比如 EVAL 。不使用。
RDB 持久化命令, 比如 SAVE 和 BGSAVE 。不使用。
AOF 持久化命令, 比如 BGREWRITEAOF 。不使用。
复制命令,比如 SLAVEOF 。Sentinel 内部可以使用,但客户端不可以使用。
发布与订阅命令, 比如 PUBLISH 和 SUBSCRIBE 。SUBSCRIBE 、 PSUBSCRIBE 、 UNSUBSCRIBE PUNSUBSCRIBE 四个命令在 Sentinel 内部和客户端都可以使用, 但 PUBLISH 命令只能在 Sentinel 内部使用。
文件事件处理器(负责发送命令请求、处理命令回复)。Sentinel 内部使用, 但关联的文件事件处理器和普通 Redis 服务器不同。
时间事件处理器(负责执行 serverCron 函数)。Sentinel 内部使用, 时间事件的处理器仍然是 serverCron 函数, serverCron 函数会调用 sentinel.c/sentinelTimer 函数, 后者包含了 Sentinel 要执行的所有操作。
15.1.2 使用哨兵专用代码

启动哨兵以后,会将普通redis服务器使用的代码替换成哨兵专用的代码,哨兵的服务器使用也使用不同的服务器,命令对应不同的实现函数。

这也解释了为什么在 Sentinel 模式下, Redis 服务器不能执行诸如 SET 、 DBSIZE 、 EVAL 等等这些命令 —— 因为服务器根本没有在命令表中载入这些命令: PING 、 SENTINEL 、 INFO 、 SUBSCRIBE 、 UNSUBSCRIBE 、 PSUBSCRIBE 和 PUNSUBSCRIBE 这七个命令就是客户端可以对 Sentinel 执行的全部命令了。

15.1.3 初始化哨兵状态

在应用了 Sentinel 的专用代码之后, 接下来, 服务器会初始化一个 sentinel.c/sentinelState 结构(后面简称“Sentinel 状态”), 这个结构保存了服务器中所有和 Sentinel 功能有关的状态 (服务器的一般状态仍然由 redis.h/redisServer 结构保存):

struct sentinelState {

    // 当前纪元,用于实现故障转移
    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;
15.1.4 初始化哨兵状态的master属性

哨兵状态的master属性是一个字典结构,用主服务器名字为键,以主服务器对应的 sentinel.c/sentinelRedisInstance 结构为值。以此保存哨兵监视的所有主服务器。

sentinelRedisInstance 结构可以是一个主服务器,也可以是一个从服务器,还可以是一个哨兵。

typedef struct sentinelRedisInstance {

    // 标识值,记录了实例的类型,以及该实例的当前状态
    int flags;

    // 实例的名字
    // 主服务器的名字由用户在配置文件中设置
    // 从服务器以及 Sentinel 的名字由 Sentinel 自动设置
    // 格式为 ip:port ,例如 "127.0.0.1:26379"
    char *name;

    // 实例的运行 ID
    char *runid;

    // 配置纪元,用于实现故障转移
    uint64_t config_epoch;

    // 实例的地址
    sentinelAddr *addr;

    // SENTINEL down-after-milliseconds 选项设定的值
    // 实例无响应多少毫秒之后才会被判断为主观下线(subjectively down)
    mstime_t down_after_period;

    // SENTINEL monitor <master-name> <IP> <port> <quorum> 选项中的 quorum 参数
    // 判断这个实例为客观下线(objectively down)所需的支持投票数量
    int quorum;

    // SENTINEL parallel-syncs <master-name> <number> 选项的值
    // 在执行故障转移操作时,可以同时对新的主服务器进行同步的从服务器数量
    int parallel_syncs;

    // SENTINEL failover-timeout <master-name> <ms> 选项的值
    // 刷新故障迁移状态的最大时限
    mstime_t failover_timeout;

    // ...

} sentinelRedisInstance;

sentinelRedisInstance.addr 属性是一个指向 sentinel.c/sentinelAddr 结构的指针, 这个结构保存着实例的 IP 地址和端口号:

typedef struct sentinelAddr {

    char *ip;

    int port;

} sentinelAddr;

在初始化哨兵状态的时候就会初始化他的master属性,而master属性的具体内容,由启动哨兵的配置文件来决定。

15.1.5 创建连向主服务器的网络连接

初始化 Sentinel 的最后一步是创建连向被监视主服务器的网络连接: Sentinel 将成为主服务器的客户端, 它可以向主服务器发送命令, 并从命令回复中获取相关的信息。

对于每个被 Sentinel 监视的主服务器来说, Sentinel 会创建两个连向主服务器的异步网络连接:

  • 一个是命令连接, 这个连接专门用于向主服务器发送命令, 并接收命令回复。
  • 另一个是订阅连接, 这个连接专门用于订阅主服务器的 sentinel:hello 频道。

为什么有两个连接?

在 Redis 目前的发布与订阅功能中, 被发送的信息都不会保存在 Redis 服务器里面, 如果在信息发送时, 想要接收信息的客户端不在线或者断线, 那么这个客户端就会丢失这条信息。
因此, 为了不丢失 sentinel:hello 频道的任何信息, Sentinel 必须专门用一个订阅连接来接收该频道的信息。
而另一方面, 除了订阅频道之外, Sentinel 还又必须向主服务器发送命令, 以此来与主服务器进行通讯, 所以 Sentinel 还必须向主服务器创建命令连接。
并且因为 Sentinel 需要与多个实例创建多个网络连接, 所以 Sentinel 使用的是异步连接。

15.2 获取主服务器信息

哨兵会以默认10秒一次的频率向主服务器发送INFO命令来获取主服务器的信息。哨兵一方面可以获得主服务器本身信息(包括服务器运行ID与在服务器间的角色),另一方面能从主服务器发回的信息中分析出所有从服务器的信息,每个从服务器都是以“slave”字符串开头的记录,里面包含了所有从服务器的ip地址与端口号。

哨兵会利用主服务器发回的自身信息更新本身master属性中的主服务器实例结构(主服务器重启会更新运行ID),而从服务器信息则用于更新master属性的主服务器实例结构中的salves属性(字典)中的从服务器实例结构。

salves属性的键是从服务器的ip地址加端口结合而成(ip:port),如果这个从服务器实例结构在salves属性不存在就新建一个,如果存在就更新这个实例结构。

哨兵->master属性(是一个字典,字典的值是主服务器实例结构)
主服务器实例结构->salves属性(是一个字典,字典的值是主服务器对应的从服务器实例结构)

主服务器实例结构中的flags属性值为SRI_MASTER,从服务器实例结构中的flags属性值为SRI_SLAVE

15.3 获取从服务器信息

哨兵不仅会与主服务器创建订阅连接和命令连接,同时也会与从服务器创建订阅连接和命令连接。

哨兵以每十秒一次的频率向从服务器发送INFO命令,获取从服务器信息(运行ID,角色信息,ip和端口,连接状态,优先级,复制偏移量),并使用这些信息对从服务器实例结构进行更新。

15.4 向主从服务器发送消息

哨兵会以默认2秒一次的频率向所有被监视的主从服务器发送信息,其中包括哨兵本身的信息和其监视主服务器的信息(监视多个主服务器时,发送对应那组主从服务器的主服务器信息)

15.5 接收主从服务器的频道信息

每个哨兵都与其对应的主从服务器建立命令连接和订阅连接两种连接。哨兵既会通过命令连接向服务器的频道发送信息,也会通过订阅连接从这个频道接收信息。

一个哨兵向一个服务器的频道发送的信息,同时会被监视这个服务器的其他哨兵接收到,监视同一个服务器的哨兵们,通过这个服务器为中转就能发现彼此的存在,发送的信息在被其他哨兵接收到以后会用于更新其它哨兵对这个哨兵的认知。

如果收到消息中哨兵ID与本身ID一样就表示这是自己发的信息,哨兵不做处理,不一样时就会用于更新自己对其他哨兵的认知。

15.5.1 更新sentinels字典

当哨兵收到订阅频道发来的消息时,就会分析消息,如果确认消息是其他哨兵发送的,就先解读消息中的主服务器信息,根据主服务信息,在自己的master字典中找到这个和其他哨兵一起监视的主服务器。

接着访问主服务器实例结构的sentinels属性(一个字典,保存着其他监视这个服务器的哨兵),再根据接收消息中的哨兵信息,在sentinels字典中查找这个哨兵是否存在。

若存在就根据收到的信息更新这个哨兵,若不存在就根据收到的信息新建一个哨兵实例结构放入字典中。

字典的键为哨兵的ip地址和端口组成(ip:port),字典的值是哨兵对应的实例结构。

15.5.2 创建连向其他哨兵的命令连接

监视同一个主服务器的哨兵之间彼此会建立命令连接(哨兵之间只有命令连接,没有订阅连接),使用命令连接可以使得哨兵之间进行信息交换。

15.6 检测主观下线状态

哨兵默认以每秒一次的频率向其连接的实例(主服务器,从服务器,哨兵)发送PING命令,并通过回复来判定其状态。

回复为+PONG,-LOADING,-MSTERDOWN为有效回复,其他回复或没有回复为无效回复。

哨兵配置文件中的down-after-milliseconds指定了一个毫秒值,如果一个实例在down-after-milliseconds毫秒内连续向哨兵返回无效回复,哨兵就会修改这个实例所对应的实例结构,将实例的flags属性置位SRI_S_DOWN,表示其进入主观下线状态。

不同哨兵配置文件中的down-after-milliseconds可能不同,所以对于实例是否进入主观下线状态,不同的哨兵可能有不同的判断。

15.7 检查客观下线状态

哨兵判定一个主服务器为主观下线状态后就会询问其他哨兵对这个主服务器状态的判断,当哨兵从其他哨兵哪里接收到足够数量的已下线判断后,哨兵就会把该主服务器判定为客观下线,并对其执行故障转移操作。

15.7.1 发送SENTINEL is-master-down-by-addr命令

哨兵使用SENTINEL is-master-down-by-addr <ip> <port> <current_epoch> <runid>

前两个参数是主服务器的地址和端口,后两个参数是哨兵的配置纪元和运行ID(选取领头哨兵时才使用)

15.7.2 接收SENTINEL is-master-down-by-addr命令

哨兵接收到SENTINEL is-master-down-by-addr命令后会根据传递的参数判断主服务器的状态,并给哨兵一个包含三个参数的回复

  • down_state:下线判断,1表示下线,0表示未下线
  • leader_runid和leader_epoch:哨兵的运行ID和配置纪元,用于选举领头哨兵
15.7.3 接收SENTINEL is-master-down-by-addr命令回复

接收到其他哨兵的回复后,发送命令的哨兵会根据回复中确认主服务器下线的数量决定主服务器状态,若达到指定数量,会把主服务器实例结构中flags属性的SRI_O_DOWN标识打开,表示主服务器客观下线。

确认数量由配置文件来决定,哨兵配置的quorm属性。

由于不同的哨兵的配置可能不同,所以当所有哨兵对主服务器状态做出判断以后,不同哨兵对主服务器的状态判断不一样。

15.8 选举领头哨兵

当一个主服务器被判定为客观下线以后,监视这个主服务器的所有哨兵就会进行协商,选出一个领头哨兵对下线的主服务器进行故障转移操作。

每个Sentinel都有成为领头的能力,而且每次选举无论是否成功,都会将配置纪元(confuguration epoch)的值自增,它实际上就是一个计数器。

局部领头:当一个Sentinel A向另一个Sentinel B发送请求SENTINEL is-master-down-by-addr + (Sentinel A 的 runid )代表A想成为B的局部领头。

所以这种规则就是先到先得,最早向目标Sentinel发送这个命令的必然成为它的Sentinel,后面的命令都会无效,当它的票数超过半数时,它就成为领头Sentinel,然后对已经下线的主服务器执行故障转移操作。没有Sentinel得票超过半数的话,配置纪元加1并重新投票。

15.9 故障转移

在选举出领头的Sentinel之后,领头Sentinel对已经下线的主服务器执行故障转移操作。步骤为:

在已下线的主服务器属下的所有从服务器里面,挑选一个从服务器,并将其转换为主服务器。(根据从服务器优先级,相同优先级选择复制偏移量较大的从服务器)
让已下线属下的所有从服务器改为复制新的主服务器,并成为新的主服务器的从服务器。
当旧的主服务器重新上线之后,它就会成为新的主服务器的从服务器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值