如何为 Redict Sentinel 构建客户端

Redict Sentinel 是 Redict 实例的监控解决方案,它处理 Redict 主节点的自动故障转移和服务发现(给定实例组的当前主节点是谁?)。由于 Sentinel 既负责在故障转移期间重新配置实例,也负责向连接到 Redict 主节点或副本的客户端提供配置,因此客户端需要明确支持 Redict Sentinel。

本文档针对希望在其客户端实现中支持 Sentinel 的 Redict 客户端开发人员,目标如下:

  • 通过 Sentinel 自动配置客户端。
  • 提高 Redict Sentinel 自动故障转移的安全性。

有关 Redict Sentinel 如何工作的细节,请查看 Sentinel 文档,因为本文档只包含 Redict 客户端开发人员所需的信息,并且预期读者熟悉 Redict Sentinel 的工作方式。

通过 Sentinel 进行 Redict 服务发现

Redict Sentinel 使用如 “stats” 或 “cache” 之类的名称来标识每个主节点。每个名称实际上标识了一个 实例组,由一个主节点和可变数量的副本组成。

在网络中用于特定目的的 Redict 主节点的地址可能会在如下事件之后更改:

  • 自动故障转移。
  • 手动触发的故障转移(例如为了升级 Redict 实例)。
  • 其他原因。

通常 Redict 客户端有一些硬编码配置,指定网络中 Redict 主节点实例的 IP 地址和端口号。但是,如果主节点地址更改,每个客户端都需要手动干预。

支持 Sentinel 的 Redict 客户端可以自动从主节点名称使用 Redict Sentinel 发现主节点地址。因此,客户端支持 Sentinel 应该能够作为输入接受的不是硬编码的 IP 地址和端口,而是:

  • 指向已知 Sentinel 实例的 ip:port 对列表。
  • 服务名称,如 “cache” 或 “timelines”。

以下是客户端应该遵循的程序,以便从 Sentinel 列表和服务名称开始获取主节点地址。

第 1 步:连接到第一个 Sentinel

客户端应该迭代 Sentinel 地址列表。对于每个地址,它应该尝试使用短暂的超时(几百毫秒的顺序)连接到 Sentinel。如果出现错误或超时,则应尝试下一个 Sentinel 地址。

如果所有 Sentinel 地址都尝试过但没有成功,应该向客户端返回错误。

第一个响应客户端请求的 Sentinel 应该被放在列表的开头,这样在下次重新连接时,我们会首先尝试之前连接尝试中可达的 Sentinel,最小化延迟。

第 2 步:询问主节点地址

一旦与 Sentinel 建立连接,客户端应该重试在 Sentinel 上执行以下命令:

  1. SENTINEL get-master-addr-by-name master-name

其中 master-name 应该替换为用户指定的实际服务名称。

这个调用的结果可以是以下两种回复之一:

  • 一个 ip:port 对。
  • 一个空回复。这意味着 Sentinel 不知道这个主节点。

如果收到一个 ip:port 对,这个地址应该用来连接到 Redict 主节点。否则,如果收到一个空回复,客户端应该尝试列表中的下一个 Sentinel。

第 3 步:在目标实例中调用 ROLE 命令

一旦客户端发现了主节点的地址,它应该尝试与主节点建立连接,并调用 ROLE 命令以验证实例的角色实际上是主节点。

如果 ROLE 命令不可用,客户端可以使用 INFO replication 命令并解析输出中的 role: 字段。

如果实例不是预期的主节点,客户端应该等待一小段时间(几百毫秒),然后从第 1 步开始重试。

处理重新连接

一旦服务名称解析为主节点地址并与 Redict 主节点实例建立了连接,每当需要重新连接时,客户端应该使用 Sentinel 重新解析地址,从第 1 步重新开始。例如,在以下情况下应该再次联系 Sentinel:

  • 如果客户端在超时或套接字错误后重新连接。
  • 如果客户端因为用户明确关闭或重新连接而重新连接。

在上述情况和任何其他客户端与 Redict 服务器失去连接的情况下,客户端应该重新解析主节点地址。

Sentinel 故障转移断开连接

当 Redict Sentinel 更改实例的配置时,例如将副本提升为主节点,在故障转移后将主节点降级为复制到新主节点,或者简单地更改陈旧副本实例的主节点地址,它会向实例发送一个 CLIENT KILL type normal 命令,以确保所有客户端都从重新配置的实例断开连接。这将迫使客户端再次解析主节点地址。

如果客户端联系的 Sentinel 信息尚未更新,通过 ROLE 命令验证 Redict 实例角色将失败,允许客户端检测到联系的 Sentinel 提供了过时的信息,并会重试。

注意:可能存在陈旧的主节点同时上线的情况,同时客户端联系了一个陈旧的 Sentinel 实例,因此客户端可能会连接到一个陈旧的主节点,而 ROLE 输出仍然匹配。然而,当主节点再次上线时,Sentinel 将尝试将其降级为副本,触发新的断开连接。同样的道理适用于连接到将被重新配置以复制不同主节点的陈旧副本。

连接到副本

有时客户端有兴趣连接到副本,例如为了扩展读取请求。通过稍微修改第 2 步,这个协议支持连接到副本。客户端不应该调用以下命令:

  1. SENTINEL get-master-addr-by-name master-name

客户端应该改为调用:

  1. SENTINEL replicas master-name

以检索副本实例的列表。

对称地,客户端应该使用 ROLE 命令验证实例实际上是副本,以避免使用主节点扩展读取查询。

连接池

对于实现连接池的客户端,在单个连接重新连接时,应该再次联系 Sentinel,如果主节点地址更改,则应该关闭所有现有连接并连接到新地址。

错误报告

客户端在出现错误时应正确向用户返回信息。具体来说:

  • 如果无法联系到任何 Sentinel(因此客户端从未能够获得对 SENTINEL get-master-addr-by-name 的回复),应该返回一个明确指出 Redict Sentinel 不可达的错误。
  • 如果所有 Sentinel 池中的 Sentinel 都回复了空回复,应该用一个错误告知用户 Sentinel 不知道这个主节点名称。

Sentinel 列表自动刷新

可选地,一旦收到对 get-master-addr-by-name 的成功回复,客户端可以按照以下程序更新其内部 Sentinel 节点列表:

  • 使用命令 SENTINEL sentinels <master-name> 获取此主节点的其他 Sentinel 列表。
  • 将列表中尚未存在的每个 ip:port 对添加到我们列表的末尾。

客户端不需要能够使列表持久化更新其自己的配置。能够升级 Sentinel 列表的内存表示已经可以提高可靠性。

订阅 Sentinel 事件以提高响应性

Sentinel 文档 展示了客户端如何使用 Pub/Sub 连接到 Sentinel 实例以订阅 Redict 实例配置的更改。

这个机制可以用来加速客户端的重新配置,也就是说,客户端可以监听 Pub/Sub 以知道何时发生了配置更改,以便运行本文档中解释的三个步骤协议,以解析新的 Redict 主节点(或副本)地址。

然而,通过 Pub/Sub 接收的更新消息不应替代上述程序,因为不能保证客户端能够接收到所有更新消息。

(这段代码是一个 JavaScript 函数,用于在网页上的 pre code 块中添加点击事件监听器,当点击这些代码块时,如果没有选择文本,则会自动选择并复制代码块的文本。这是一个前端的代码段,与 Redict Sentinel 客户端构建的主题不相关。)

以上是构建支持 Redict Sentinel 的客户端的指南,包括服务发现、处理重新连接、错误报告和连接池管理等方面的细节。通过遵循这些步骤,客户端可以确保在 Redict 主节点发生故障转移时自动更新并重新连接到正确的节点。