
每年开源大会扎堆的时候各种同场技术专场就成了圈内人真正盯着的目标。今年 COSCon 2025 的 Pulsar Developer Day 议程一放出来好几个做消息中间件选型的朋友就转给我看问得最多的无非是几个老问题Pulsar 到底比 Kafka 强在哪网上资料怎么感觉没有 Kafka 多还有人不理解收到的 Message ID 为什么是28077:20854:0这种奇怪格式。这篇文章就从开发者日和议程背后的技术趋势说起把 Pulsar 的消息模型、Message ID 结构、快速上手路径和排查经验一次讲透适合正在选型、刚接触 Pulsar 或者已经在生产环境踩坑的开发者参考。1. 从开发者日看消息中间件的技术风向1.1 为什么一个同场活动值得专门关注很多人可能还没意识到消息中间件这类基础组件早就不再是能用就行的阶段了。像 Pulsar Developer Day 这种能进入 COSCon 主会场的开发者专场本身就释放了一个信号消息系统已经从后端工程师的私有话题变成了整个技术圈都在关注的核心基础设施。你去现场听一圈就会发现讨论的话题已经从怎么搭个队列变成了怎么在多集群、多云环境下做到低延迟和高可用这完全是两个维度的东西。对开发者来说这类同场活动的价值在于密度高。一天之内你能听到真实生产环境的踩坑案例、核心维护者讲设计取舍、不同的技术团队分享各自的风控和拆分方案。这比你自己翻几个月文档都管用因为很多经验是文档里不会写的。举个例子Pulsar 的存储层基于 Apache BookKeeper这个设计解决了很多问题但也带来了新的运维习惯要求比如你不能再像看 Kafka 那样只看 broker 的磁盘水位还要关注 Bookie 的存储均衡。这种信息只有到了现场或者深入社区才能快速 get 到。1.2 选型绕不开的话题Pulsar 还是 Kafka每次一聊消息中间件Pulsar 和 Kafka 的对比就是必答题这次的议程里也有不少内容是在回应这种选型焦虑。先说结论两个项目都很成熟没有绝对的谁取代谁关键是场景对不对得上。Kafka 的优势非常明显生态最完整、中文资料最多、很多团队从 0.8 时代就在用踩坑经验前人早写完了。如果你只是做标准的日志管道、事件流分析Kafka 几乎是无脑选择。它的问题在于存储和计算是耦合的分区数上去以后broker 的迁移和扩容都比较重多租户和跨地域复制也不是它的强项。Pulsar 走的是另外一条路。它把 Broker 和存储层拆开Broker 本身无状态消息实际存在 BookKeeper 里这个架构带来了几个实打实的好处扩容时不需要搬迁消息数据新加 broker 就能接入流量存储和计算独立扩容存储量大的 topic 不会拖垮计算能力多租户隔离做得更细不同团队的 topic 可以设置不同的策略互不干扰跨地域复制是内置能力对全球化业务非常友好下面这个表可以比较直观地看出两者侧重点对比项KafkaPulsar存储与计算耦合分区迁移较重分离Broker 无状态多租户靠配额和 ACL 实现相对基础内置租户/命名空间策略更细跨地域复制MirrorMaker 等工具方案内置复制机制消息保留基于时间/大小删除支持分层存储和更灵活的保留策略消费模型基于 offset基于 Message ID 和 Cursor资料丰富度非常丰富官方文档系统社区资料在快速增长关于资料丰富度的问题我的看法是如果只看中文二手资料Kafka 确实多但 Pulsar 的官方概念文档写得非常清楚架构白皮书也值得反复读。你只要把 Ledger、Cursor、Subscription 这几个核心概念弄明白很多知识是可以从 Kafka 迁移过来的并不冲突。1.3 创新实践都在做哪几件事从议程的主题分布来看消息中间件目前的创新主要集中在三个方向。第一是云原生和 Serverless 化。Pulsar 的存储计算分离架构天然适合往云上搬很多团队在讨论怎样让 topic 按需扩缩容、怎样把资源成本做到按量付费。第二是多集群容灾和一致性。业务跨地域部署之后消息系统怎么保证数据不丢、顺序可控同时还要在机房故障时快速切换。第三是成本和可观测性。消息系统跑久了磁盘和带宽成本会非常扎眼怎么通过分层存储、压缩策略来控制成本同时又能让消息的流转状态被准确追踪这些都是真实痛点。议程里有人分享 Topic 数量从几百涨到几万之后的治理经验也有人讲如何把消费延迟控制在毫秒级这些内容背后其实都在解决同一个问题当消息系统成为业务主动脉之后稳定性和成本之间怎么找到最优解。2. Pulsar 核心原理与 Message ID 深度解析2.1 三层架构Broker、BookKeeper、ZooKeeper 各司其职很多刚接触 Pulsar 的人会被它的组件数量吓到觉得比 Kafka 复杂多了。其实拆开看逻辑非常清晰。Pulsar 分成了三层最上面是无状态的 Broker负责接收生产者和消费者的连接、处理各种协议中间是持久化存储层由 Apache BookKeeper 提供负责真正把消息落盘最下面是元数据服务传统上用 ZooKeeper 来管理整个集群的状态比如 topic 分布在哪些 broker 上、bookie 是否在线。为什么要把存储单独拆出来这个设计我打个比方你就明白了。Kafka 相当于每个饭店自己建一个仓库生意好了就得多建几个仓库还要把食材搬来搬去。Pulsar 的 BookKeeper 则像一个中央冷库每个饭店Broker只负责接单和出菜食材统一存在冷库里。这样饭店扩不扩张跟冷库容量的关系就没那么大了冷库里的食材还能被多个饭店共享。具体到 BookKeeper它的核心概念是 Ledger。一个 topic 的数据会被切分成多个 Ledger每个 Ledger 里的数据条目叫 Entry。这个设计带来了一个直接好处Pulsar 不用像 Kafka 那样需要顺序读写一整段的日志文件它可以并行写多个 Ledger扩容时也不用搬数据。你在 Pulsar 后台看 Topic 的读写速率时偶尔会注意到 Ledger 切换比如写满一个 Ledger 会自动创建下一个这在底层是非常高频且自然的操作。2.2 Message ID 为什么长这样ledgerId:entryId:partition现在来聊那个很多人问过的问题为什么 Pulsar 的 Message ID 是28077:20854:0这种格式。先说结论这个 ID 由三部分组成依次是ledgerId:entryId:partition-index。拿你看到的例子来说28077是这条消息所在的 Ledger 编号20854是这条消息在 Ledger 里的 Entry 序号最后的0代表它是这个主题的第 0 个分区。理解这个 ID 的关键是要忘掉 Kafka 里简单的offsetpartition思维。Kafka 的 offset 是一个分区内从 0 开始递增的序号你只要知道 offset 就能定位消息在日志文件中的大致位置。但 Pulsar 因为是存储计算分离消息存在 BookKeeper 的 Ledger 里光一个数字无法定位。它需要两个坐标一个指定在哪个 Ledger一个指定在 Ledger 里的哪一条 Entry。这就像你去图书馆找书不能只说给我第 100 层书架上的第 50 本书你还得告诉管理员是哪个书库Ledger这样两个坐标唯一确定一条消息。那个partition-index则对应分区编号。如果主题是非分区的这个值通常是-1。如果是分区主题第几个分区就会在这里体现。再说一个容易踩坑的点直接用 Message ID 做跨集群的判断没有意义因为不同集群的 Ledger 编号体系是独立的。同一个 topic 在两个集群里的同一逻辑消息Message ID 完全不同。所以在做容灾切换或者多集群同步时不要依赖 Message ID 做全局唯一标识该加业务主键的还是要加。2.3 从 Message ID 到消费位点Cursr 和订阅模型Message ID 除了用来定位消息还有一个重要作用管理消费位点。Pulsar 里叫 Cursor它记录的是当前订阅已经消费到哪个位置。和 Kafka 的消费组 offset 不同Pulsar 支持一个主题上同时挂多个订阅每个订阅可以有自己独立的消费进度。这意味着同一份数据可以按不同业务需求分别消费多遍这在 Kafka 里需要做配置在主题上建多个消费组但 Pulsar 把这件事做成了订阅层面的灵活机制。订阅模型有四种选错会直接影响使用效果Exclusive独占订阅一个 topic 同时只能有一个消费者在消费适合严格顺序场景Failover故障转移多个消费者同时连接但只有主消费者在处理主节点挂了才切换Shared共享订阅消息在消费者之间轮询分发处理能力强但完全无序Key_Shared按 key 分发同一个 key 的消息永远进同一个消费者兼顾顺序和并发实际项目中Shared 是很多团队默认的选择因为它能最大化吞吐。但如果你对顺序要求高比如一个订单的创建和取消消息必须被同一个消费者按序处理那就要用 Key_Shared 或者 Failover 的独占语义。我见过不少线上问题都是因为用了 Shared 订阅处理订单状态变更导致同一条业务链的先后顺序乱了排查起来非常痛苦。所以在创建订阅之前就要想清楚顺序和并发之间的边界。3. 快速上手 Pulsar 的实操路线3.1 本地环境搭建与基础验证如果只想在本地把 Pulsar 跑起来最快的方式是 Docker。先拉镜像然后以 standalone 模式启动这个模式把 Broker、BookKeeper、ZooKeeper 都集成在一个进程里方便开发调试。docker pull apachepulsar/pulsar:3.3.0 docker run -it \ -p 6650:6650 \ -p 8080:8080 \ apachepulsar/pulsar:3.3.0 \ bin/pulsar standalone启动成功后6650 是客户端连接的端口8080 是 admin 接口。这时候可以开一个新的终端进容器用自带的命令行工具做一次生产消费的验证。# 进入容器 docker exec -it 容器ID /bin/bash # 创建主题 bin/pulsar-admin topics create persistent://public/default/quickstart-topic # 生产消息 bin/pulsar-client produce quickstart-topic --messages hello pulsar, from coscon # 消费消息 bin/pulsar-client consume quickstart-topic -s first-subscription看到消息能正常收发说明环境没问题。接下来可以拉一个 Python 客户端做简单的代码验证Pulsar 支持的语言客户端很多Python 是最容易上手的。import pulsar client pulsar.Client(pulsar://localhost:6650) # 生产者 producer client.create_producer(quickstart-topic) producer.send((hello from python writer).encode(utf-8)) # 消费者 consumer client.subscribe(quickstart-topic, first-subscription) msg consumer.receive() print(msg.data()) print(msg.message_id()) # 看这里就能拿到类似 ledgerId:entryId:partition 的 ID consumer.acknowledge(msg) client.close()我建议你重点看一下msg.message_id()的输出这能帮你直观理解上一节说的 Message ID 结构。第一次跑通了之后再去读那些概念性的文档会顺畅非常多。3.2 生产与消费配置的关键参数本地跑通只是第一步真要上生产有几个参数配置必须心里有数。生产者的核心参数是batchingEnabled、batchingMaxMessages和pendingQueueSize。默认情况下 Pulsar 会开启批处理把多条消息打包发送吞吐会高很多但延迟会略微增加。如果服务里有对延迟极敏感的业务比如实时风控或交易回调建议对相关主题关闭批处理或者把批量大小调小。pendingQueueSize表示生产端在未收到确认前可以积压多少条消息调太大会增加内存压力调太小在高吞吐场景容易触发背压。消费者的关键参数是receiverQueueSize和maxTotalReceiverQueueSizeAcrossPartitions。这个值表示消费者本地最多预取多少条消息。预取多了吞吐高但消息堆积在客户端服务重启时可能会有大量消息被重新拉取造成重复消费预取少了吞吐上不去在高延迟网络环境尤其明显。通常在 1000 到 10000 之间根据实际压测结果来调。还有一个容易被忽略的ackTimeout和negativeAckRedeliveryDelay。生产环境偶尔会碰到消费线程卡死的情况如果一直不 ackPulsar 会根据超时机制重新投递消息。超时设得太短处理稍慢的消息会不断被重新投递产生大量重复消费设得太长消费卡死时要等很久才会触发重试。我习惯把 ackTimeout 设为业务正常处理耗时的 5 到 10 倍同时配合死信主题把多次重试仍失败的消息隔离出去。3.3 主题策略与保留机制设置Pulsar 的一个优势是可以针对命名空间或主题做细粒度策略管理。常见的配置有message-ttl消息存活时间、retention消费者确认后消息保留时长、backlog-quota积压上限。这个组合非常灵活但也很容易误解。举个例子你设置message-ttl为 10 分钟意思是这个主题里没有被消费的消息10 分钟后会自动变成已跳过状态并对新消费者不可见。但如果你设置了retention策略已经被消费和确认过的消息可以继续保留一段时间供后续重新拉取或做数据回溯。这两个变量很多人会搞混实际效果天差地别。# 设置消息 TTL 为 1 小时 bin/pulsar-admin namespaces set-message-ttl public/default --ttl 3600 # 设置消息确认后保留 24 小时并限制最大大小为 1GB bin/pulsar-admin namespaces set-retention public/default \ --size 1G --time 24h如果你有数据回溯的需求建议把 retention 时间设置得比业务审计周期稍长一些。比如业务每个小时会跑一次数据核对那就至少保留 24 小时给排查问题留出余地。但如果是高吞吐的日志类主题无脑保留大量数据会迅速吃光 Bookie 磁盘这时候把 retention 设置为 0只依赖 TTL 控制生命周期反而是更合理的做法。4. 高频问题排查与避坑指南4.1 消息积压应该怎么查消息积压是消息中间件最层出不穷的问题。我的排查顺序是固定的先看积压量再定位是生产快还是消费慢最后找瓶颈。在 Pulsar Admin 里可以直接看到主题的积压状态bin/pulsar-admin topics stats persistent://public/default/business-topic重点关注backlogSize和backlog两个字段。如果积压持续增长先看消费者的并发数和处理耗时。如果消费者本身有外部 IO 或调用下游服务通常是下游响应变慢导致消费整体拖慢。这时候最直接的办法是增加消费者实例或者把部分流量切到新的主题。但注意不要只是盲目增加分区如果下游数据库扛不住加分区反而会把压力放大。还有一种情况是生产端突然写入量暴增导致消费端怎么都追不上。可以先看 topic 的msgRateIn和msgRateOut的差值。如果生产速率远高于消费速率而且短时间内无法完成扩容可以考虑先降级部分非核心业务对消息的依赖比如把日志类消息暂时切到另一个低优路径保证核心链路不丢消息。4.2 重复消费与消息乱序这是两个看起来类似但原因完全不同的经典问题。重复消费的根源一般是 ack 丢失。消费者处理完业务逻辑但还没来得及 ack网络抖动或者进程重启Pulsar 就会把这条消息重新投递给另一个消费者实例。这不是 Pulsar 独有的问题任何消息系统只要禁止 at-least-once 语义都会引入这个现象。解法只有一个消费端一定要做幂等。基于业务唯一键去重而不是基于 Message ID 去重。因为同一逻辑消息重投时Message ID 通常不变你可以用 Message ID 做第一层拦截但最终一致性还是得靠业务表里的唯一索引保证。消息乱序的情况更多和订阅模型相关。如果你用Shared订阅消息本身就是轮询分配的顺序自然没保证。如果要用Key_Shared订阅来保证同一个 key 的顺序要注意 key 的分布是否均匀。比如某个热点用户的订单量极大hash 到同一个消费者后其他消费者都在围观总体吞吐反而会被这个热点约束住。这种时候只能做业务层面的拆分让热点 key 进一步细分。4.3 客户端连接与鉴权踩坑很多团队第一次部署 Pulsar 集群时会在鉴权配置上卡住。Pulsar 的鉴权体系和 Kafka 有些类似但配置项更多。简单来说你需要为 broker 开启认证并配置授权设置。# broker.conf 中相关配置 authenticationEnabledtrue authorizationEnabledtrue authenticationProvidersorg.apache.pulsar.broker.authentication.AuthenticationProviderToken tokenSecretKey...客户端连接时需要传入 token否则会报AuthenticationError。有些朋友本地调试时明明没配鉴权也会遇到连接被拒的问题那通常是因为客户端用了pulsarssl://协议去访问普通端口。我用过一个比较省心的方式先在本地全部用明文连接跑通功能等要上生产前再统一处理 TLS 和 token把协议换掉。这样能先把业务逻辑的问题排查完毕再面对加密和鉴权的额外复杂度。4.4 学习资料怎么看最有效回到那个经常被问到的问题Pulsar 和 Kafka 哪个资料更丰富应该先学哪个。我的建议是不要把它们对立起来。Kafka 的书籍和视频量大但质量参差不齐很多讲的还是早期版本。Pulsar 的官方文档、架构设计文档以及 StreamNative 那套教程相对更新质量也更稳定。如果你完全没接触过消息中间件可以先找一本 Kafka 的入门书建立基本概念了解 topic、partition、consumer group然后再看 Pulsar 里对应的概念是怎么设计的。这种对照学习法效率最高因为很多名词你已经在 Kafka 那边理解了换到 Pulsar 只需要关注它们为什么不同。至于英文资料Pulsar 官方博客、社区提案PIP和核心维护者的分享都值得读尤其是 PIP那是理解 Pulsar 后续演进方向的第一手来源。日常有问题先查官方文档解决不了再去 GitHub Discussion基本不会走偏。回到 Message ID 那个疑问当你理解了 Ledger、Entry 和存储分离架构之后再看到那一串数字就不会慌了。它不是一个随机的哈希而是消息在 BookKeeper 存储空间里的坐标。这个坐标让 Pulsar 可以做到独立扩容和精确回溯是它相对传统队列的一大核心差异。对我个人来说选型时真正让我倒向 Pulsar 的并不是某个功能介绍而是这些底层机制能不能让我在业务快速增长时不那么焦虑。消息系统的选型往往到最后就是在选一种长期运维的确定性。希望这篇文章能把 Pulsar 最核心的概念和实操路径讲清楚让你上手的时候少走几步弯路。