sentinel 系统介绍
程序员文章站
2022-05-28 12:09:41
...
Sentinel(哨兵)是 Redis 的高可用性(high availability)解决方案:由一个或多个 Sentinel 实例组成的 Sentinel 系统可以监视任意多个主服务器,以及它们属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,之后改由它来处理命令请求。
本节接下来将对 Sentinel 系统的初始化过程、实现原理,以及它如何对主服务器执行故障转移等操作进行详细的介绍。
启动并初始化 Sentinel
启动一个 Sentinel 可以使用命令:
$ redis-server /path/to/sentinel.conf --sentinel
或者:
$ redis-sentinel /path/to/sentinel.conf
Sentinel 本质上只是一个运行在特殊模式下的 Redis 服务器,所以启动的第一步就是初始化一个普通的 Redis 服务器。不过由于执行的工作不同,所以初始化过程也有所差别。比如,普通服务器在初始化时会通过载入 RDB 文件或者 AOF 文件来还原数据库状态,但因为 Sentinel 并不使用数据库,所以初始化 Sentinel 时就不会载入 RDB 或 AOF 文件。
下表展示了 Redis 服务器在 Sentinel 模式下运行时,服务器各个主要功能的使用情况。
启动 Sentinel 的第二步就是将一部分普通 Redis 服务器代码替换成 Sentinel 专用代码。比如,使用不同的默认端口 26379,而非常规的 6379,还有载入不同的命令列表等。
在应用了 Sentinel 的专用代码后,服务器会初始化一个 sentinelState 结构,其中保存了服务器中所有和 Sentinel 功能有关的状态(服务器的一般状态仍然由 redisServer 结构保存):
其中的 masters 字典记录了所有被 Sentinel 监视的主服务器的相关信息,字典的值是一个 sentinelRedisInstance 结构,代表一个被监视的 Redis 服务器实例,可以是主服务器、从服务器、或者另一个 Sentinel。
对 Sentinel 状态的初始化将引发对 masters 字典的初始化,而 masters 字典的初始化是根据被载入的 Sentinel 配置文件来进行的。
初始化 Sentinel 的最后一步是创建连向被监视主服务器的网络连接,Sentinel 将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关信息。对于每个被监视的主服务器来说,Sentinel 会创建两个连向主服务器的异步网络连接:
* 一个是命令连接,专门用于向主服务器发送命令,并接收命令回复。
* 另一个是订阅连接,专门用于订阅主服务器的 __sentinel__:hello 频道。
Sentinel 默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送 INFO 命令,并根据命令回复来获取主服务器的当前信息,比如主服务器的运行 ID(run_id)、服务器角色、主服务器属下的从服务器信息等。
当 Sentinel 发现主服务器的从服务器时,除了会为之创建相应的实例结构之外,还会创建连接到从服务器的命令连接和订阅连接。之后,Sentinel 也会以十秒一次的频率通过命令连接向从服务器发送 INFO 命令,然后根据命令回复信息,不断地更新从服务器的实例结构。
默认情况下,Sentinel 会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
其中,前四个以“s_”开头的参数记录的是 Sentinel 自身的信息,分别表示 Sentinel 的 IP、端口号、运行 ID、当前的配置纪元(configuration epoch),后四个以“m_”开头的记录的则是主服务器的信息,分别表示主服务器的名字、IP、端口号和当前的配置纪元。
对于监视同一个服务器的多个 Sentinel 来说,一个 Sentinel 发送的信息会被其他 Sentinel 接收到,这些信息会被用于更新其他 Sentinel 对发送信息 Sentinel 的认知,也会被用于更新其他 Sentinel 对被监视服务器的认知。
当 Sentinel 通过频道信息发现一个新的 Sentinel 时,它不仅会为新 Sentinel 在对应主服务器实例结构的 sentinels 字典中创建相应的实例结构,还会创建一个连向新 Sentinel 的命令连接,而新 Sentinel 也同样会创建连向这个 Sentinel 的命令连接。使用命令连接相连的各个 Sentinel 可以通过向其他 Sentinel 发送命令请求来进行信息交换,比如对 Sentinel 实现主观下线检测和客观下线检测。
下线状态检测
Sentinel 默认会以每秒一次的频率向所有与之建立了命令连接的实例(包括主服务器、从服务器和其他 Sentinel 在内)发送 PING 命令,之后再通过命令回复来判断对应的实例是否在线。Sentinel 配置文件中的 down-after-milliseconds 选项指定了 Sentinel 判断实例进入主观下线所需的时间长度:如果一个实例在配置的毫秒内,连续向 Sentinel 返回无效回复(除 +PONG、-LOADING 和 -MASTERDOWN 之外的其他回复),那么 Sentinel 就会修改这个实例对应的实例结构,打开 flags 属性中的 SRI_S_DOWN 标识,以此来表示该实例已经进入主观下线状态。
当 Sentinel 将一个主服务器判断为主观下线后,为了更进一步确认,它会使用命令“SENTINEL is-master-down-by-addr”向同样监视这一主服务器的其他 Sentinel 进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或客观下线),同时也会让它们顺便选出领头 Sentinel。当从其他 Sentinel 那里接收到足够数量的已下线判断后(超过 quorum 参数配置的值),Sentinel 就会将其判断为客观下线,并打开主服务器实例结构 flags 属性的 SRI_O_DOWN 标识,然后执行故障转移操作。
故障转移
当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个 Sentinel 会进行协商,选举出一个领头 Sentinel,以让其对下线主服务器执行故障转移操作。
以下是 Redis 选举领头 Sentinel 的规则和方法:
1、所有在线的 Sentinel 都有被选为领头 Sentinel 的资格。
2、每次进行选举后,不论选举是否成功,所有 Sentinel 的配置纪元计数器都会自增一次。
3、在一个配置纪元里,所有 Sentinel 都有一次将某个 Sentinel 设置为局部领头 Sentinel 的机会,并且局部领头一旦设置,在这个配置纪元里就不能再更改。
4、每个发现主服务器进入客观下线的 Sentinel 都会要求其他 Sentinel 将自己设置为局部领头。
5、Sentinel 设置局部领头的规则是先到先得。
6、领头 Sentinel 的产生需要半数以上 Sentinel 的支持。
7、如果在给定的时限内,没有一个 Sentinel 被选举为领头,那么各个 Sentinel 将在一段时间后再次进行选举,直到选出领头为止。
在领头 Sentinel 选举出来后,它将对已下线的主服务器执行故障转移操作,这包括以下三个步骤:
1)在已下线主服务器属下的所有从服务器里面,挑选出一个作为主服务器。
2)让其它从服务器改为复制新的主服务器。
3)将已下线主服务器设置为新的主服务器的从服务器。
具体来说,领头 Sentinel 在挑选新的主服务器时,它是按照下面的规则来对已下线主服务器的从服务器列表进行过滤的:
1、删除列表中所有处于下线或者断线状态的从服务器。
2、删除列表中所有最近五秒内没有回复过领头 Sentinel 的 INFO 命令的从服务器,以保证列表中剩余的从服务器都是最近成功进行过通信的。
3、删除所有与已下线主服务器连接断开超过 down-after-milliseconds * 10 毫秒的从服务器,这可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接。
之后,领头 Sentinel 将把按照优先级最高、复制偏移量最大和运行 ID 最小的顺序挑选出的从服务器作为主服务器。
对于让其他从服务器和已下线主服务器变为新的主服务器的从服务器,则是通过命令 SLAVEOF 命令实现的。
参考书籍:《Redis设计与实现》第16章 —— Sentinel。
本节接下来将对 Sentinel 系统的初始化过程、实现原理,以及它如何对主服务器执行故障转移等操作进行详细的介绍。
启动并初始化 Sentinel
启动一个 Sentinel 可以使用命令:
$ redis-server /path/to/sentinel.conf --sentinel
或者:
$ redis-sentinel /path/to/sentinel.conf
Sentinel 本质上只是一个运行在特殊模式下的 Redis 服务器,所以启动的第一步就是初始化一个普通的 Redis 服务器。不过由于执行的工作不同,所以初始化过程也有所差别。比如,普通服务器在初始化时会通过载入 RDB 文件或者 AOF 文件来还原数据库状态,但因为 Sentinel 并不使用数据库,所以初始化 Sentinel 时就不会载入 RDB 或 AOF 文件。
下表展示了 Redis 服务器在 Sentinel 模式下运行时,服务器各个主要功能的使用情况。
启动 Sentinel 的第二步就是将一部分普通 Redis 服务器代码替换成 Sentinel 专用代码。比如,使用不同的默认端口 26379,而非常规的 6379,还有载入不同的命令列表等。
在应用了 Sentinel 的专用代码后,服务器会初始化一个 sentinelState 结构,其中保存了服务器中所有和 Sentinel 功能有关的状态(服务器的一般状态仍然由 redisServer 结构保存):
struct sentinelState{ uint64_t current_epoch; // 当前纪元,用于实现故障转移 // 保存了所有被这个 sentinel 监视的主服务器 // 字典的键是主服务器的名字 // 字典的值则是一个指向 sentinelRedisInstance 结构的指针 dict *masters; int tilt; // 是否进入了 TILT 模式? int running_scripts; // 目前正在执行的脚本的数量 mstime_t tilt_start_time; // 进入 TILT 模式的时间 mstime_t previous_time; // 最后一次执行时间处理器的时间 list *scripts_queue; // FIFO 队列,包含了所有需要执行的用户脚本 }sentinel;
其中的 masters 字典记录了所有被 Sentinel 监视的主服务器的相关信息,字典的值是一个 sentinelRedisInstance 结构,代表一个被监视的 Redis 服务器实例,可以是主服务器、从服务器、或者另一个 Sentinel。
typedef struct sentinelRedisInstance{ /* ... other fields */ int flags; // 标识值,记录了实例的类型 // 实例的名字 // 主服务器的名字由用户在配置文件中设置 // 从服务器以及 Sentinel 的名字由 Sentinel 自动设置,格式为“ip:port” char *name; char *runid; // 实例的运行 ID uint64_t config_epoch; // 配置纪元,用于实现故障转移 sentinelAddr *addr; // 实例的地址 // 保存了监视同一个主服务器的其他 Sentinel // 字典的键是 Sentinel 的名字,格式为“ip:port” // 字典的值则是对应 Sentinel 的 sentinelRedisInstance 实例结构 dict *sentinels; // 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;
对 Sentinel 状态的初始化将引发对 masters 字典的初始化,而 masters 字典的初始化是根据被载入的 Sentinel 配置文件来进行的。
初始化 Sentinel 的最后一步是创建连向被监视主服务器的网络连接,Sentinel 将成为主服务器的客户端,它可以向主服务器发送命令,并从命令回复中获取相关信息。对于每个被监视的主服务器来说,Sentinel 会创建两个连向主服务器的异步网络连接:
* 一个是命令连接,专门用于向主服务器发送命令,并接收命令回复。
* 另一个是订阅连接,专门用于订阅主服务器的 __sentinel__:hello 频道。
Sentinel 默认会以每十秒一次的频率,通过命令连接向被监视的主服务器发送 INFO 命令,并根据命令回复来获取主服务器的当前信息,比如主服务器的运行 ID(run_id)、服务器角色、主服务器属下的从服务器信息等。
当 Sentinel 发现主服务器的从服务器时,除了会为之创建相应的实例结构之外,还会创建连接到从服务器的命令连接和订阅连接。之后,Sentinel 也会以十秒一次的频率通过命令连接向从服务器发送 INFO 命令,然后根据命令回复信息,不断地更新从服务器的实例结构。
默认情况下,Sentinel 会以每两秒一次的频率,通过命令连接向所有被监视的主服务器和从服务器发送以下格式的命令:
PUBLISH __sentinel__:hello "<s_ip>,<s_port>,<s_runid>,<s_epoch>,<m_name>,<m_ip>,<m_port>,<m_epoch>"
其中,前四个以“s_”开头的参数记录的是 Sentinel 自身的信息,分别表示 Sentinel 的 IP、端口号、运行 ID、当前的配置纪元(configuration epoch),后四个以“m_”开头的记录的则是主服务器的信息,分别表示主服务器的名字、IP、端口号和当前的配置纪元。
对于监视同一个服务器的多个 Sentinel 来说,一个 Sentinel 发送的信息会被其他 Sentinel 接收到,这些信息会被用于更新其他 Sentinel 对发送信息 Sentinel 的认知,也会被用于更新其他 Sentinel 对被监视服务器的认知。
当 Sentinel 通过频道信息发现一个新的 Sentinel 时,它不仅会为新 Sentinel 在对应主服务器实例结构的 sentinels 字典中创建相应的实例结构,还会创建一个连向新 Sentinel 的命令连接,而新 Sentinel 也同样会创建连向这个 Sentinel 的命令连接。使用命令连接相连的各个 Sentinel 可以通过向其他 Sentinel 发送命令请求来进行信息交换,比如对 Sentinel 实现主观下线检测和客观下线检测。
下线状态检测
Sentinel 默认会以每秒一次的频率向所有与之建立了命令连接的实例(包括主服务器、从服务器和其他 Sentinel 在内)发送 PING 命令,之后再通过命令回复来判断对应的实例是否在线。Sentinel 配置文件中的 down-after-milliseconds 选项指定了 Sentinel 判断实例进入主观下线所需的时间长度:如果一个实例在配置的毫秒内,连续向 Sentinel 返回无效回复(除 +PONG、-LOADING 和 -MASTERDOWN 之外的其他回复),那么 Sentinel 就会修改这个实例对应的实例结构,打开 flags 属性中的 SRI_S_DOWN 标识,以此来表示该实例已经进入主观下线状态。
当 Sentinel 将一个主服务器判断为主观下线后,为了更进一步确认,它会使用命令“SENTINEL is-master-down-by-addr”向同样监视这一主服务器的其他 Sentinel 进行询问,看它们是否也认为主服务器已经进入了下线状态(可以是主观下线或客观下线),同时也会让它们顺便选出领头 Sentinel。当从其他 Sentinel 那里接收到足够数量的已下线判断后(超过 quorum 参数配置的值),Sentinel 就会将其判断为客观下线,并打开主服务器实例结构 flags 属性的 SRI_O_DOWN 标识,然后执行故障转移操作。
故障转移
当一个主服务器被判断为客观下线时,监视这个下线主服务器的各个 Sentinel 会进行协商,选举出一个领头 Sentinel,以让其对下线主服务器执行故障转移操作。
以下是 Redis 选举领头 Sentinel 的规则和方法:
1、所有在线的 Sentinel 都有被选为领头 Sentinel 的资格。
2、每次进行选举后,不论选举是否成功,所有 Sentinel 的配置纪元计数器都会自增一次。
3、在一个配置纪元里,所有 Sentinel 都有一次将某个 Sentinel 设置为局部领头 Sentinel 的机会,并且局部领头一旦设置,在这个配置纪元里就不能再更改。
4、每个发现主服务器进入客观下线的 Sentinel 都会要求其他 Sentinel 将自己设置为局部领头。
5、Sentinel 设置局部领头的规则是先到先得。
6、领头 Sentinel 的产生需要半数以上 Sentinel 的支持。
7、如果在给定的时限内,没有一个 Sentinel 被选举为领头,那么各个 Sentinel 将在一段时间后再次进行选举,直到选出领头为止。
在领头 Sentinel 选举出来后,它将对已下线的主服务器执行故障转移操作,这包括以下三个步骤:
1)在已下线主服务器属下的所有从服务器里面,挑选出一个作为主服务器。
2)让其它从服务器改为复制新的主服务器。
3)将已下线主服务器设置为新的主服务器的从服务器。
具体来说,领头 Sentinel 在挑选新的主服务器时,它是按照下面的规则来对已下线主服务器的从服务器列表进行过滤的:
1、删除列表中所有处于下线或者断线状态的从服务器。
2、删除列表中所有最近五秒内没有回复过领头 Sentinel 的 INFO 命令的从服务器,以保证列表中剩余的从服务器都是最近成功进行过通信的。
3、删除所有与已下线主服务器连接断开超过 down-after-milliseconds * 10 毫秒的从服务器,这可以保证列表中剩余的从服务器都没有过早地与主服务器断开连接。
之后,领头 Sentinel 将把按照优先级最高、复制偏移量最大和运行 ID 最小的顺序挑选出的从服务器作为主服务器。
对于让其他从服务器和已下线主服务器变为新的主服务器的从服务器,则是通过命令 SLAVEOF 命令实现的。
参考书籍:《Redis设计与实现》第16章 —— Sentinel。