首页 > 大数据 > 数据挖掘 > 技术干货:Apache Pulsar 在移动云上的应用
随着 移动云的快速发展,越来越多的客户对云原生消息中间件提出了 更多需求,从而可以将主要的精力聚焦在应用程序上,大致有以下方面:
针对以上诉求,同时为了 统一公有云和私有云架构,移动云选择 Apache Pulsar 和 Kubernetes 来构建性能卓越、安全稳定、弹性伸缩、运维简便的云原生消息系统。
基于 Apache Pulsar 计算存储分离的云原生架构,我们将用于计算的 Kubernetes 集群和用于存储的 BookKeeper 集群物理分离,如下:

简单起见,这里我们以共享 Zookeeper 为例(可根据实例数量及实例资源大小,在 Kubernetes 中独享 Zookeeper 集群)以及直接使用 NodePort 的服务暴露方式将 Proxy 服务提供给客户端(也可根据需求选用合适的 LB 云服务或者使用开源的 LB,例如:Metallb: https://metallb.universe.tf/)。
🔧 怎么实现共享Bookie资源
我们期望 Kubernetes 中的多个 Pulsar 实例能够同时共享底层的 Bookie 存储资源,这样可以更快捷地实现计算存储分离。在 2.6.0 版本之前,Pulsar 实例在初始化元数据时,不支持设置 chroot 路径,并且只支持使用固定的 ledger 路径,不能使用已存在的 BookKeeper 集群。为此,我们通过优化 initialize-cluster-metadata 命令来支持设置 chroot 路径,以及在 broker 配置中添加 bookkeeperMetadataServiceUri 参数来指定 BookKeeper 集群的连接信息。
(详见:
🔧 服务怎样暴露
Pulsar 通过引入可选的 Proxy 组件来解决客户端不能直连 Broker 以及直连可能带来的管理开销问题,例如在云环境中或者在 Kubernetes 集群中运行时,
(参考 PIP-1:
https://github.com/apache/pulsar/wiki/PIP-1:-Pulsar-Proxy)
此外,Pulsar 官网提供了各个组件的 yaml 模板,
(参考 pulsar-helm-chart:
https://github.com/apache/pulsar-helm-chart/tree/master/charts/pulsar/templates)
这样可以很快捷地在 Kubernetes 集群上构建一个 Pulsar 集群,我们一开始采用了如下的架构:

期间有遇到一些小的问题,例如,Proxy 无法正常启动(在 Proxy 的 StatefulSet 中 initContainers 的条件是至少有一个 Broker 在运行),如下:
可以通过将 podManagementPolicy 策略由 Parallel 改为 OrderedReady(或者将 Proxy 的 initContainers 条件修改为指定 Broker 的副本数都在运行)来临时解决这个问题。这其实是 Proxy 的一个死锁问题导致的,由 Masahiro Sakamoto 发现并修复,
(详见 PR-7690:
https://github.com/apache/pulsar/pull/7690)
将在 2.6.2 和 2.7.0 版本中发布。此外,在不断的压测过程中,Proxy 会偶现内存持续增长,直至 Pod 重启(Error in writing to inbound channel. Closing java.nio.channels.ClosedChannelException: null),在做了一些验证和评估之后,我们决定通过其他方式来实现 Pod 内的 Broker 服务暴露,大致原因如下(供参考):
其中 Client 配置 multi-hosts 的实现方式是配置几个 Proxy Url,实际就只用这几个 Proxy,可简单通过以下步骤验证:
1.2个 broker: broker3:6650,broker4:6650
2.2个 proxy: proxy1:6650,proxy2:6650
3. 创建多个分区的topic用来生产消费(确保其 namespace 的 bundle 数量是 broker 的整数倍)
4. client url: 配置为 proxy1:6650 时,proxy1 上有相应的负载(TCP连接),proxy2没有负载
5. client url: 配置为 proxy1:6650, proxy2:6650 时,两个 proxy 上面均有负载
🔧 能否做到直连 Broker
由于 Broker 注册在 Zookeeper 中的服务地址是 podIP或者 pod 域名,Kubernetes 集群外的客户端是不能直接访问的,因此需要一种对外部客户端可见的服务地址。Pulsar 在 2.6.0 版本中引入 PIP-61: https://github.com/apache/pulsar/wiki/PIP-61:-Advertised-multiple-addresses 来支持广播多个 URL 监听地址,例如,可设置如下内/外监听:
这里我们使用 pod 域名(broker-0.broker-headless.pulsardev.svc.cluster.local.)作为 Broker 间的通讯地址,使用 Pod 所在的实际 Worker 节点 IP 和 预先分配的 NodePort 端口作为外部的通讯地址。
StatefulSet 中每个 Pod 的 DNS 格式为:statefulSetName-{0…N1}.serviceName.namespace.svc.cluster.local.
为了使集群外部的客户端能够直连 Broker 所在的 Pod,我们在 ConfigMap 中维护了 Worker 节点名字和 IP 的映射关系以及预先分配好的 NodePort 端口,这样在 StatefulSet 的 containers 启动脚本中,我们就可以通过命令 bin/apply-config-from-env.py conf/broker.conf; 将需要暴露的实际 Worker 节点 IP 和 预先分配的 NodePort 端口写到 Broker 配置 advertisedListeners 中,这样 Broker 启动后注册在 Zookeeper 中的外部通讯地址( external:pulsar://10.192.6.23:38068)对集群外部的客户端就是可见的了。其中,比较关键的一步是通过环境变量将 Pod 信息呈现给容器,
(参考 Pod-Info:
https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information)
例如,在 Broker 的 StatefulSet 的 yaml 文件中添加如下配置:
这样在 bin/apply-config-from-env.py 中,我们就可以通过上述信息根据 ConfigMap 中的节点和端口信息得到要暴露的实际 URL,写到 Broker 配置中,进而注册到 Zookeeper 中来发现实际的服务地址。
此外,PIP-61:
https://github.com/apache/pulsar/wiki/PIP-61:-Advertised-multiple-addresses
目前只支持 TCP 协议的服务发现,当需要在集群外部访问 HTTP 协议时,advertisedListeners 暂无法提供帮助。为了解决这个问题,我们在 Broker 中定制化了一个 webServiceExternalUrl 配置,然后通过上述类似的方法将需要暴露的实际 Worker 节点 IP 和 预先分配的 NodePort 端口(HTTP 协议)注册到 Zookeeper 中,这样对集群外部的 Admin Client 就是可见的了。在2.6.0 版本中,客户端在使用 advertisedListenerName 时,Broker 返回的地址是错误的,我们对此进行了修复。
(详见 PR-7737:
https://github.com/apache/pulsar/pull/7737)。
另外,我们在使用该特性的过程中修复了获取 bundle 时的空指针问题,以及支持客户端 Shell 指定监听名字,
(详见:
注:Service 在转发请求时,需要打上对应 Pod 节点名字的 selector 标签,例如:

以上是我们对于直连 Broker 的探索,相比 Proxy 的方案有如下优势:
🔧 怎样解决 IPv4/IPv6 双栈
对于移动云的场景,越来越多的用户期待云产品能够支持 IPv4/IPv6 双栈,以满足多种场景下的应用需求。
在 2.6.0 版本之前,Pulsar 只支持 IPv4 环境的部署,为此我们增加了对 IPv6 的支持。
(详见PR-5713:
https://github.com/apache/pulsar/pull/5713)。
此外,Kubernetes 从 1.16+ 版本增加了对 Pods 和 Services 的双栈支持。
(参考 Dual-Stack:
https://kubernetes.io/docs/concepts/services-networking/dual-stack/)
基于以上特性,我们只需要为 Kubernetes 中的 Pulsar 实例增加 IPv6 的 Service 即可(spec.ipFamily 设置为 IPv6)。然后,通过前面类似的服务暴露方案,将对集群外客户端可见的 IPv6 服务地址注册到 Zookeeper 中即可,如下:
值得注意的是,系统属性 java.net.preferIPv4Stack 默认是 false,在支持 IPv6 的双栈系统上,使用 Java 的 Socket 会默认通过底层 native 方法创建一个 IPv6 Socket(可以同时支持 IPv4 和 IPv6 主机通信),当 TCP 客户端的 java.net.preferIPv4Stack 属性设置为 true 时,如果要创建一个 host 为 IPv6 的 Socket,会抛出异常 java.net.SocketException: Protocol family unavailable。目前,Pulsar 客户端连接时优先使用 IPv4,当前的环境变量和脚本中,该属性都设置为了 true。
(详见PR-209:
https://github.com/apache/pulsar/pull/209)
因此,在支持 IPv6 的双栈时,需要将这些脚本中(即 bin 目录下的 pulsar,pulsar-admin,pulsar-client,pulsar-perf)的属性 java.net.preferIPv4Stack 设置为 false。其中,Broker 启动时会使用到 bin/pulsar 脚本,需要确保 Broker 启动后是同时监听 IPv4/IPv6 的端口,大致如下:
上述 Pod 里执行的结果中 ::: * 代表同时监听了 IPv4/IPv6,如果是0.0.0.0:*,则只支持 IPv4。期间在使用过程中,我们还修复和优化了一些问题,例如,客户端不支持 Mult-Hosts 的 IPv6 地址等。
(详见 PR-8120:
https://github.com/apache/pulsar/pull/8120)
🔧 如何简捷地管理实例
为了满足移动云用户对于管理简洁可控的需求,我们还定制化了一些管理上的功能,部分列举如下:
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69966433/viewspace-2745543/ ,如需转载,请注明出处,否则将追究法律责任。