
COSCon’25 与 Pulsar Developer Day 2025 背靠背联办的消息放出来时我最先注意到的是那个把口号玩得很响的主题“Make MQ Great Again”。乍一看有点戏谑但混过消息中间件领域的人会心一笑——MQ 这个技术门类确实沉寂太久了。这几年流处理抢尽风头Kafka 几乎成了“消息系统”的代名词连招聘 JD 里都只写 Kafka很多后入行的工程师已经把 RabbitMQ、RocketMQ 当成古董更别提 IBM MQ 这种老牌企业级队列。可是真正做过大规模异步架构、跨团队消息治理、多租户隔离的人心里都清楚旧式 MQ 的痛点一直没有被完美解决。Pulsar 带着存算分离、多租户、统一队列与流模型这些设计出来搅局确实给这个领域重新点了一把火。这篇文章不打算做会议议程的流水账那没意思。我把自己在现场听分享、看演示、跟同行交流时觉得真正有价值的东西整理出来Pulsar 凭什么在 MQ 领域翻牌、架构上哪些决策被反复讨论、一线落地时有哪些容易踩的坑以及工作坊里那套三十多分钟就能跑起来的部署 Demo 到底怎么复现。无论你是正在做消息中间件选型的技术决策者还是想给自己的系统引入一套更弹性的消息底座都能从里面捞到点实在的东西。1. 这场联动的核心信号MQ 并没有老只是需要新的解法1.1 传统消息队列的历史包袱老一代消息中间件的统治时代现在回看其实很短。IBM MQ 这类商业产品统治企业市场的时候消息队列的核心价值是“稳定”和“可靠”一套队列跑十年不出事故比什么都重要。代价也很明显闭源、价格贵、扩展性差、跟云原生基本绝缘。你很难想象在容器环境里把 IBM MQ 横向扩容成几十个节点这不光是成本问题架构上就拧着劲。开源之后RabbitMQ 把 AMQP 协议带给大众Routing 和 Topic 的灵活性让业务系统第一次觉得消息队列是“好用”的。但 RabbitMQ 的性能极限和可靠投递之间的平衡点很难把握一旦消息量上来节点瓶颈立刻出现。RocketMQ 在国内电商场景打磨得不错事务消息和延迟消息是杀手锏但它整体还是面向固定集群的架构思路跨地域容灾和存储弹性上相对吃力。Kafka 改变了游戏规则用日志追加的方式把吞吐推到百万级但也把“消息队列”变成了“日志管道”——它本质上不是一个通用 MQ而是为流式数据设计的提交日志。所以 MQ 领域的真实状态是传统队列不够弹性Kafka 不够“队列”。两者之间的空档正好是 Pulsar 想站的位置。1.2 Pulsar 的设计出发点我最早接触 Pulsar 是 2.x 时代当时最打动我的不是某个具体功能而是它的整体设计逻辑把 Broker 和存储彻底拆开。Broker 只管转发、鉴权、流控这类无状态计算数据落到 BookKeeper 上存储层可以独立扩展。这直接解决了 Kafka 集群里最让人头疼的问题——分区迁移和节点重启时的大量数据复制。多租户是另一个让我觉得“这才是为企业而生”的设计。Pulsar 的资源模型是 tenant → namespace → topic 三层。每个 tenant 之间有完整的隔离不同业务部门可以在同一个集群里共享消息基础设施但又互不干扰配额和策略都在 namespace 级别控制。这对中大型公司来说价值极大因为消息集群是最不适合“一人一套”的基础设施太浪费而多租户能让资源池统一运营。口号喊得再响最终还是要落到代码和架构上。Pulsar Developer Day 这种活动存在的意义就是把“存算分离”“多租户”这些听起来很高大上的词拆开告诉你怎么在真实环境里把它们用起来。所以这场联合活动其实释放了一个强烈信号MQ 领域没有被判死刑相反它在云原生时代找到了新的演化方向。2. 现场最值得回味的几个技术观点2.1 存算分离不是炫技是被规模逼出来的选择技术会议的 Session 里“为什么采用存算分离”这个问题几乎每次都会被问到。台上的回答也很坦率不是所有人都需要存算分离但如果你碰到过 Kafka 某个节点宕机后分区重平衡导致集群整体性能跳水你就会明白存储和计算纠缠在一起很痛。Pulsar 的做法是 Broker 和 BookieBookKeeper 的存储节点独立部署。Broker 是无状态的意味着它可以水平伸缩得很快也可以在瞬间缩容不需要担心本地数据丢失。Bookie 承担实际的数据持久化底层用 BookKeeper 的 Journal Ledger 机制。写路径是先写 Journal 再定期刷入 Ledger本质上是牺牲一点写放大来换故障恢复速度。现场分享里有一个数字我记得很清楚某个集群在模拟机房故障时因为数据分布在多个 Bookie 上剩余节点可以用多副本策略在分钟级完成数据重建而不是像 Kafka 那样先把整个分区的数据重新复制一遍。代价同样存在。存算分离引入了额外的网络跳数如果机房内网质量不好延迟会一层层叠加上去。而且 BookKeeper 本身是一套分布式系统意味着运维知识体系多了一个维度。活动现场不少做运维的同学吐槽Broker 确实好管了但 Bookie 的磁盘调度、换盘策略、Journal 空间管理又逼着你学新东西。这个取舍很真实——没有免费的架构只有适不适合你当前的技术储备和业务规模。2.2 消费模型与 Cursor 机制的再设计MQ 用户最关心的一个问题就是“消费进度存在哪里”。老一代队列通常靠 broker 端记录 offsetKafka 靠消费者提交 offset这里面都有各自的麻烦。Pulsar 的 Cursor 设计是消费位置由 Broker 统一管理订阅模式有四种——Exclusive独占、Shared共享、Failover故障转移、Key_Shared按键分区。现场有人问Exclusive 和 Failover 看起来不都类似吗答案在于对顺序性的保证程度。Exclusive 是最严格的单消费者模式任何时刻只有一个消费者可以消费该主题Failover 允许多个消费者同时连接但同一时刻只有一个是主消费者其他是备胎。Shared 模式则彻底放开限制多条消息可以被多个消费者抢着消费吞吐上去了但全局顺序完全无法保证。Key_Shared 是 Pulsar 非常有辨识度的能力。它按照消息的 key 做 hash把相同 key 的消息路由到同一个消费者这样你在保持高并发消费的同时还能保证某个业务维度比如同一订单、同一用户的顺序。这个能力在 Kafka 里做不到Kafka 只能靠分区数来限定并发。但 Key_Shared 也有自己的禁忌如果消息的 key 数量很少hash 分布会严重倾斜造成某个消费者热点压满而其他消费者空转。现场给的判断标准是key 的基数至少要大于消费者数量的几十倍否则就别用 Key_Shared老老实实走 Shared 业务内排序。2.3 分区顺序性与 Topic 扩展的取舍Kafka 的顺序性解法是分区内有序你要扩展并发度就得增加分区但这会带来一个历史遗留问题分区增加后客户端并不知道新分区存在除非触发 rebalance。而一旦触发 rebalance整个消费组都会停顿。Pulsar 的处理方式更优雅Topic 内部采用分段Segment存储扩展并发不需要重新分区直接增加订阅的 consumer 数量即可Broker 会重新分配分段。这意味着你在 Pulsar 里面对“并发度调整”时心理负担小很多。现场有位分享者用自己的业务举例他们每天晚上有个耗时很长的批任务早上又有高并发的实时小请求同一个 Topic 两种流量模型。以往用 Kafka 时只能按峰值去留分区数用 Pulsar 后可以在不同时段动态调整消费者的并发数凌晨批量任务用 Shared 拉全量白天实时请求用 Key_Shared 保证单用户有序。这个例子让我印象很深因为这种动态伸缩的体验才是云原生时代消息队列该有的姿态。3. 一线实践者分享的生产落地经验3.1 从 Kafka 迁移到 Pulsar 的真实路径大会的实践分享环节里关于“从 Kafka 迁到 Pulsar”的话题人气最高这确实是当前最普遍的诉求。正统路线分三步第一步是接入层兼容Pulsar 内置了 Kafka 协议处理器你不需要改客户端代码就能直接把流量导过来第二步是数据迁移用工具做位点同步把 Kafka 里的历史消息搬过来第三步是流量切换逐步把消费者切到 Pulsar 上。但现场的分享者毫不客气地指出第一步并没有听起来那么美好。Kafka 协议兼容层对最常见的消息生产消费、消费者组管理支持得不错但如果你用到了 Kafka Streams、事务、或在消息 header 里塞了特殊格式协议层就可能出现细微差异。有个团队在迁移时遇到一个诡异问题客户端设置的 acksall 在 Kafka 上正常切到 Pulsar 兼容层后出现偶发超时定位了很久才发现是兼容层对批次batch的处理方式和原版不完全一致。所以迁移前必须把客户端用到的所有高级特性列一个清单逐个验证。第二步最容易踩的坑是积压数据和位点映射。Kafka 的 offset 和 Pulsar 的 cursor 是两套完全不同的体系迁移工具有时会因为 topic 的 key 分布差异导致位点映射偏掉。现场建议如果是做一次性迁移不要追求保留所有历史消息设置一个明确的消息过期时间比如迁最近 24 小时宁可丢尾巴也比迁移中途卡死强。3.2 主题规划与租户隔离的实践经验Topic 怎么命名、namespace 怎么划分是很多 Pulsar 新手忽略的“战略问题”。现场给出的建议是租户层对应组织架构如订单、支付、用户namespace 层对应业务域或环境比如线上、线下、灰度topic 则严格按照{租户}/{命名空间}/{主题名}的结构来组织。这样做的原因是 Pulsar 的鉴权、配额、备份策略全部作用于 tenant 或 namespace如果这一层划分混乱后面所有治理动作都会很难受。一位有管理上千个 topic 经验的运维说他们早期的命名空间划分过粗导致所有业务共用一个 namespace结果某个业务因为消费逻辑死循环疯狂建立 backlog把整个 namespace 的存储配额打爆所有业务一起遭殃。后来改成按业务域细分 namespace 后至少可以用 quota 把故障隔离在小范围内。这个经验很朴素但真到了要设计消息治理策略时就是这种“朴素经验”在救场。3.3 演讲嘉宾反复提醒的高危操作现场有三位分享者不约而同提到了一些“看似合理但千万别这么做”的操作我把它们列成速查表高危操作后果正确姿势所有消息都走 Key_Shared 保证顺序key 基数小导致消费者热点严重倾斜先估算 key 基数基数小用 Shared 业务内排序背压backlog策略默认不设置消费堵塞时消息无限积压磁盘撑爆为每个 namespace 设置 backlog quota 和 eviction 策略把 producer 的 maxPendingMessages 调过大生产端内存 Jitter 严重OOM 风险按单 topic 生产峰值和 broker 内存预算反向计算用默认参数直接做生产集群单机跑起来无感集群化后 Bookie 磁盘 IO 成瓶颈至少把 Journal 和 Ledger 磁盘分开SSD 留给 Journal这些建议在官方文档里其实都写到了但没踩过坑的人很难有切肤之感。活动里花了不少时间讲这些本质上是在提醒大家Pulsar 的架构优势是弹性但弹性不是躺赢很多参数是需要结合实际流量做调校的。4. 动手工作坊复盘三十分钟跑起一套多租户消息环境4.1 本地快速部署从零到能发消息工作坊的 Demo 环境用的是 Pulsar 的 Standalone 模式适合本地学习也适合快速验证想法。启动方式非常简单用官方提供的二进制包或者 Docker 镜像都行。我习惯用 Docker Compose 跑一个固定版本避免本地环境不一致version: 3 services: pulsar: image: apachepulsar/pulsar:4.0.0 container_name: pulsar-dev ports: - 6650:6650 - 8080:8080 volumes: - ./pulsar-data:/pulsar/data command: bin/pulsar standalone启动后检查一下状态# 查看集群是否就绪 docker exec -it pulsar-dev bin/pulsar-admin clusters list # 应该输出 standalone这里有一个新手容易犯的错Standalone 模式默认把 Bookie 和 Broker 放在同一个进程里端口 6650 是消息流量的服务端口8080 是管理接口。如果你在公司网络里做演示记得把 8080 端口暴露给负载均衡或网关否则别人只能看到你跑起来了却访问不到管理接口。4.2 创建租户、命名空间与主题的核心参数接下来是体验 Pulsar 多租户能力的关键路径。用命令行完成整套创建流程# 创建租户表示一个业务大部门 docker exec -it pulsar-dev bin/pulsar-admin tenants create order-center # 创建命名空间表示一个业务环境或业务域 docker exec -it pulsar-dev bin/pulsar-admin namespaces create order-center/prod # 设置 namespace 的保留策略保留最近 3 天或积压到 1GB 开始丢旧消息 docker exec -it pulsar-dev bin/pulsar-admin namespaces set-retention -s 3d -b 1G order-center/prodretention 策略是最容易被忽略但影响很大的参数。Pulsar 默认的 retention 可能只保留很短时间如果你希望消费者迟到一段时间后还能读到历史消息就必须显式调大。但 retention 也不是越大越好它直接占用 Bookie 的磁盘所以工作坊给的经验值是“根据你数据的重要程度和数据量大小定能重放的数据没必要留太久”。创建 Topic 有两种方式显式创建和消息写入时自动创建。工作坊特意演示了显式创建因为生产环境里自动创建 topic 容易造成“命名不规范、主题满天飞”的失控局面# 显式创建 topic docker exec -it pulsar-dev bin/pulsar-admin topics create persistent://order-center/prod/order-eventstopic 的完整路径有三段persistent 表示持久化存储order-center 是租户prod 是命名空间order-events 是主题名。只看这个路径任何人都能快速判断业务归属和进件环境这就是规范化的价值。4.3 用 Python 客户端跑一个生产消费循环命令行走了一圈之后工作坊用 Python 客户端演示了最核心的生产消费闭环。Python 客户端的优势是简洁适合演示但生产首选还是 Java 客户端因为性能和多线程支持更强。先安装pip install pulsar-client生产者代码import pulsar client pulsar.Client(pulsar://localhost:6650) producer client.create_producer( topicpersistent://order-center/prod/order-events, block_if_queue_fullTrue, batching_max_messages500, batching_max_publish_delay_ms10, ) # 发送消息时绑定 orderId 作为 key producer.send((new-order-123456).encode(utf-8), properties{region: cn-hangzhou}) print(message sent) client.close()消费者代码用 Shared 模式import pulsar client pulsar.Client(pulsar://localhost:6650) consumer client.subscribe( topicpersistent://order-center/prod/order-events, subscription_nameorder-svc-consumer, consumer_typepulsar.ConsumerType.Shared, ) while True: msg consumer.receive(timeout_millis5000) if msg: print(freceived: {msg.data().decode(utf-8)}, key{msg.properties().get(region)}) consumer.acknowledge(msg) else: break client.close()这里有个工作坊反复强调的细节消息确认必须在处理完成之后调用而不是“收到消息先 ack 再慢慢处理”。因为 Pulsar 的 redelivery 机制是基于 ack 的一旦 ack 后才处理、处理又抛异常这条消息就永久丢失了。我看到现场有不少人改代码时习惯把 ack 写在最前面这种习惯在低风险业务里无感在高价值订单场景就是一个雷。5. 现场答疑实录新手最容易卡壳的几个问题5.1 部署方面的常见误区答疑环节的第一个高频问题是生产环境该不该用 Standalone 或单节点集群答案很直接不该。Standalone 适合开发预览单节点集群虽然能用但当你滚动升级或更换一个磁盘损坏的 Bookie 时才会意识到单节点的脆弱。生产集群的最低配置是 3 个 Broker 3 个 Bookie且两个角色的节点不要混布否则就是名义上的“存算分离”实际上一台机器重启全集群抖动。还有个非常实际的磁盘问题。Bookie 的 Journal 和 Ledger 对磁盘的要求完全不同Journal 要求低延迟高 IOPS建议 SSD 或 NVMeLedger 是顺序写入为主的大块数据普通 HDD 也能扛但容量要够大。如果你只有一块盘硬把所有数据塞在一起高吞吐场景下 Journal 的写延迟会拖垮整个写入路径。现场有位运维分享的教训是他们最开始图省事共用了数据盘结果生产抖动一查发现是 Journal 和 Ledger 争抢 IO 导致的连锁反应后来哪怕用云盘也要在购买时选两块卷分开挂载。5.2 性能调优与数据可靠性问到“为什么我的 Pulsar 吞吐上不去”时工作坊给的排查第一步不是看 broker 的指标而是先看客户端的配置。最常见的问题是 producer 的 batching 参数没开。Pulsar 的吞吐建立在批量消息聚合的基础上默认单条 send 的网络往返开销会限制吞吐天花板。推荐配置是 batching_max_messages 设到 500 到 1000batching_max_publish_delay_ms 控制在 5 到 20 毫秒之间。单独发一条消息延迟会更低但吞吐会明显下降。可靠性方面很多人关注会不会丢消息。Pulsar 在写入路径上的持久化机制是生产者发送消息到 BrokerBroker 写入 Journal 后返回确认如果你的 ack-timeout 设置太短消息可能还没有完成确认就被标记为超时重发。这里有个关键判断分布式消息系统只能保证“至少一次”而非“恰好一次”如果你需要精确去重得在消费端配合消息唯一 ID 做幂等处理。这个道理在所有 MQ 中通用但 Pulsar 的 key 属性让实现幂等变得更顺手。5.3 生态协同怎么融入已有的技术栈最后一个常被提到的话题是生态。Pulsar 已经集成了 Flink、Spark、Storm 这些主流计算引擎现场演示了 Flink Connector 的基础用法直接订阅 Pulsar topic 作为流数据源写回结果到另一个 topic。这个链路很适合做实时数仓的接入层比绕过 Kafka 再接 Flink 至少省掉一跳转发。监控体系方面Pulsar 官方指标可以通过 Prometheus 暴露再配 Grafana 模板看板来观察。现场给的建议是至少盯四个指标生产积压backlog count、消费滞后consumer lag、Bookie 的 Journal 写入延迟、Broker 的连接数。前两个反映业务消费健康后两个反映基础设施健康。很多“半夜消息堆积半天没人发现”的事故本质上是监控指标维度不够只看吞吐和 CPU不看积压——等业务方找上门的时候磁盘已经快满了。把工作坊的代码带回去把 MQ 的未来也带回去参加完这一整天的活动我最大的感受是Pulsar 不是一个“更好的 Kafka 替代品”而是另一种思维模型。Kafka 强在流式日志Pulsar 强在弹性与多租户两者共存于很多公司也不是什么奇怪的事。真正让我兴奋的是这种联合活动终于让 MQ 重新回到了技术议程的中心位置——它不只是基础设施里的“管道”而可以是支撑业务创新的平台能力。如果你看完这篇回顾也想动手试试我的建议很朴素先跑一遍上面第三节的 Standalone 部署把创建租户、命名空间、主题的链路走通再用 Python 客户端让自己看到消息流动。三十多分钟的时间投入换来的是对一套完全不同架构逻辑的直观认知。踩过几次坑之后你会发现MQ 的问题从来不是“老了”而是“以前没人愿意为它做一场属于开发者自己的节日”。Pulsar Developer Day 做到了这本身就值得记录。