最近不少技术群都在倒计时:COSCon‘25 同场活动 Pulsar Developer Day 还有 3 天就要开了。这个开发者日把主题定在“消息中间件创新实践”,我个人觉得比泛泛的技术布道更有看头。原因很简单:消息中间件这几年变化太快,快到“选型”本身已经变成一个长期决策,而不是上线前拍个脑袋就能定的事。与此同时,以 Pulsar 为代表的一批架构,正在把消息队列从“简单收发”推向“存储、计算、协议、云原生一体”的综合平台。这篇文章不打算做成某个演讲的预告复读,而是借着这个活动的主题,把我自己从接触 Pulsar、再到生产环境落地、再到排查各种奇怪问题的经验捋一遍。如果你本来打算参加这个开发者日,正好可以当成会前热身;如果你只是在朋友圈看到倒计时海报、想来了解一下 Pulsar 到底在解决什么问题,这篇文章也能给你一个比较完整的入门路径。1. 为什么消息中间件需要一场专门的开发者日1.1 一次线上告警引发的思考先讲一件真实发生过的事。某个电商大促周期前夜,我负责的系统突然收到大量消费积压告警,消费者的 CPU 并没有跑满,日志里也没有异常堆栈,但消息就是消费不掉。第一反应当然是加机器,结果扩容之后积压还在涨。后来查了很久才发现,问题出在消费者的 ack 超时上:消息处理要 90 秒,但 ack 超时被设成 30 秒,消息在客户端已经处理完、还没回报确认的时候就被判定为超时,重新投递到另一个消费者实例,形成“重复消费 + 重复计算”的死循环。这种问题在 RocketMQ、RabbitMQ、Kafka 里多多少少都存在,只是表现形态不同。消息中间件的核心从来不只是“把消息从 A 送到 B”,而是要在吞吐、延迟、顺序、可靠性、幂等等多个维度之间做权衡。任何一个小参数配置错误,都可能在流量高峰变成事故。而类似 ack 超时这种问题,恰恰是你在官方文档里很难找到、只有真正跑过生产才能积累下来的经验。一个专门聚焦“消息中间件创新实践”的开发者日,真正的价值就在这里:台上讲的不是概念,而是别人踩过的坑。1.2 消息中间件面临的四大生存挑战从更大的视角看,现在业务系统对消息中间件的要求,已经远远超过了十年前“发个通知、解个耦”的范畴。我把它概括为四个挑战。第一是高吞吐与低延迟并存。很多业务同时需要“每秒几十万条写入”和“毫秒级查询”,这对存储和网络的设计要求是完全不同的。传统消息队列把数据落在本地磁盘,靠批量刷盘来提升吞吐,但一旦某个副本磁盘故障,恢复成本巨大。第二是长时保留与流式回溯。现代数据分析场景要求消息不只被消费一次,还要能保留几天、几周甚至几个月,供离线任务重新读取。Kafka 的默认保留时间通常只有几天,因为它把消息和主题分区分段绑定在 broker 本地,长时间保留意味着成本不可控。第三是多租户与资源共享。在云原生环境下,一个团队可能要部署多套环境、多个业务线共用同一套集群。消息中间件必须支持在同一套集群内做租户隔离、权限控制、配额限制,否则各业务线互相影响,运维会彻底失控。第四是开源生态的融合。消息中间件再也不是孤立的“管道”,它需要对接数据库变更捕获、流处理框架、对象存储、数据湖、BI 分析等一堆周边系统。这一点对 Pulsar 这类后来者尤为重要,因为它从设计之初就把“存储”和“计算”分开来,天然适合扩展。这四个挑战,正是 Pulsar Developer Day 主题里“创新实践”四个字的底气所在。Pulsar 不是简单地做一个“更快更强的 Kafka”,而是在架构层面回答了上述问题。这也是我最初关注它的原因。2. 拆开 Pulsar 的底层设计:它为什么敢说创新2.1 存储计算分离到底指什么很多消息队列的架构图里,broker 既是接收消息的入口,也是持久化消息的存储节点。Kafka 就是这种设计的代表:主题的分区分布在多个 broker 上,每个 broker 既要处理请求,又要管理本地磁盘上的日志段。看起来简单,但有一个隐性代价——扩缩容非常麻烦。你增加一个 broker 时,不会自动获得数据分布;你得做分区迁移,而在迁移过程中,集群的可用性和性能都会受影响。Pulsar 的做法完全不同。它把服务层(Broker)和存储层(Apache BookKeeper 集群)彻底拆开。Broker 只负责接收请求、管理游标、投递消息,本身不存数据;所有消息数据落在 BookKeeper 的 Bookie 节点上。这样 Broker 几乎可以做到无状态,想扩容就直接加机器,完全没有数据迁移的负担。你可以理解成餐厅里“前台点单”和“后厨做菜”彻底分开,客流大了加前台服务员就能提高翻台率,不用重新装修后厨。这套架构带来的第一个直观好处是存储扩容极其灵活。如果磁盘不够,加 Bookie 节点就够了,数据会自动在 BookKeeper 层做 rebalance;Broker 层的扩容则完全是为了横向提升吞吐和连接数。对于云原生环境来说,这种“计算和存储独立伸缩”的能力几乎是刚需,因为流量高峰和存储增长往往是不同步的。2.2 Segment 与 Ledger 设计带来的实际好处Pulsar 的底层存储不是直接写一大段连续日志,而是把每个 Topic 按固定大小切成分段(Segment),默认 128MB 左右一个。每段数据会封装到一个 Ledger 里,均匀分布到多个 Bookie 上,并按照配置写多个副本。当前段写满后,自动滚动到新的段。这个设计初看只是工程细节,但它的价值体现在四个方面。一是热点均衡。Kafka 的热点问题很常见:某个分区很大,流量都砸在同一块磁盘上。Pulsar 的 Segment 天然分散在多个 Bookie 上,任何分区的写入和读取都会分散到集群所有存储节点,单个 Topic 很难打爆某块盘。二是恢复效率高。BookKeeper 的 Ledger 写入机制类似多副本日志,且每个 Ledger 有独立的写入状态。某个存储节点宕机时,上层只要确认 Ledger 有足够的 ack,就能选择其他副本继续读,不需要像 Kafka 那样把整个副本做全量复制。三是支持分层存储。当 Segment 超过一定时间或大小,Pulsar 可以把它从 Bookie 滚动到对象存储(比如 S3 或兼容 S3 的 MinIO)。这样消息可以保留更久,而本地存储成本几乎不增长。离线回溯历史消息时再动态拉回来。这个能力对“消息再来一次”的场景特别实用。四是多租户的实现更干净。Pulsar 的 Topic 在逻辑上属于某个 Namespace,Namespace 又属于某个 Tenant。底层存储可以在租户之间共享,但每个租户都有独立的权限、配额和统计。这比用物理集群隔离节省太多成本。2.3 Pulsar 与其他中间件的选型对照很多人在选型时纠结于 Pulsar 和 Kafka、RocketMQ 的差异。这里我不做全面对比,只列出几个影响到落地的关键维度,方便你结合自己的场景评估。维度KafkaRabbitMQRocketMQPulsar存储与计算耦合,本地磁盘内存 + 磁盘镜像文件顺序写分离,Broker 无状态 + BookKeeper 存储扩展方式分区迁移,运维较重集群镜像模式主从/多副本动态加 Bookie/Broker,节点间自动均衡消息保留按时间/大小清理内存为主,有限按时间/大小,半文件化默认保留到消费确认,可无限 + 分层存储多租户需要多集群逻辑划分支持相对弱有 Tags/隔离但仍有限Tenant + Namespace 原生多租户消费模型分区内有序队列/路由队列/广播多种订阅模式,同一 Topic 可切换跨地域复制MirrorMaker,成本高联邦插件主从复制原生 Geo-Replication,异步多写流处理Kafka Streams 强弱有 API 但生态少Pulsar Functions / Pulsar SQL 一体化这张表不是要证明 Pulsar 一定更强,而是想说明它的架构取舍更适合哪些场景:多环境共享集群、海量 Topic、长时保留、跨地域容灾需求多,以及团队愿意接受多一套 BookKeeper 运维的学习成本。反过来,如果你的团队对 Kafka 已经非常熟悉,业务规模也不大,硬切 Pulsar 未必划算。选型永远是“用合适的技术解决合适的问题”,而不是追逐新名词。3. 跑一个生产级 Pulsar 集群:从部署到调优3.1 最小可用的生产级拓扑很多初学者会把 Pulsar standalone 当成生产环境来用,这是第一个坑。standalone 模式只适合本地验证 API。生产环境的最小可靠拓扑,至少是:3 个 ZooKeeper 节点、3 个 Bookie 节点、2 个以上 Broker 节点。如果你希望通过同一套集群承载多个业务线,建议再加一组监控组件(Prometheus 算子或手动采集),因为 Pulsar 的磁盘、Backlog、Bookie 状态这些指标非常依赖持续观测。如果你在 Kubernetes 里部署,强烈推荐使用 Pulsar Operator 或 Helm Chart,它会把 ZooKeeper、Bookie、Broker、Proxy 这几个组件拆成独立的 StatefulSet,并处理好存储卷的 PVC。我见过很多人在 K8s 里把 Bookie 直接放在普通 Pod 上,结果节点调度后数据全丢,这是不上 StatefulSet 的最常见事故。3.2 必须理解的 broker.conf 关键参数Pulsar 的参数很多,但真正影响落地的核心参数并不算多。下面列几个我每次部署都会重点看一遍的配置,并解释为什么它们值得关注。配置项默认值生产建议为什么managedLedgerDefaultEnsembleSize23每个 Segment 的副本分散到几个 Bookie,至少要能容忍一个节点故障managedLedgerDefaultWriteQuorum23写入需要确认几个副本,3 副本才能保证任意一台故障不丢数据managedLedgerDefaultAckQuorum12客户端写入成功后至少有几个副本已落盘确认,提高可靠性managedLedgerDefaultMarkDeleteRateLimit0.1根据消费频率上调限制游标推进速率,太慢会导致已消费消息无法及时释放存储maxMessageSize5242880 (5MB)按业务调整默认限制单条消息 5MB,发大消息会直接报错tlsEnabledfalsetrue生产环境必须开启,否则消息明文传输brokerDeduplicationEnabledfalsetrue开启消息去重,配合生产端幂等,消费端压力更小其中副本参数是最容易被忽略的。很多教程里用本机集群演示,把副本数都设成 1,看起来一切正常;等真正部署到 3 节点集群时忘了改,结果一个 Bookie 宕机导致数据不可读。这里的核心逻辑是:写 Quorum 决定一条消息至少要写成功几份,ack Quorum 决定客户端收到成功响应前最少需要几份确认。如果 ack=1,也就是只有一份副本落盘就返回成功,那么另一副本还没写完时机器宕机,消息就丢了。生产至少要用 3 份写入、2 份确认,这是可靠性和性能之间比较稳妥的平衡点。3.3 快速验证:从零发一条消息部署完成后,最简单的验证方式是用 Pulsar 自带的命令行工具。假设你已经有一个名为coscon-demo的租户,下面这组操作可以帮你确认集群链路是否正常。创建一个命名空间和持久化 Topic:bin/pulsar-admin namespaces create coscon-demo/event bin/pulsar-admin topics create persistent://coscon-demo/event/order-created用 CLI 快速生产和消费:bin/pulsar-client produce persistent://coscon-demo/event/order-created \ --messages "hello-pulsar" bin/pulsar-client consume persistent://coscon-demo/event/order-created \ --subscription-name sub-demo \ --num-messages 1如果这一步通了,再用 Java 或 Python 客户端写一个最小 Demo。我习惯先验证四个点:生产端是否连接同一个 Broker 地址、消息是否落盘到 Bookie、消费者是否能从最新位置收到消息、以及重启消费者后是否能从之前的位置继续消费。这四点都通过,集群才算是真的可用,而不是仅仅“起来了”。3.4 日常扩容的正确姿势扩容这件事,Pulsar 比 Kafka 简单,但也不是零操作。Broker 扩容最简单:加节点、更新连接地址、把流量负载均衡过去就行。Bookie 扩容稍微需要注意:新增 Bookie 后,已有段不会自动搬过去,只影响后续新建的段。如果你希望数据重新均衡,需要执行 Bookie 的 rebalance 命令,它会迁移部分 Ledger 的副本到新节点。很多人误解 Pulsar 扩容是“完全无感”,其实 rebalance 期间也会消耗带宽和磁盘 IO,最好在业务低峰期做。我在一次大促前扩容 Bookie,由于没有控制 rebalance 速率,跨机房复制带宽被打满,导致消息跨地域同步延迟飙升。后来才学会在 rebalance 时设置maxConcurrentIngeLeadingLedgerRebuilds之类的限制参数,把速度降下来,业务稳定性立刻恢复。4. 生产实践中容易踩的坑4.1 消费者“假死”的真相:ack 超时和重投递回到开头那个线上事故。Pulsar 消费者客户端里,ackTimeout如果设置得太短,而业务代码处理消息需要更长时间,就会产生重复投递。默认情况下 Pulsar 不启用自动 ack 超时,但很多人会主动开启,目的是“防止消费者卡死”。这个初衷没问题,问题是没想清楚自己的业务耗时。正确的做法是:如果你预计单条消息最长处理 60 秒,就把 ackTimeout 设到至少 2 倍以上;更推荐的做法是关闭自动 ack 超时,依靠negativeAckRedeliveryBackoff和消息重试策略来手动控制。Pulsar 客户端支持负向确认(negative ack),当你发现这条消息处理不了时主动 requeue,而不是等超时自动重投。这个机制其实更精准。另外要注意,生产者在开启了去重功能后,重发消息会按消息 ID 去重;但消费者端并没有全局去重能力。也就是说,端到端有且仅有一次投递,还是不能只靠中间件保证,消费端仍然要按照业务主键做幂等表或去重缓存。这个思路对所有消息队列都一样,算是中间件使用者的“基础必修课”。4.2 积压问题别急着扩消费者消息积压一上涨,大家第一反应就是加消费者实例。但在 Pulsar 里,如果消费积压出现在 Shared 订阅上,加实例确实通常会有效;可如果是 Exclusive 或 Failover 订阅,一个 Topic 同一时刻只有一个消费者能接收消息,加一百个实例也白搭。更隐蔽的情况是,Topic 的消费者数量已经超过分区数也不解决问题。Kafka 里对比分区数来均衡,Pulsar 里虽然支持动态扩展消费者数量,但实际吞吐还受 Bookie 读取能力、Broker 网络以及消费者本地处理能力限制。我处理过一次积压,怎么加消费者都无效,最后查到是消费者实例全部绑定在同一台宿主机上,网络带宽成了瓶颈。所以排查积压应该按这个顺序:先看订阅类型是否允许多消费者并行,再看每个实例的资源水位,最后才考虑加实例。积压的另一个常见原因是没有正确设置消息保留策略。如果 Namespace 配置为“未确认消息也长期保留”,那么消费完成后消息并不会及时清理,磁盘占用持续上升,最终 Storage 指标飙升,甚至触发 Bookie 写入失败——这时候表现也是“消息堆积”,但根因根本不在消费端。所以日常要同时观察backlog和storageSize两个指标,别混为一谈。4.3 多租户权限模型实战Pulsar 的多租户是一个大优势,但很多团队把它当成“翻不过去的坎”。原因很简单:权限模型虽然是三个层次(Superuser / Tenant / Namespace),但要真正落地,需要和内部账号体系打通。我的建议是从第一天就养成“非超级用户操作”的原则。日常开发者使用只有某个 Namespace 读写权限的账号,管理员和流水线账号另配。即使在内部开发环境,我也不推荐所有人用同一个 admin token,否则出问题根本不知道是谁改了配置。在给某个业务方开权限时,通常会做这几件事:# 创建租户 bin/pulsar-admin tenants create pay-center # 在租户下创建命名空间 bin/pulsar-admin namespaces create pay-center/order-prod # 授予某个 client 角色对 namespace 的生产和消费权限 bin/pulsar-admin namespaces grant-permission \ --role app-order-service \ --actions produce,consume \ pay-center/order-prod权限和 Topi 数量配合起来效果更明显:一个业务用一个 Namespace,加机器、换客户端都不互相影响。如果遇到团队成员需要一个“临时订阅”,我宁可让他们在共享 Namespace 里创建一个专用订阅,也不要放开所有人的 admin 权限。方便和安全之间的平衡点,其实就在这里。5. 创新实践:Pulsar 不只是消息队列5.1 用 Functions 和 IO Connectors 做轻量流处理Pulsar 被讨论很多的创新点,是它把消息队列和流处理两个概念放到了一套模型里。Pulsar Functions 允许你直接写一个简单函数,部署到集群里,让消息在传递过程中被过滤、转换、聚合。它适合的场景是“几十行代码就能搞定的处理逻辑”,比如日志清洗、字段补全、消息路由等。我见过一个典型的落地案例:某个团队需要把前端埋点数据里的用户标识统一替换为匿名 ID,原来做法是写一个 Flink 任务,重且复杂。后来改成 Pulsar Function,直接订阅原始 Topic,处理完写到另一个 Topic,一个 JAR 包搞定。这种场景不需要引入单独的流处理集群,运维负担小,出错的概率也低。Pulsar IO 则解决了“外部系统接入”的问题。通过 Source/Sink Connector,可以直接把数据库的 CDC 变更、Kafka 数据、S3 文件等导入到 Pulsar,也可以把消息导出到 Elasticsearch、MySQL、HDFS 等。对于不想为每个对接目标写专用适配器的团队,这是一条很务实的快车道。5.2 多协议接入:让存量系统低门槛迁移Pulsar 的另一个“创新实践”方向是兼容多协议。通过 KoP(Kafka-on-Pulsar)、MoP(MQTT-on-Pulsar)、AMQP-on-Pulsar 这类协议处理器,一个 Pulsar 集群可以直接接受 Kafka 客户端、MQTT 设备、AMQP 客户端连接。这对存量系统太重要了。很多公司不是不想迁到 Pulsar,而是 Kafka 客户端已经在几十个服务里跑着,改造量大、风险高。使用 KoP 后,客户端代码几乎不用改,只需要把 broker 地址切换到 Pulsar 的 KoP 端口,就完成了底层替换。当然,协议处理层的性能会有一些损耗,但换来的是存储计算分离、多租户、长保留这些能力。如果团队正在做“通过换消息中间件降低长期运维成本”的规划,这种渐进式路径远比“一把梭重写”稳妥。5.3 跨地域复制与云边协同跨地域复制是 Pulsar 比较有代表性的原生能力,本质上是在多个集群之间异步同步消息。它的好处是,你可以做到“两地三中心”级别的容灾,而不只是简单的主从切换。实际落地时,我建议先分清楚两种模式:主动复制的两个集群都接收生产者和消费者写入,适合灾备双活;被动复制的主集群负责生产,备集群只消费,适合读多写少的场景。Pulsar 支持的是一对一、双向、星型甚至多副本的网络拓扑,配置上选择一个 Namespace 启用 Geo-Replication 即可。跨机房复制最常见的坑是延迟放大。每次同步都要经过网络,两个地点距离越远延迟越高。不过你可以通过调整复制消息的持久级别来降低耗时,也可以只在核心业务 Topic 上开启复制,而不是整个 Namespace 全部开启。我见过有人因为图省事,把包含日志流和业务流的 Namespace 一键开了双向复制,结果跨机房带宽直接被打满。把需要复制的业务面和数据量分开规划,才能发挥这个功能的真正价值。6. 参会前该做哪些准备:倒计时 3 天的正确打开方式6.1 这次活动适合谁听Pulsar Developer Day 并不是面向所有人的讲座,它更适合这几类人:正在做消息中间件选型或二次评估的后端开发;维护着多套 Kafka 集群、想降低运维复杂度的 SRE;数据平台工程师,尤其是接触流处理和数据湖的;以及那些已经上了 Pulsar、但希望进一步优化参数和排查线上问题的人。如果你只是想要一个“把消息发出去”的示例,这个开发者日的内容可能比你预期的更深,但同样能帮你建立整体架构认知。我建议你在现场听分享时,把重点放在“实践案例”上,而不是放大 PPT 上的架构图。6.2 现场值得重点关注的话题很多人参会带了个笔记本,结果发现自己记录的都是大概念。我的经验是,提前先给自己列一张“问题清单”,到现场闻讯。比如:你所在场景的 Topic 规模、单 Topic 流量和 Pulsar 官方/社区案例差多少?他们是怎么处理消费端幂等的?是依赖中间件能力,还是业务去重?有没有小规模集群的部署经验?资源有限的情况下,副本参数该降到多少?遇到消费积压和存储增长同时出现时,他们优先排查哪些指标?做跨地域复制时,网络延迟和成本有没有量化数据?这些问题比“Pulsar 和 Kafka 哪个好”更有价值,因为后者只要一搜就能找到大量观点,前者需要真正跑过生产的人才能给出可信答案。6.3 带着运行数据去交流如果手头已经有 Pulsar 集群或概念项目,我会建议你在会前把集群的一些基础运行数据导出,比如 Topic 总量、每日消息条数、平均消息大小、峰值吞吐、Backlog 分布、存储使用量。带着这些数据去听分享、提问,效果完全不同。之前我去参加类似技术活动时,最喜欢找那些“踩过生产环境坑”的分享者聊天。很多人会告诉你,他们用的副本数是多少、为什么不是默认值;遇到批量消费时怎么调整批处理参数;以及分层存储落地时对象存储的访问延迟对消费的影响有多大。这些信息通常不在官方文档里,只有面对面交流才能问出来。如果你确实没法到现场,我建议活动结束后第一时间看社区里的分享视频和 PPT,重点选择和你业务场景最接近的那几个复盘。倒计时 3 天,要做的不是把 Pulsar 官方文档从头啃一遍,而是先梳理自己手上系统的现状、把上面那张问题清单写好,然后再带着具体需求去摄取信息。写在最后我在实际跑消息中间件这几年的最大体会是:再好的架构和参数,都抵不过“业务场景理解到位”。很多人学 Pulsar 时喜欢先看特性列表,然后照着部署一遍,最后发现线上还是要靠监控和排查来成长。Pulsar Developer Day 这样的活动,价值不在于告诉你哪款中间件最牛,而在于把一堆真实跑过的实践放在你面前,供你对照自己的系统做判断。另外一个很实用的小技巧:每次参加完类似开发者日,都别急着把笔记归档。把你记录到的问题和案例带回工位,对照自己的配置和指标做一遍“体检”,哪怕只是修改一个 ackTimeout、调整一个副本参数,也比收藏十篇技术文章更有意义。距离活动正式开场还有 3 天,如果你本来就在调研消息中间件方向,这 3 天正好用来把自己手上的系统现状梳理一遍;把问题准备充分了,现场每一分钟都能榨出远超票价的干货。
最近不少技术群都在倒计时:COSCon‘25 同场活动 Pulsar Developer Day 还有 3 天就要开了。这个开发者日把主题定在“消息中间件创新实践”,我个人觉得比泛泛的技术布道更有看头。原因很简单:消息中间件这几年变化太快,快到“选型”本身已经变成一个长期决策,而不是上线前拍个脑袋就能定的事。与此同时,以 Pulsar 为代表的一批架构,正在把消息队列从“简单收发”推向“存储、计算、协议、云原生一体”的综合平台。这篇文章不打算做成某个演讲的预告复读,而是借着这个活动的主题,把我自己从接触 Pulsar、再到生产环境落地、再到排查各种奇怪问题的经验捋一遍。如果你本来打算参加这个开发者日,正好可以当成会前热身;如果你只是在朋友圈看到倒计时海报、想来了解一下 Pulsar 到底在解决什么问题,这篇文章也能给你一个比较完整的入门路径。1. 为什么消息中间件需要一场专门的开发者日1.1 一次线上告警引发的思考先讲一件真实发生过的事。某个电商大促周期前夜,我负责的系统突然收到大量消费积压告警,消费者的 CPU 并没有跑满,日志里也没有异常堆栈,但消息就是消费不掉。第一反应当然是加机器,结果扩容之后积压还在涨。后来查了很久才发现,问题出在消费者的 ack 超时上:消息处理要 90 秒,但 ack 超时被设成 30 秒,消息在客户端已经处理完、还没回报确认的时候就被判定为超时,重新投递到另一个消费者实例,形成“重复消费 + 重复计算”的死循环。这种问题在 RocketMQ、RabbitMQ、Kafka 里多多少少都存在,只是表现形态不同。消息中间件的核心从来不只是“把消息从 A 送到 B”,而是要在吞吐、延迟、顺序、可靠性、幂等等多个维度之间做权衡。任何一个小参数配置错误,都可能在流量高峰变成事故。而类似 ack 超时这种问题,恰恰是你在官方文档里很难找到、只有真正跑过生产才能积累下来的经验。一个专门聚焦“消息中间件创新实践”的开发者日,真正的价值就在这里:台上讲的不是概念,而是别人踩过的坑。1.2 消息中间件面临的四大生存挑战从更大的视角看,现在业务系统对消息中间件的要求,已经远远超过了十年前“发个通知、解个耦”的范畴。我把它概括为四个挑战。第一是高吞吐与低延迟并存。很多业务同时需要“每秒几十万条写入”和“毫秒级查询”,这对存储和网络的设计要求是完全不同的。传统消息队列把数据落在本地磁盘,靠批量刷盘来提升吞吐,但一旦某个副本磁盘故障,恢复成本巨大。第二是长时保留与流式回溯。现代数据分析场景要求消息不只被消费一次,还要能保留几天、几周甚至几个月,供离线任务重新读取。Kafka 的默认保留时间通常只有几天,因为它把消息和主题分区分段绑定在 broker 本地,长时间保留意味着成本不可控。第三是多租户与资源共享。在云原生环境下,一个团队可能要部署多套环境、多个业务线共用同一套集群。消息中间件必须支持在同一套集群内做租户隔离、权限控制、配额限制,否则各业务线互相影响,运维会彻底失控。第四是开源生态的融合。消息中间件再也不是孤立的“管道”,它需要对接数据库变更捕获、流处理框架、对象存储、数据湖、BI 分析等一堆周边系统。这一点对 Pulsar 这类后来者尤为重要,因为它从设计之初就把“存储”和“计算”分开来,天然适合扩展。这四个挑战,正是 Pulsar Developer Day 主题里“创新实践”四个字的底气所在。Pulsar 不是简单地做一个“更快更强的 Kafka”,而是在架构层面回答了上述问题。这也是我最初关注它的原因。2. 拆开 Pulsar 的底层设计:它为什么敢说创新2.1 存储计算分离到底指什么很多消息队列的架构图里,broker 既是接收消息的入口,也是持久化消息的存储节点。Kafka 就是这种设计的代表:主题的分区分布在多个 broker 上,每个 broker 既要处理请求,又要管理本地磁盘上的日志段。看起来简单,但有一个隐性代价——扩缩容非常麻烦。你增加一个 broker 时,不会自动获得数据分布;你得做分区迁移,而在迁移过程中,集群的可用性和性能都会受影响。Pulsar 的做法完全不同。它把服务层(Broker)和存储层(Apache BookKeeper 集群)彻底拆开。Broker 只负责接收请求、管理游标、投递消息,本身不存数据;所有消息数据落在 BookKeeper 的 Bookie 节点上。这样 Broker 几乎可以做到无状态,想扩容就直接加机器,完全没有数据迁移的负担。你可以理解成餐厅里“前台点单”和“后厨做菜”彻底分开,客流大了加前台服务员就能提高翻台率,不用重新装修后厨。这套架构带来的第一个直观好处是存储扩容极其灵活。如果磁盘不够,加 Bookie 节点就够了,数据会自动在 BookKeeper 层做 rebalance;Broker 层的扩容则完全是为了横向提升吞吐和连接数。对于云原生环境来说,这种“计算和存储独立伸缩”的能力几乎是刚需,因为流量高峰和存储增长往往是不同步的。2.2 Segment 与 Ledger 设计带来的实际好处Pulsar 的底层存储不是直接写一大段连续日志,而是把每个 Topic 按固定大小切成分段(Segment),默认 128MB 左右一个。每段数据会封装到一个 Ledger 里,均匀分布到多个 Bookie 上,并按照配置写多个副本。当前段写满后,自动滚动到新的段。这个设计初看只是工程细节,但它的价值体现在四个方面。一是热点均衡。Kafka 的热点问题很常见:某个分区很大,流量都砸在同一块磁盘上。Pulsar 的 Segment 天然分散在多个 Bookie 上,任何分区的写入和读取都会分散到集群所有存储节点,单个 Topic 很难打爆某块盘。二是恢复效率高。BookKeeper 的 Ledger 写入机制类似多副本日志,且每个 Ledger 有独立的写入状态。某个存储节点宕机时,上层只要确认 Ledger 有足够的 ack,就能选择其他副本继续读,不需要像 Kafka 那样把整个副本做全量复制。三是支持分层存储。当 Segment 超过一定时间或大小,Pulsar 可以把它从 Bookie 滚动到对象存储(比如 S3 或兼容 S3 的 MinIO)。这样消息可以保留更久,而本地存储成本几乎不增长。离线回溯历史消息时再动态拉回来。这个能力对“消息再来一次”的场景特别实用。四是多租户的实现更干净。Pulsar 的 Topic 在逻辑上属于某个 Namespace,Namespace 又属于某个 Tenant。底层存储可以在租户之间共享,但每个租户都有独立的权限、配额和统计。这比用物理集群隔离节省太多成本。2.3 Pulsar 与其他中间件的选型对照很多人在选型时纠结于 Pulsar 和 Kafka、RocketMQ 的差异。这里我不做全面对比,只列出几个影响到落地的关键维度,方便你结合自己的场景评估。维度KafkaRabbitMQRocketMQPulsar存储与计算耦合,本地磁盘内存 + 磁盘镜像文件顺序写分离,Broker 无状态 + BookKeeper 存储扩展方式分区迁移,运维较重集群镜像模式主从/多副本动态加 Bookie/Broker,节点间自动均衡消息保留按时间/大小清理内存为主,有限按时间/大小,半文件化默认保留到消费确认,可无限 + 分层存储多租户需要多集群逻辑划分支持相对弱有 Tags/隔离但仍有限Tenant + Namespace 原生多租户消费模型分区内有序队列/路由队列/广播多种订阅模式,同一 Topic 可切换跨地域复制MirrorMaker,成本高联邦插件主从复制原生 Geo-Replication,异步多写流处理Kafka Streams 强弱有 API 但生态少Pulsar Functions / Pulsar SQL 一体化这张表不是要证明 Pulsar 一定更强,而是想说明它的架构取舍更适合哪些场景:多环境共享集群、海量 Topic、长时保留、跨地域容灾需求多,以及团队愿意接受多一套 BookKeeper 运维的学习成本。反过来,如果你的团队对 Kafka 已经非常熟悉,业务规模也不大,硬切 Pulsar 未必划算。选型永远是“用合适的技术解决合适的问题”,而不是追逐新名词。3. 跑一个生产级 Pulsar 集群:从部署到调优3.1 最小可用的生产级拓扑很多初学者会把 Pulsar standalone 当成生产环境来用,这是第一个坑。standalone 模式只适合本地验证 API。生产环境的最小可靠拓扑,至少是:3 个 ZooKeeper 节点、3 个 Bookie 节点、2 个以上 Broker 节点。如果你希望通过同一套集群承载多个业务线,建议再加一组监控组件(Prometheus 算子或手动采集),因为 Pulsar 的磁盘、Backlog、Bookie 状态这些指标非常依赖持续观测。如果你在 Kubernetes 里部署,强烈推荐使用 Pulsar Operator 或 Helm Chart,它会把 ZooKeeper、Bookie、Broker、Proxy 这几个组件拆成独立的 StatefulSet,并处理好存储卷的 PVC。我见过很多人在 K8s 里把 Bookie 直接放在普通 Pod 上,结果节点调度后数据全丢,这是不上 StatefulSet 的最常见事故。3.2 必须理解的 broker.conf 关键参数Pulsar 的参数很多,但真正影响落地的核心参数并不算多。下面列几个我每次部署都会重点看一遍的配置,并解释为什么它们值得关注。配置项默认值生产建议为什么managedLedgerDefaultEnsembleSize23每个 Segment 的副本分散到几个 Bookie,至少要能容忍一个节点故障managedLedgerDefaultWriteQuorum23写入需要确认几个副本,3 副本才能保证任意一台故障不丢数据managedLedgerDefaultAckQuorum12客户端写入成功后至少有几个副本已落盘确认,提高可靠性managedLedgerDefaultMarkDeleteRateLimit0.1根据消费频率上调限制游标推进速率,太慢会导致已消费消息无法及时释放存储maxMessageSize5242880 (5MB)按业务调整默认限制单条消息 5MB,发大消息会直接报错tlsEnabledfalsetrue生产环境必须开启,否则消息明文传输brokerDeduplicationEnabledfalsetrue开启消息去重,配合生产端幂等,消费端压力更小其中副本参数是最容易被忽略的。很多教程里用本机集群演示,把副本数都设成 1,看起来一切正常;等真正部署到 3 节点集群时忘了改,结果一个 Bookie 宕机导致数据不可读。这里的核心逻辑是:写 Quorum 决定一条消息至少要写成功几份,ack Quorum 决定客户端收到成功响应前最少需要几份确认。如果 ack=1,也就是只有一份副本落盘就返回成功,那么另一副本还没写完时机器宕机,消息就丢了。生产至少要用 3 份写入、2 份确认,这是可靠性和性能之间比较稳妥的平衡点。3.3 快速验证:从零发一条消息部署完成后,最简单的验证方式是用 Pulsar 自带的命令行工具。假设你已经有一个名为coscon-demo的租户,下面这组操作可以帮你确认集群链路是否正常。创建一个命名空间和持久化 Topic:bin/pulsar-admin namespaces create coscon-demo/event bin/pulsar-admin topics create persistent://coscon-demo/event/order-created用 CLI 快速生产和消费:bin/pulsar-client produce persistent://coscon-demo/event/order-created \ --messages "hello-pulsar" bin/pulsar-client consume persistent://coscon-demo/event/order-created \ --subscription-name sub-demo \ --num-messages 1如果这一步通了,再用 Java 或 Python 客户端写一个最小 Demo。我习惯先验证四个点:生产端是否连接同一个 Broker 地址、消息是否落盘到 Bookie、消费者是否能从最新位置收到消息、以及重启消费者后是否能从之前的位置继续消费。这四点都通过,集群才算是真的可用,而不是仅仅“起来了”。3.4 日常扩容的正确姿势扩容这件事,Pulsar 比 Kafka 简单,但也不是零操作。Broker 扩容最简单:加节点、更新连接地址、把流量负载均衡过去就行。Bookie 扩容稍微需要注意:新增 Bookie 后,已有段不会自动搬过去,只影响后续新建的段。如果你希望数据重新均衡,需要执行 Bookie 的 rebalance 命令,它会迁移部分 Ledger 的副本到新节点。很多人误解 Pulsar 扩容是“完全无感”,其实 rebalance 期间也会消耗带宽和磁盘 IO,最好在业务低峰期做。我在一次大促前扩容 Bookie,由于没有控制 rebalance 速率,跨机房复制带宽被打满,导致消息跨地域同步延迟飙升。后来才学会在 rebalance 时设置maxConcurrentIngeLeadingLedgerRebuilds之类的限制参数,把速度降下来,业务稳定性立刻恢复。4. 生产实践中容易踩的坑4.1 消费者“假死”的真相:ack 超时和重投递回到开头那个线上事故。Pulsar 消费者客户端里,ackTimeout如果设置得太短,而业务代码处理消息需要更长时间,就会产生重复投递。默认情况下 Pulsar 不启用自动 ack 超时,但很多人会主动开启,目的是“防止消费者卡死”。这个初衷没问题,问题是没想清楚自己的业务耗时。正确的做法是:如果你预计单条消息最长处理 60 秒,就把 ackTimeout 设到至少 2 倍以上;更推荐的做法是关闭自动 ack 超时,依靠negativeAckRedeliveryBackoff和消息重试策略来手动控制。Pulsar 客户端支持负向确认(negative ack),当你发现这条消息处理不了时主动 requeue,而不是等超时自动重投。这个机制其实更精准。另外要注意,生产者在开启了去重功能后,重发消息会按消息 ID 去重;但消费者端并没有全局去重能力。也就是说,端到端有且仅有一次投递,还是不能只靠中间件保证,消费端仍然要按照业务主键做幂等表或去重缓存。这个思路对所有消息队列都一样,算是中间件使用者的“基础必修课”。4.2 积压问题别急着扩消费者消息积压一上涨,大家第一反应就是加消费者实例。但在 Pulsar 里,如果消费积压出现在 Shared 订阅上,加实例确实通常会有效;可如果是 Exclusive 或 Failover 订阅,一个 Topic 同一时刻只有一个消费者能接收消息,加一百个实例也白搭。更隐蔽的情况是,Topic 的消费者数量已经超过分区数也不解决问题。Kafka 里对比分区数来均衡,Pulsar 里虽然支持动态扩展消费者数量,但实际吞吐还受 Bookie 读取能力、Broker 网络以及消费者本地处理能力限制。我处理过一次积压,怎么加消费者都无效,最后查到是消费者实例全部绑定在同一台宿主机上,网络带宽成了瓶颈。所以排查积压应该按这个顺序:先看订阅类型是否允许多消费者并行,再看每个实例的资源水位,最后才考虑加实例。积压的另一个常见原因是没有正确设置消息保留策略。如果 Namespace 配置为“未确认消息也长期保留”,那么消费完成后消息并不会及时清理,磁盘占用持续上升,最终 Storage 指标飙升,甚至触发 Bookie 写入失败——这时候表现也是“消息堆积”,但根因根本不在消费端。所以日常要同时观察backlog和storageSize两个指标,别混为一谈。4.3 多租户权限模型实战Pulsar 的多租户是一个大优势,但很多团队把它当成“翻不过去的坎”。原因很简单:权限模型虽然是三个层次(Superuser / Tenant / Namespace),但要真正落地,需要和内部账号体系打通。我的建议是从第一天就养成“非超级用户操作”的原则。日常开发者使用只有某个 Namespace 读写权限的账号,管理员和流水线账号另配。即使在内部开发环境,我也不推荐所有人用同一个 admin token,否则出问题根本不知道是谁改了配置。在给某个业务方开权限时,通常会做这几件事:# 创建租户 bin/pulsar-admin tenants create pay-center # 在租户下创建命名空间 bin/pulsar-admin namespaces create pay-center/order-prod # 授予某个 client 角色对 namespace 的生产和消费权限 bin/pulsar-admin namespaces grant-permission \ --role app-order-service \ --actions produce,consume \ pay-center/order-prod权限和 Topi 数量配合起来效果更明显:一个业务用一个 Namespace,加机器、换客户端都不互相影响。如果遇到团队成员需要一个“临时订阅”,我宁可让他们在共享 Namespace 里创建一个专用订阅,也不要放开所有人的 admin 权限。方便和安全之间的平衡点,其实就在这里。5. 创新实践:Pulsar 不只是消息队列5.1 用 Functions 和 IO Connectors 做轻量流处理Pulsar 被讨论很多的创新点,是它把消息队列和流处理两个概念放到了一套模型里。Pulsar Functions 允许你直接写一个简单函数,部署到集群里,让消息在传递过程中被过滤、转换、聚合。它适合的场景是“几十行代码就能搞定的处理逻辑”,比如日志清洗、字段补全、消息路由等。我见过一个典型的落地案例:某个团队需要把前端埋点数据里的用户标识统一替换为匿名 ID,原来做法是写一个 Flink 任务,重且复杂。后来改成 Pulsar Function,直接订阅原始 Topic,处理完写到另一个 Topic,一个 JAR 包搞定。这种场景不需要引入单独的流处理集群,运维负担小,出错的概率也低。Pulsar IO 则解决了“外部系统接入”的问题。通过 Source/Sink Connector,可以直接把数据库的 CDC 变更、Kafka 数据、S3 文件等导入到 Pulsar,也可以把消息导出到 Elasticsearch、MySQL、HDFS 等。对于不想为每个对接目标写专用适配器的团队,这是一条很务实的快车道。5.2 多协议接入:让存量系统低门槛迁移Pulsar 的另一个“创新实践”方向是兼容多协议。通过 KoP(Kafka-on-Pulsar)、MoP(MQTT-on-Pulsar)、AMQP-on-Pulsar 这类协议处理器,一个 Pulsar 集群可以直接接受 Kafka 客户端、MQTT 设备、AMQP 客户端连接。这对存量系统太重要了。很多公司不是不想迁到 Pulsar,而是 Kafka 客户端已经在几十个服务里跑着,改造量大、风险高。使用 KoP 后,客户端代码几乎不用改,只需要把 broker 地址切换到 Pulsar 的 KoP 端口,就完成了底层替换。当然,协议处理层的性能会有一些损耗,但换来的是存储计算分离、多租户、长保留这些能力。如果团队正在做“通过换消息中间件降低长期运维成本”的规划,这种渐进式路径远比“一把梭重写”稳妥。5.3 跨地域复制与云边协同跨地域复制是 Pulsar 比较有代表性的原生能力,本质上是在多个集群之间异步同步消息。它的好处是,你可以做到“两地三中心”级别的容灾,而不只是简单的主从切换。实际落地时,我建议先分清楚两种模式:主动复制的两个集群都接收生产者和消费者写入,适合灾备双活;被动复制的主集群负责生产,备集群只消费,适合读多写少的场景。Pulsar 支持的是一对一、双向、星型甚至多副本的网络拓扑,配置上选择一个 Namespace 启用 Geo-Replication 即可。跨机房复制最常见的坑是延迟放大。每次同步都要经过网络,两个地点距离越远延迟越高。不过你可以通过调整复制消息的持久级别来降低耗时,也可以只在核心业务 Topic 上开启复制,而不是整个 Namespace 全部开启。我见过有人因为图省事,把包含日志流和业务流的 Namespace 一键开了双向复制,结果跨机房带宽直接被打满。把需要复制的业务面和数据量分开规划,才能发挥这个功能的真正价值。6. 参会前该做哪些准备:倒计时 3 天的正确打开方式6.1 这次活动适合谁听Pulsar Developer Day 并不是面向所有人的讲座,它更适合这几类人:正在做消息中间件选型或二次评估的后端开发;维护着多套 Kafka 集群、想降低运维复杂度的 SRE;数据平台工程师,尤其是接触流处理和数据湖的;以及那些已经上了 Pulsar、但希望进一步优化参数和排查线上问题的人。如果你只是想要一个“把消息发出去”的示例,这个开发者日的内容可能比你预期的更深,但同样能帮你建立整体架构认知。我建议你在现场听分享时,把重点放在“实践案例”上,而不是放大 PPT 上的架构图。6.2 现场值得重点关注的话题很多人参会带了个笔记本,结果发现自己记录的都是大概念。我的经验是,提前先给自己列一张“问题清单”,到现场闻讯。比如:你所在场景的 Topic 规模、单 Topic 流量和 Pulsar 官方/社区案例差多少?他们是怎么处理消费端幂等的?是依赖中间件能力,还是业务去重?有没有小规模集群的部署经验?资源有限的情况下,副本参数该降到多少?遇到消费积压和存储增长同时出现时,他们优先排查哪些指标?做跨地域复制时,网络延迟和成本有没有量化数据?这些问题比“Pulsar 和 Kafka 哪个好”更有价值,因为后者只要一搜就能找到大量观点,前者需要真正跑过生产的人才能给出可信答案。6.3 带着运行数据去交流如果手头已经有 Pulsar 集群或概念项目,我会建议你在会前把集群的一些基础运行数据导出,比如 Topic 总量、每日消息条数、平均消息大小、峰值吞吐、Backlog 分布、存储使用量。带着这些数据去听分享、提问,效果完全不同。之前我去参加类似技术活动时,最喜欢找那些“踩过生产环境坑”的分享者聊天。很多人会告诉你,他们用的副本数是多少、为什么不是默认值;遇到批量消费时怎么调整批处理参数;以及分层存储落地时对象存储的访问延迟对消费的影响有多大。这些信息通常不在官方文档里,只有面对面交流才能问出来。如果你确实没法到现场,我建议活动结束后第一时间看社区里的分享视频和 PPT,重点选择和你业务场景最接近的那几个复盘。倒计时 3 天,要做的不是把 Pulsar 官方文档从头啃一遍,而是先梳理自己手上系统的现状、把上面那张问题清单写好,然后再带着具体需求去摄取信息。写在最后我在实际跑消息中间件这几年的最大体会是:再好的架构和参数,都抵不过“业务场景理解到位”。很多人学 Pulsar 时喜欢先看特性列表,然后照着部署一遍,最后发现线上还是要靠监控和排查来成长。Pulsar Developer Day 这样的活动,价值不在于告诉你哪款中间件最牛,而在于把一堆真实跑过的实践放在你面前,供你对照自己的系统做判断。另外一个很实用的小技巧:每次参加完类似开发者日,都别急着把笔记归档。把你记录到的问题和案例带回工位,对照自己的配置和指标做一遍“体检”,哪怕只是修改一个 ackTimeout、调整一个副本参数,也比收藏十篇技术文章更有意义。距离活动正式开场还有 3 天,如果你本来就在调研消息中间件方向,这 3 天正好用来把自己手上的系统现状梳理一遍;把问题准备充分了,现场每一分钟都能榨出远超票价的干货。