本文将从四个维度,深入剖析 Pulsar 在多可用区高可用领域的容灾策略。首先,我们将从整体架构的角度切入,洞察其设计之精髓;其次,我们将探寻 Pulsar 在云原生领域的努力;再者,我们将聚焦 Pulsar 在高可用方面的设计,展现其稳健可靠的特性;最后,我们延申拓展 Pulsar 在跨地区同步方面的未来规划。
作者简介
林宇强
腾讯云高级工程师
专注于消息队列、API 网关、微服务、数据同步等 PaaS 领域。有多年的开发和维护经验,目前在腾讯云从事 TDMQ for Apache Pulsar 商业化产品方向的研发工作。
整体架构
腾讯云TDMQ for Apache Pulsar(简称TDMQ Pulsar版)是基于 Apache Pulsar 开发的一款金融级商业化消息中间件。这个系统主要包括三个核心部分:ZK(ZooKeeper)、BK(BookKeeper)和 Broker。
因为我们用的是腾讯云,所以 ZooKeeper 部分就直接用了云上的服务,省去了自己维护的麻烦。而 BK 和 Broker 部分,则是由我们自己来管理和维护的。当然,我们也对这些部分做了一些扩展,主要是为了更好地和云环境集成。
在公有云上提供服务,我们主要关注的是如何与云的标准功能对接。比如,我们添加了计费、云 API 接口、管理控制以及云监控等功能。这些都是为了让我们的产品能具备云服务应有的基础设施能力。
另外,我们也增强了系统的可观测性。你可能不知道,可观测性主要看三个东西:Metrics(度量指标)、Log(日志)和 Trace(追踪)。我们这里主要提供了 Trace 功能,能记录每一条消息从生产到存储再到投递的全过程,这样你就能在云上直接看到消息流转的完整链路。
我们还基于 Pulsar 自带的功能,开发了一套 Metrics 数据采集链路,这些数据会接入到我们的云监控系统中,与云服务的标准相统一。
再来说说我们的整体架构。与开源模式相比,我们增加了一个叫 Lookup Service 的模块。Lookup Service 是地域级别的模块,每个地区只部署一套,用于同一个地区所有 Broker 集群的路由、寻址功能,是基于 Pulsar Http Lookup Service 进行二次开发的。这个模块的作用就像 RocketMQ 里的 NameServer 一样,负责调度。它可以根据需要,把一个租户或者一个命名空间调度到我们底层的任何一个物理集群上,实现了在物理机群之间的灵活调度和漂移。
我们的整个架构已经全面容器化了,所有的模块都是在容器上部署的,这样更加灵活和高效。
云原生与有状态服务
接下来是关于我们在容器化相关做的一些考量,容器化主要核心的关键点在于 BK 它是一个有状态的服务,所以我们就要考虑怎么在容器上去实现有状态服务的对接。
以下是云原生与有状态服务的三个关键点:
- 节点:计算+存储+网络;
- 有状态服务:存储+网络;
- 可插拔:云盘+弹性网卡。
一个节点从功能上大致可以拆分成三部分:容器和虚机
- 计算:CPU、内存等;
- 存储:磁盘;
- 网络:网卡。
有状态服务,核心:
- 节点标识:Hostname、IP、K8s 域名等;
- 节点绑定的存储空间:系统盘、数据盘;
- 节点绑定的网络标识:IP、K8s 域名等。
核心问题:
- 存储:云盘,只需保证节点重启时能够重新绑回之前的同一块盘。
- 网络:弹性网卡,节点重启时绑回之前 IP 对应的弹性网卡。
关键在于确保每个服务实例有一个固定的 IP 地址和对应的磁盘存储。CPU 和内存资源则可以在云资源池中灵活分配,无需固定绑定。只要服务实例的 IP 地址和磁盘映射保持不变,即便它在资源池中迁移到其他节点,也能保持其有状态服务的特性,就像它一直在同一个节点上运行一样。
所以实际上我们有状态服务,要考虑的就是存储和网络该怎么弹性的去做插拔,为了实现这一点,我们利用腾讯云的能力,采用云盘作为存储,弹性网卡提供网络服务。这使得每个 Pod 或机器都能在资源池中自由移动,无论迁移到哪个节点,只要正确挂载相应的弹性网卡和云盘,就能保持服务的连续性和稳定性。
这样的设计极大地减轻了我们对资源分配的担忧。云上的资源可以根据需要灵活调配,无需担心资源不足或紧急扩容的问题。同时,也避免了繁琐的机器申请和审批流程,提高了工作效率。
此外,我们还遵循云原生的不可变原则。一旦 Pod 启动,其内部的进程、程序和配置都保持不变。这种不可变性有助于确保服务的稳定性和可预测性,避免了因配置修改或命令执行不当而引入的风险。
这样有什么好处呢?
- CPU、内存可以随时切换任意机器。
- 基于云的计算资源池,不再担心机器库存问题,补充机器的流程耗时问题,紧急扩容时到处腾挪机器的问题。
- 规范和稳定性:基于云原生的规范,容器启动后即为不可变服务,不会出现服务上线后,人工上机器该配置等非标操作,引发各种后续问题。
变更流程
变更流程是我们在云原生和容器场景下,进行集群变更的标准化过程。这包括集群的扩容、缩容、新集群的创建或配置升级。
流程的前几个步骤,如确定规格和渲染配置,与云服务的常规发货流程相似。但关键的环节是我们的部署流程,特别是针对 Pulsar 集群的部署。
首先,我们需要构建或购买 ZK(ZooKeeper)服务。在云环境下,我们可以直接购买并使用 ZK。
接下来是 BK(BookKeeper)的初始化和部署。在 BK 部署之前,需要对 ZK 中的相关元素进行初始化。
随后,我们进行 Broker 的初始化,并创建接入点,以便与公有云服务进行网络连接。
由于整个流程相当复杂,并且包含许多依赖关系,我们选择使用 Helm 进行流程编排。Helm 的可编程性使我们能够精确地控制流程的各个环节。例如,在部署 Broker 之前,必须先部署 BK,并确保至少有两个 BK 节点启动后,Broker 才能开始启动。
通过使用 Helm,我们可以自动化处理各种依赖关系,从而统一整个流程。无论是创建新集群、升级配置,还是进行扩容或缩容,都可以遵循相同的流程,只是可能会跳过某些步骤,如在进行升级操作时,可能会跳过初始化步骤。
高可用
部署拓扑
关于高可用性的核心,我们首先需要关注的是如何进行拓扑规划。
多可用区部署主要涵盖三个要点。
- ZK(ZooKeeper):为了实现多可用区部署,至少需要3个可用区,其布局应遵循1-1-1或2-2-1的模式,图示为1-1-1布局。这种配置能确保任何一个可用区发生故障时,系统仍能保持可用性。
- BK(BookKeeper):这是一个有状态服务,需要确保磁盘数据与IP地址的对应关系维持不变。这一点对于有状态容器的实现至关重要。
- Broker:这是一个无状态服务,因此,理论上只要剩下一台机器能够承受住所有流量,服务就仍然可用。
为了实现 Pulsar 的多可用区高可用性,通常需要至少三个节点分布在三个可用区。这是因为 ZooKeeper 需要满足半数以上的节点保持可用。例如,如果 ZooKeeper 规划了三个节点,为了满足半数可用性并确保在一个可用区故障后,其余可用区能正常运行,就需要至少三个可用区,节点分布可以是1-1-1。如果是五个 ZooKeeper 节点,那么可以采用2-2-1的布局,以确保任意一个可用区故障后,剩余两个可用区的节点总数仍超过一半。
BK 作为有状态服务,其核心是确保存活的BK节点数量要多于 Broker 设计的多副本数量。因为 Pulsar 的 Topic 存储使用了多副本策略,所以只要 BK 的存活节点数量大于其多副本数量,理论上系统就能保持可用性。此外,机架感知可能会对 BK 的数量产生一定影响。
相比之下,Broker 的处理就简单多了。由于它是无状态服务,因此即使在一个包含六个节点的拓扑中,仅剩一个节点,只要这个节点能承受住所有流量,服务就仍然是可用的。
因此,在讨论高可用性时,我们更多关注的是如何配置 BK,因为 ZooKeeper 的配置模式相对简单且为人们所熟知。
BK 的高可用
关于BK的高可用性,我们主要考虑了以下四个方面来达成这个目标:
- 多可用区部署:举例来说,我们计划在三个不同的可用区域进行部署。
- 多副本:利用 Pulsar 的内置功能,我们强调数据的安全性。当某个副本发生故障时,其他副本可以确保数据的完整性和可恢复性。
- 机架感知:此功能有助于系统了解并优化数据的物理存储位置,从而提高数据的可靠性和访问速度。
- 变更可用性:主要依赖于 Pulsar 的 Auto Recovery 模块。当有多个副本时,若其中一个 BK 节点发生故障或宕机,部分数据的副本数可能会少于预期。此时,Auto Recovery 功能会自动启动,补充和恢复所需的副本数量。但需要注意的是,我们设计这个功能仅在需要变更时启动,而非长期运行,以避免产生不必要的数据冗余或错误。
多副本
Pulsar 本身提供了一个 Quorum 机制来确保数据的安全性和可靠性,这主要涉及到三个关键参数:
- Ensemble Quorum:指的是当某个 Topic 需要写入数据时,必须选择的 BK 节点数量。只有当选择的 BK 节点数量大于或等于 Ensemble 设定的值时,Topic 才能正常写入数据。
- Write Quorum:这个参数决定了在 Ensemble 中选择多少个节点来写入数据。例如,如果 Ensemble 设定为三个节点,而 Write Quorum 选择两个节点,那么系统会从这三个节点中选择两个进行数据写入。
- Ack Quorum:数据写入完成后,需要等待足够数量的节点发送确认(Ack)信号,只有当收到这么多 Ack 后,系统才会向客户端报告写入成功。
在讨论 BK 节点写入数据的流程中,我们更关注 Ensemble Quorum 参数。如果我们设定 Ensemble 为3,那么系统会选择3个 BK 节点进行写入。Pulsar 的设计特点是采用条带化写入方式,这意味着同一条消息会被分别写入不同的 BK 节点。例如,如果 Write Quorum 设定为2,那么同一条消息的一部分会写入第一个和第二个 BK 节点,下一条消息会写入第二个和第三个BK节点,以此类推,这种方式可以确保所有BK节点的负载均衡,并充分利用每个 BK 节点的能力。
总的来说,这是我们通过利用 Pulsar 的多副本功能来提供数据的安全性和可靠性的方式。
机架感知
这个模型相当于我们有三个区域,每个区域都有两个存储节点,所以总共有六个节点。为了确保数据的高可用性,我们需要保证每一条信息都至少在两个区域有备份。但是,如果我们不进行特定设置,有可能会出现一条信息的两个备份都存储在同一个区域的情况。这样,如果该区域出现故障,这条信息就会完全丢失。
因此,我们需要控制信息的写入方式,确保每个信息的两个备份都分布在两个不同的区域。Pulsar 的存储节点机架感知功能就提供了这样的能力。
在设计时,我们将每个可用区看作是一个机架,因此有三个可用区就相当于有三个机架。当我们设定参数时,如果选择“332”模式,那就意味着有6个存储节点。如果一个区域失效,就只剩下4个节点。在理论上,此时的最大副本数可以设为4,但通常建议设为2或3,具体取决于你的副本策略是“221”还是“332”。
关于机架感知,关键参数 Region 感知开关应让其保持关闭状态,因为我们没有多个区域,所以这样设置较为简便。
另一个关键参数是最小机架数,这个参数能确保每条信息的副本都分布在多个机架上,机架在这里对应着我们的可用区。例如,如果我们想要信息至少分布在两个区域,那就将最小机架数设为2。这样,即使三个可用区中有一个发生故障,也能确保所有消息都仍然至少剩下一份副本可用。这些设置与 Pulsar 的多副本功能和机架感知功能相配合,共同实现了存储节点的多区域可用性。
延伸:跨地区同步
接下来,我将介绍一下我们未来要提供的能力,主要是跨地区同步。
跨地区同步对用户来说,就像在广州和上海各购买了一个服务实例,并将这两个实例联动起来。联动后,两个实例的元数据和消息会进行双向同步。如果用户的业务主要在广州,那么通常他们会直接通过广州本地的服务进行访问。但如果广州的服务出现故障,我们可以通过域名解析切换,让用户通过另一条路径访问到上海的服务。
由于上海的服务一直与广州保持数据同步,虽然可能会有一定的延迟,但大部分数据都可以恢复。只有一种极端情况,就是在切换时,广州可能堆积了一些未处理的消息,这些消息在上海可能无法及时消费。这时,只能等待广州的服务恢复后,再切换回去重新消费这些消息。
对于用户来说,如果想要访问其他地区的服务,比如上海,他们需要使用云联网等云上策略。但这种方式需要接受跨地区的时延,国内地区间的时延通常是几十毫秒,而跨半个地球的时延可能达到两三百毫秒。
关于跨地区时延的影响,主要分为元数据和消息同步两个方面。元数据主要涉及Topic 的创建和消费等场景。一般来说,Topic 不会频繁变更。如果在灾难切换时有Topic 创建且未同步,可能需要人工到远端集群创建 Topic,或者制定灾难场景下的快速应急预案。
此外,还有一些特殊业务场景,如跨境电商,需要在全球多个地区进行交易,但离线报表需要汇总到国内处理。这时,可以在不同地区部署服务实例,并将数据同步到国内进行报表处理。同时,各地区的在线业务订阅和离线订阅也可以实现全球多地区的联动。