“故障总是在意料之外,情理之中发生。”
——亚马逊 CTO Werner Vogels
Apache Pulsar Committer、《Apache Pulsar in Action》作者 David Kjerrumgaard 拥有十多年的大型分布式系统工作职业经历,他以亲身经历证实:失败是此类系统不可避免的现实——现代分布式系统中涉及的组件数量众多,几乎不可能实现 100% 正常运行时间。因此,在 Apache Pulsar 等大型系统之上构建应用程序时,在架构中构建弹性非常重要。构建弹性应用程序的必要先决条件是选择可靠的软件系统作为应用技术栈的基础组件,而 Pulsar 肯定满足这一要求。
开发高可用性应用程序需要的不仅仅是在软件技术栈中搭建 Apache Pulsar 等类似容错服务等,还需要实时检测和解决故障,包括在数据中心中断时进行内置故障转移。
截至目前已发布版本,Pulsar 客户端只能与单个 Pulsar 集群交互,无法检测和响应集群级别的故障事件。在集群完全故障的情况下,这些客户端无法自动将其消息重新路由到辅助/备用集群。因为客户端无法建立与活跃集群的连接,任何使用 Pulsar 客户端的应用程序都容易受到长时间中断的影响,并可能会导致数据丢失和失掉业务 SLA。
随着 Pulsar 2.10 的发布,自动化集群故障转移功能已添加到 Pulsar 客户端库中。本博客将介绍如何在应用程序更改代码来运行此新功能。
Apache Pulsar 的架构包含了几个容错特性:组件冗余、数据复制,以及连接感知客户端库,它会自动检测出客户端与服务层中的一个 broker 断开连接并将其恢复。连接故障检测和恢复完全在 Pulsar 客户端内部处理,对应用程序完全可见。
图 1: Pulsar 客户端提供透明的重新连接和/或连接故障转移到另一个 broker,使应用程序开发人员免受瞬态网络故障(transient network failures)的影响。
正如前文所说,在使用复杂的分布式系统时,故障是不可避免的。这就是 Pulsar 客户端存在的意义。Pulsar 的自动负载均衡会定期将 topic 重新分配给不同的 broker,以更均匀地分配传入的客户端流量,客户端对重新分配的 topic 的所有读/写将自动断开。
在这种情况下,依靠客户端的自动恢复功能来重新连接到新分配的 broker 并继续处理不会错过任何时间节点。从应用程序的角度来看,这种从一个 broker 到另一个 broker 的转换是透明的,不会引发需要由应用程序处理的异常。
注意,连接自动恢复仅在全部 broker 都位于同一环境中的同一集群时才有效。在从活跃集群到在不同区域运行的备用集群的故障转移场景中,连接自动恢复不起作用。这是当前 Pulsar 客户端库的一大缺点。我们会在下一部分解释原因。
用于提供持续可用性的常见技术是将单独的集群实例配置为在活跃/备用模式下运行。在不同区域配置冗余基础架构有助于减轻数据中心或云区域故障的影响。
在这样的配置中,所有流量都被路由到“活跃”集群,数据被复制到“备用”集群,以使它们尽可能地保持同步,以确保当用户需要切换到“备用”集群时,消息数据可供消费者使用。
此外,用户必须复制所有 Pulsar 订阅,以确保消费者从故障发生前中断的确切位置恢复消息消费。
为实现持续可用性,如果“活跃”集群因任何原因出现故障,所有活跃的生产者和消费者应立即重定向到“热”备用集群。这种转换对连接的应用程序应该是透明的。
图 2: 多区域主动/备用 Pulsar 安装,数据在两个实例之间进行异地复制,客户端通过单个静态 URL 的 DNS 记录定向到活跃集群。
此配置依赖于一个区域负载均衡器,该负载均衡器将请求路由到每个集群内的 Pulsar 代理池。这些代理实例也是无状态的,并且能够根据 topic 名称将传入请求路由到适当的 broker。
为了确保持续可用性,Pulsar 客户端被配置为使用单个静态 URL 连接到位于 Pulsar 代理前面的负载均衡器。DNS 记录被更新以指向“活跃”集群的区域负载均衡器,进而将所有流量路由到该集群中的 Pulsar broker。选择哪个代理实例并不重要,因为它们都执行完全相同的功能并根据 topic 名称将流量转发到适当的 broker。
要将客户端从“活跃”集群重定向到备用集群,客户端应用程序正在使用的 Pulsar 端点的 DNS 条目必须更新为指向备用集群的负载均衡器。
理论上,当 DNS 记录更新时,客户端将被重新路由到备用集群。但是,这种方法有两个缺点:
尽管这些问题都不是致命的,客户端最终将被重新路由到备用集群。但是,这些问题可能引发潜在延迟。长时间的延迟将丢掉 SLA,入站数据会开始备份并可能被丢弃。我们当然尽力避免任何时长的宕机。
我们很高兴地宣布,即将发布的 Apache Pulsar 2.10 版本中包含两种新的替代策略来避免集群故障转移的 DNS 更改方法导致的长时间延迟:
第一个故障转移策略是 AutoClusterFailover
,会在集群中断时自动从主集群切换到备用集群。此行为由监控主集群的探测任务控制。当它发现主集群失败超过 failoverDelayMs
设置的参数时,会将客户端连接切换到辅助集群。构建客户端的代码如下:
Map<String, Authentication> secondaryAuth =
new HashMap<String, Authentication>();
secondaryAuth.put("other", AuthenticationFactory.create(
"org.apache.pulsar.client.impl.auth.AuthenticationTls",
"tlsCertFile:/path/to/my-role.cert.pem," +
"tlsKeyFile:/path/to/my-role.key-pk8.pem"));
ServiceUrlProvider failover =
AutoClusterFailover.builder()
.primary("pulsar+ssl://broker.active.com:6651/")
.secondary(
Collections.singletonList("pulsar+ssl://broker.standby.com:6651"))
.failoverDelay(30, TimeUnit.SECONDS)
.switchBackDelay(60, TimeUnit.SECONDS)
.checkInterval(1000, TimeUnit.MILLISECONDS)
.secondaryAuthentication(secondaryAuth)
.build();
PulsarClient pulsarClient =
PulsarClient.builder()
.serviceUrlProvider(failover)
.authentication("org.apache.pulsar.client.impl.auth.AuthenticationTls",
"tlsCertFile:/path/to/my-role.cert.pem" +
"tlsKeyFile:/path/to/my-role.key-pk8.pem")
.build();
注意:辅助/备用集群的安全凭证在 java.util.Map 内提供,而主集群身份验证凭证包含在原始的 PulsarClientBuilder
中。在这种特殊情况下,TLS 证书即使适用于两个 Pulsar 集群也仍然需要分别单独提供。
切换到辅助集群后,AutoClusterFailover 将继续探测主集群。如果主集群恢复正常并在 switchBackDelayMs
期间保持活跃状态,它将切换回主集群。
另一种故障转移策略 ControlledClusterFailover
支持从主集群切换到备用集群,以响应外部服务发送的信号,管理员能够触发集群切换。
在这种特殊情况下,java.util.Map 中提供的安全凭证是供 Pulsar 客户端使用的,用于对 urlProvider
属性指定的服务进行身份验证,而不是备用 Pulsar 集群。构建客户端的代码如下:
Map<String, String> header = new HashMap<>();
header.put("clusterA", "<credentials-for-urlProvider>");
ServiceUrlProvider provider =
ControlledClusterFailover.builder()
.defaultServiceUrl("pulsar+ssl://broker.active.com:6651/")
.checkInterval(1, TimeUnit.MINUTES)
.urlProvider("http://failover-notification-service:8080/check")
.urlProviderHeader(header)
.build();
PulsarClient pulsarClient =
PulsarClient.builder()
.serviceUrlProvider(provider)
.build();
该客户端将每分钟查询一次 urlProvider
端点以检索它应该与之交互的 Pulsar 集群的服务 URL。Pulsar 客户端期望调用 urlProvider
端点返回一个 JSON 格式的消息,其中不仅包含备用集群连接 URL,还包含任何所需的身份验证相关参数。因此,在编写用于控制 Pulsar 集群故障转移的端点服务时需要注意 JSON 格式。示例如下:
{
"serviceUrl": "pulsar+ssl://standby:6651",
"tlsTrustCertsFilePath": "/security/ca.cert.pem",
"authPluginClassName":"org.apache.pulsar.client.impl.auth.AuthenticationTls",
"authParamsString": " \"tlsCertFile\": \"/security/client.cert.pem\"
\"tlsKeyFile\": \"/security/client-pk8.pem\" "
}
在持续大规模运行任何类型的软件系统中,故障是不可避免的。因此,制定应急计划来处理意外的区域故障非常重要。数据的异地复制固然是处理故障的一个重要手段,但远远不够。拥有能够自动检测并响应此类中断的故障感知客户端同样重要。截至目前发布版本,Apache Pulsar 只提供了跨地域机制。
最新版本的 Apache Pulsar 2.10 版本提供了两种不同类型的故障感知客户端。用户可以通过任一策略来确保应用程序不受区域性中断的影响。本博客详细介绍了每一个策略,并展示了代码。
最重要的是,这些客户端与现有的 Pulsar 客户端 100% 向后兼容。这意味着用户可以无忧地在现有代码库中替换现有客户端,而不会出现任何问题。目前,这些新类仅适用于 Java 客户端库,但社区将不断将其添加到其他客户端。