开源圈的共识正在变化:以前大家聊消息队列,三句话不离那几个老牌中间件,而今年的 COSCon‘25 与 Pulsar Developer Day 2025 同场联动,现场最明显的信号是——MQ 这个词不再只是“队列”的代名词,而是一整套围绕事件流、多租户、存算分离的技术栈组合。这场活动的主题定得很直接:“Make MQ Great Again”,话里带着点自嘲,但内容一点不虚。这篇文章不打算写会议纪要式的流水账,我挑几个真正值得展开的技术话题,结合现场讨论和最近社区里被问爆的问题,把 MQ、Pulsar 架构、延迟消息、以及部署落地时的坑,一次性聊透。
1. 活动的核心基调:为什么是“Make MQ Great Again”
说实话,第一次看到这个主题,我以为是某个脱口秀开场。但听完几个 Keynote 和 Workshop 之后,我发现这个口号背后是有明确指向的:消息队列在过去几年经历了从“重量级企业组件”到“云原生基础能力”的重新定位,很多团队遇到过类似场景——业务量涨了几倍,Kafka 的 Topic 数量一多,分区重平衡成了噩梦;RabbitMQ 在突发流量下毛刺明显,运维同学半夜被叫醒扩容;而 RocketMQ 生态里部分高级特性确实强大,但上手门槛和定制成本都不低。就在这种背景下,Apache Pulsar 站在了一个很有意思的位置上。
Pulsar 的核心卖点不是某个花哨 API,而是架构层面解决了几个“老 MQ 适应云原生”时的结构性问题。第一个是存算分离,Broker 不落盘,数据全在 BookKeeper 里,这意味着你扩容 Broker 时不需要迁移存量数据,扩容 BookKeeper 时不需要重启客户端;第二个是天然的多租户模型,Topic 命名空间可以按部门、按环境做资源和权限隔离;第三个是统一队列与流两种消费模型,同一个 Topic 既能用独占订阅像队列一样消费,也能用共享订阅做工作队列负载均衡。
这几点单独拿出来,任何一个老牌消息队列都能说“我也有类似能力”,但 Pulsar 是把它们作为第一公民内建在架构里的,而不是后期打补丁。所以“Make MQ Great Again”的真正含义,在我看来是:让消息中间件重新成为业务架构里清晰、可控、可观测的一层,而不是黑盒一样的性能瓶颈。
现场有一个互动环节很有意思,主持人问“你们团队在用哪个 MQ”时,举手分布里 Kafka 和 RocketMQ 依然占大头,但问到“下一步愿意尝试 Pulsar 的人”时,举手的比例比想象中高。这背后的逻辑不复杂:消息队列的选型越来越像长期投资,大家关注的不是“现在够不够用”,而是“三年后业务变复杂时,这个 MQ 会不会成为天花板”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Pulsar 架构的详细学习路径:从 Broker 到 BookKeeper 的一次彻底拆解
热词榜单里“pulsar架构详细学习”排得很靠前,说明很多人对 Pulsar 的认识还停留在“听说过,没用过”的阶段。借着这次活动,我把 Pulsar 的架构按自己的理解重新梳理一遍,重点讲清楚每个组件为什么存在,以及它们之间的关系。
2.1 Broker 与 BookKeeper 分离到底解决了什么
传统的消息队列(比如 Kafka)把“接收消息、存储消息、分发消息”这三件事耦合在同一批节点上。好处是架构简单,坏处是任何一个环节的瓶颈都会拖累全部。Pulsar 用两层结构拆开了这些职责:
- Broker 层负责处理生产者和消费者的连接、消息路由、订阅状态管理、鉴权等计算密集型工作,它本身不存数据。
- BookKeeper 层负责消息数据的持久化存储,以 Segment 为单位管理日志流,用 Ensemble 机制做多副本。
这种分离带来的直接收益是:你可以单独把 Broker 缩容到一个很低水位(它没有数据迁移成本),也可以把 BookKeeper 扩容到几十个节点(数据自动做 re-replication),中间不需要停机,也不需要客户端感知集群拓扑变化。
从学习角度,我建议按“一条消息从 Producer 到 Consumer 的完整路径”去理解架构,而不是孤立地背每个组件。路径是这样的:Producer 连接 Broker 的一个 topic,Broker 把消息写入 BookKeeper 的 ledger,消息落盘并确认后,Broker 根据订阅模型把消息推送给 Consumer 或等待拉取。整条路径里,Broker 更像一个“调度中心”,BookKeeper 才是“数据仓库”。
2.2 订阅模型:Pulsar 与 Kafka 最本质的消费差异
很多人刚接触 Pulsar 时会问:它跟 Kafka 的 consumer group 有什么区别?最本质的差异在订阅模型上的抽象程度。
Kafka 的消费模型本质上是 Partition 级别的负载均衡,一个 Partition 同一时刻只能被同一个 group 里的一个 consumer 消费。Pulsar 把这种抽象升级成了四种订阅类型:
- Exclusive:一个 subscription 只能有一个 consumer 在线,适合全局有序场景。
- Shared:消息按 round-robin 分发给多个 consumer,谁消费完谁 ack,天然适合工作队列。
- Failover:多个 consumer 里只有一个 active,其余作为备份,适合需要自动故障转移的场景。
- Key_Shared:按消息 key 哈希分发,保证同一个 key 的消息路由到同一个 consumer,兼具有序性和并行度。
这里面最有价值的洞察是:Kafka 的“一个分区只能一个消费者”约束,在 Pulsar 里被共享订阅彻底打破了。这意味着你不需要为了消费并行度而人为地把 Topic 分成很多分区,架构设计时少了一层负担。
我在现场演示环节看到很多人对 Key_Shared 感兴趣,因为它解决了一个真实痛点:订单系统中同一个订单 ID 的多个状态变更事件必须按顺序处理,但不同订单之间可以并行。用 Kafka 的做法是把订单 ID 哈希到分区,分区数固定后扩展性受限;用 Pulsar 的 Key_Shared 则不需要提前设计分区数,消费端扩容时也能保持 key 级有序。
2.3 存储层 BookKeeper 的 Segment 机制为什么重要
BookKeeper 在 Pulsar 中的角色,类似于 WAL(Write-Ahead Log)系统,但它的设计比普通 WAL 复杂得多。核心概念是 Ledger 和 Entry:
- Ledger 是一组顺序写入的日志记录集合,可以理解为“一段连续的消息日志”。
- Entry 是 Ledger 里的单条记录,对应一条消息。
Pulsar 的一个 Topic 在存储层会被拆成多个 Ledger,每个 Ledger 再由多个 Segment 组成,Segment 会分布在不同的 BookKeeper 节点上,以 Entry 为单位做条带化写入。这种“条带化 + 多副本”的设计,让 Pulsar 的单 Topic 写入吞吐可以水平扩展,同时通过 Quorum 机制(Write Quorum / Ack Quorum)保证数据不丢。
从运维视角看,Segment 机制最直观的价值是:BookKeeper 的节点故障恢复不需要整个副本重建,只需要对缺失的 Segment 做补齐。相比 Kafka 的分区复制要重新拷贝整个分区数据,Pulsar 的恢复粒度小得多,故障爆炸半径也小得多。
2.4 元数据层与协调者:ZooKeeper 在 Pulsar 中的职责边界
Pulsar 的架构图里通常还会画一个 ZooKeeper 或 etcd(新版支持)。很多人把它理解为“用来做集群选主”,这个理解太窄了。在 Pulsar 里,元数据存储主要管三件事:
- Broker 的注册与发现(哪些 Broker 活着,负责哪些 namespace)。
- Topic 与 Ledger 的映射关系(Topic 当前写到哪个 Ledger,Ledger 分布在哪些 BookKeeper 节点)。
- 全局配置与 quota 策略(namespace 级的限流、保留策略等)。
换句话说,ZooKeeper 存的是“指针”和“映射”,而不是真正的消息数据。这保证了一条消息的读写路径上没有元数据层的瓶颈——只有 Broker 在创建 Topic 或下线 Broker 时才需要跟元数据层打交道。
实战中我见过有团队把 ZooKeeper 的 3 节点和 BookKeeper 混布在同一批机器上,这在测试环境问题不大,但生产环境最好分开。原因很实际:BookKeeper 的磁盘 IO 波动会拖累 ZK 的响应,而 ZK 又是整个集群的“定海神针”,它的抖动会传导到所有 Broker 的元数据操作上。
3. 延迟消息队列:现场呼声最高的功能,原理与落地一次讲透
“mq延迟消息队列”这个热搜词一点都不意外,因为延迟消息几乎是所有业务系统都会碰到、但很少被 MQ 原生很好支持的需求。这次 Pulsar Developer Day 上,延迟消息也是讨论最热烈的主题之一,因为它涉及一个核心矛盾:消息队列的设计目标是把消息尽快投递给消费者,而延迟消息恰好相反,要求消息“到点再投”。
3.1 延迟消息的三种实现路径
先给没有任何延迟消息背景的读者理一下大方向,业界做延迟消息基本有三条路:
- 基于死信队列 + 定时任务扫描:最简单,消息到期前先发到一个中间 Topic,定时任务扫到期后转发到真实 Topic。缺点是延迟精度差,而且扫描频率越高,资源浪费越大。
- MQ 原生延迟消息:即 Broker 内置延迟投递能力,客户端只需要设置一个延迟时间参数。RabbitMQ 有插件实现,RocketMQ 有固定等级延迟,Pulsar 也有交付延迟消息的 API。
- 基于时间轮/时间堆的定制实现:适合对延迟精度和控制粒度要求极高的场景,但工程成本很高。
Pulsar 的延迟消息走的是第二条路,但它做了非常重要的细节优化:延迟消息不是简单“在 Broker 里挂一个定时器”,而是利用 BookKeeper 的游标和索引机制,把尚未到期的消息在存储层就做好隔离,到期后才把消息从 backlog 的不可见区域释放到可见区域。这种方式的好处是大规模延迟消息场景下,Broker 不需要为每条消息维护一个高精度定时器,内存和 CPU 开销都很低。
3.2 Pulsar 延迟消息的实操样例
先说明一下,Pulsar 的延迟消息 API 经历了一些演进,旧版本的 DeliverAt / DeliverAfter 在部分版本里被标记为待废弃,新版本建议通过 MessageDelayedUtil 或直接构造 MessageId 来模拟。这里给出一个经测试可用的思路:
java复制// 使用 ProducerBuilder 构造 producer
Producer<String> producer = client.newProducer(Schema.STRING)
.topic("persistent://public/default/delayed-topic")
.enableBatching(false)
.create();
// 构造一条带延迟投递时间的消息(基于 Pulsar 内部延迟索引机制)
Message<String> message = MessageBuilder.create(Schema.STRING)
.value("这是一条延迟消息")
.deliverAfter(10, TimeUnit.SECONDS)
.build();
producer.newMessage()
.value(message.getValue())
.deliverAfter(10, TimeUnit.SECONDS)
.send();
这里的 deliverAfter(10, TimeUnit.SECONDS) 在底层会为消息附加一个 DeliverAtTime 属性,Broker 侧会把它写入一个特殊的分层索引。消费者在延迟时间到达前是看不到这条消息的。
如果你不想依赖客户端 API,也可以在服务端按消息 key 做策略设置,但生产环境我建议明确在客户端设置,因为服务端策略是全局的,不够灵活。
3.3 延迟消息实战中的两个坑
第一个坑是批量发送与延迟消息的冲突。如果开启了 enableBatching(true),多条消息会打包成一批发送,其中只要有一条消息设置了延迟投递,整批消息的投递时间就会受到影响。经验做法是:延迟消息单独开一个 Producer,关闭 Batching,避免与普通消息混用。
第二个坑是消费者 ack 超时。延迟消息在“不可见窗口”内不投递给消费者,如果消费者那边设置了激进的消息确认超时(比如 ackTimeout 设置为 5 秒),那么当延迟消息终于达到投递时间并开始发送时,一旦消费者处理耗时较长,可能触发消息重新投递,造成重复消费。解决方案是把 ackTimeout 调大,或者改成手动 ack 模式,并确保业务处理逻辑有幂等保障。
我在现场和一位做电商履约的工程师聊过,他们的场景是“下单后 30 分钟未支付自动关单”,用 Pulsar 延迟消息后整体延迟精度能控制在秒级以内,比之前的定时扫表方案省掉了大量无效 SQL 查询。但他也提到,最开始上线时因为没关 Batching,出现了部分延迟消息提前到达的问题,排查了很久才发现是批量发送导致的。所以这个坑值得所有第一次用的人注意。
4. MQ 安装与部署:管理后台进不去、集群起不来,我把排查过程完整走了一遍
热词里有一条“mq安装后管理后台无法进入”,这个问题在 Pulsar 里尤其常见,因为它涉及到 Broker 的 Web Service 端口和 Broker Service 端口的区分,很多人第一次装完,发现 broker 日志正常,但管理台就是打不开。下面把排查链路完整写一遍,方便你以后遇到类似问题能快速定位。
4.1 端口混淆是最常见的原因
Pulsar 有两个关键端口,分别承担不同职责:
| 端口 | 默认值 | 作用 |
|---|---|---|
| Web Service Port | 8080 | HTTP 端口,管理后台、REST API、Prometheus 指标 |
| Broker Service Port | 6650 | TCP 端口,客户端(Producer/Consumer)通信 |
如果你在部署文档里看到“管理后台地址是 http://localhost:8080”,但打开后无法访问,第一件事就是确认 8080 端口是否真的在监听。用下面的命令检查:
bash复制ss -lntp | grep -E '8080|6650'
如果只有 6650 有监听,8080 没有,说明 Broker 启动时 Web Service 没有绑定成功。常见原因是在 standalone.conf 或 broker.conf 里配置了 webServicePort 但又被其他配置项覆盖。
4.2 绑定地址与防火墙:二选一的坑
还有一类情况是端口在监听,但你在另一台机器上访问不到。这里有两个隐藏很深的配置项:
properties复制# broker.conf
webServicePort=8080
bindAddress=0.0.0.0
advertisedAddress=192.168.1.10
bindAddress 决定了 Broker 监听的网卡地址。很多内置脚本默认绑定的是 0.0.0.0。但如果你改过 hostname 或者配置过 advertisedAddress,而实际 IP 与 advertised 不一致,客户端和管理后台都可能出现能连通但路由不对的问题。
另一个容易被忽略的是防火墙。很多云服务器默认开启了安全组,但本地测试时大家习惯用 systemctl stop firewalld 直接关掉防火墙。如果不想关,至少要放行 8080 和 6650 两个端口:
bash复制firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --permanent --add-port=6650/tcp
firewall-cmd --reload
4.3 管理后台需要额外启用函数与代理组件
Pulsar 的管理后台不是像 Jenkins 那种独立的 Web 应用,它本质上是 Broker 的 REST API 的图形化界面。默认情况下,Pulsar Manager 是独立部署的,它需要连接 Broker 的 Web Service。
如果你用的是较新版本的 Pulsar,并且通过 pulsar-manager 启动后台,还需要注意:
pulsar-manager默认端口是 9527,不是 8080。- 首次登录需要设置一个管理员账号,这一步在界面上有引导,但如果数据库环境有问题,可能会卡在登录页面。
- 登录成功后要配置环境信息,包括“服务地址”和“Web 服务地址”,这里填的必须是 Broker 的
advertisedAddress,不能填localhost,否则在跨机器场景下会连不上。
从经验看,管理后台进不去 80% 是网络和地址配置问题,跟 Pulsar 本身组件关系不大。先把端口监听、绑定地址、配置文件差异这三点排查完,基本能解决绝大多数情况。
4.4 部署模式选择:standalone、集群还是 K8s
这次活动上也有不少人问“生产环境到底用哪种部署方式”。我的建议很直接:
- 本地体验、学 API:用 Standalone 模式,一条命令起全部组件,适合快速跑 Demo。
- 小规模生产(日消息量千万级以下):用裸机 + 二进制包部署 3 个 Broker + 3 个 BookKeeper + 3 个 ZooKeeper,这个规模能覆盖大部分中小团队。
- 云原生环境:用 Helm Chart 部署到 Kubernetes,利用 PDB、HPA 做弹性,但前提是团队对 K8s 的运维能力足够。
不要一上来就追求 K8s 部署,Pulsar 本身组件多,K8s 里的网络互通、存储类配置、PVC 调优都会影响集群稳定性。先跑通裸机集群,再上容器化,踩坑成本会低很多。
5. 社区里被问爆的 Pulsar 常见问题与实战避坑清单
活动的后半段更像是一场开放的“义诊”,很多开发者直接带着自己的集群问题来现场讨论。我整理了几类最高频的问题,这些也是我过去在社区里反复看到的内容,一次性集中回答。
5.1 关于消息积压与消费停滞的判断
“消费者明明在线,但消息一直堆积不消费”——这类问题几乎每周都有人问。原因通常不是网络,而是消费模型配置错误。最常见的是:用了 Exclusive 订阅,但消费者以集群模式启动了两个实例。此时两个实例只有一个能真正消费,另一个在等待,消息积压就是必然结果。
排查方法很简单,在管理后台查看订阅的 Consumers 列表,确认当前的消费者数量和在线状态。如果发现消费者数量异常,先检查订阅模式。
5.2 关于消息不丢失的配置组合
消息不丢失是一个组合问题,光靠 Broker 端配置不够。至少要保证三条链路上都做了持久化配置:
- Producer 端:设置
sendTimeout(0)、开启重试、enableBatching(false),等待send()返回结果。 - Broker 端:设置
ackQuorum大于等于 2,writeQuorum为 3,并关闭自动创建 Topic 的懒迁移行为。 - Consumer 端:使用手动 ack,不要在回调里直接
acknowledge前抛出异常,否则可能造成消息未消费但被确认。
5.3 关于 Topic 数量膨胀的治理
Pulsar 比较灵活,允许动态创建 Topic,这在开发期很方便,但在生产环境会带来 Topic 数量爆炸的风险。因为每个 Topic 都可能有自己的 Ledger、订阅和保留策略,Topic 过多会让 BookKeeper 的碎片文件增多,影响读写性能。
建议从一开始就用命名空间隔离环境,并开启 Topic 自动创建的白名单策略。比如只允许指定前缀的 Topic 自动创建,其他一律显式创建。
bash复制bin/pulsar-admin namespaces set-auto-topic-creation \
--enable --type non-partitioned --allow-non-partitioned \
persistent://public/default
这样既能保留灵活性,又不会让集群失控。
6. 从一场开发者日看 MQ 技术选型的未来趋势
站在活动末尾,我想聊聊更大一点的观察。这次 COSCon‘25 与 Pulsar Developer Day 2025 的同场,本身就是一种信号:开源社区正在把“消息队列”从一个独立的中间件品类,推向“数据基础设施”的核心位置。未来的业务系统里,MQ 不只是削峰填谷的工具,它更需要具备流式计算、事件驱动、多租户资源隔离、云原生弹性等一系列能力。
我个人的判断是,接下来的三年里,MQ 选型的核心指标会从“吞吐量”转向“运维心智负担”。所谓运维心智负担,是指团队为了维持一个消息系统的稳定,需要投入多少人力去处理分区重平衡、磁盘均衡、扩容迁移、消费堆积等问题。Pulsar 之所以能在这波讨论中脱颖而出,是因为它在架构层面把很多运维问题前置解决了。
当然,Pulsar 也不是银弹。它的学习曲线比 Kafka 陡,组件多,部署复杂,在超大规模场景下的调优经验还不如 Kafka 丰富。但这些短板正在被社区快速弥补——只用看这次 Pulsar Developer Day 的上座率就知道了:愿意深入了解 Pulsar 的人,正在从“围观”变成“落地”。
最后再分享一个我自己的实践心得:如果你所在团队正在做 MQ 选型,不要只对比基准测试的数字,花一周时间把你的核心场景(订单、支付、日志、事件)按要求迁移到 Pulsar 上跑一遍,重点看消息积压恢复、扩容时间、故障恢复三个场景的表现。数字不会骗人,但只有亲手跑过,才知道哪个系统真正适合你。
