周末的日程表上最让我期待的一件事就是去 COSCon25 开源集市上找 Apache Pulsar 的展台。作为一个从 Pulsar 2.x 时代就开始在生产环境折腾消息队列的老用户这几年我对它是又爱又恨。爱的是它的架构确实先进多租户、分层存储、跨地域复制这些特性在同类的消息中间件里几乎找不到对手恨的是它的调优成本和排障成本都不低尤其是某些消费模式在不恰当的版本和配置下会冒出一堆让人摸不着头脑的怪问题。借着这次开源集市的机会我正好把最近踩过的一个大坑——Pulsar 的 KeyShared 模式不消费的问题——完整复盘一遍也算给准备去展台交流的朋友们提前交个底。这篇内容我会分成两个部分来聊先说说 Pulsar 这个项目本身为什么值得你在 COSCon25 的集市上多停留几分钟再重点拆解 KeyShared 模式在实际生产里不消费的 bug 现象、排查思路和最终修复方案。看完之后你至少能在展台前和技术 committer 聊出几个有深度的问题而不是只拿几张贴纸就走。1. 为什么这周末要去 COSCon25 开源集市的 Pulsar 展台1.1 先搞清楚 Pulsar 到底解决了什么问题很多人第一次接触 Pulsar 的时候第一反应是“这不又是一个 Kafka 吗”。如果你也这么想那说明你还停留在消息队列 1.0 的认知层面。Pulsar 最重要的是把计算和存储彻底拆开了它底层的存储引擎是 Apache BookKeeperBroker 层只负责消息的路由、订阅管理、鉴权这些事情不存数据。这意味着什么意味着你在扩容的时候不用像 Kafka 那样把整个分区迁来迁去只要加 Broker 节点就能水平扩展数据落盘也可以做到真正的多副本强一致不像某些系统那样把“可能丢失消息”当作 trade-off 写在文档里。再说多租户Pulsar 里的 namespace 和 topic 可以做到非常细粒度的策略隔离企业里不同部门、不同业务线共用一个集群是完全可行的这在成本和运维资源上能省下一大截。还有一个我在生产里特别点赞的功能是分层存储你可以把很久以前的消息自动卸载到对象存储里本地只保留热数据这让 topic 的保留期从“几天”直接变成“几十年”而且完全不影响在线读写性能。如果你所在的团队正在做微服务架构改造、事件驱动架构落地或者需要一套能同时处理流式和队列场景的消息平台Pulsar 绝对应该在你的选型清单里。在 COSCon25 开源集市上Pulsar 社区的伙伴们会直接把整个运行架构用可视化的方式展示出来你甚至可以去他们的展台看真实的集群监控面板了解生产环境的部署规模。1.2 COSCon25 开源集市上 Pulsar 社区准备了什么COSCon 是开源社主办的中国开源年会开源集市是历届大会里最有烟火气的一块区域。跟正式的会议室演讲不同集市上的项目方会把展位布置得很活络你可以直接看到 committer 和 Maintainer 本人在那里坐着手里拿着贴纸、文化衫随时准备跟你从架构设计聊到社区八卦。Pulsar 在今年 COSCon25 开源集市上的展台按照社区提前公布的信息会有几个比较实在的内容。首先是项目全景图展示从 Broker 到 BookKeeper再到 Pulsar Proxy、Function、IO Connector你可以在现场看一遍完整的组件地图理清楚每个模块的作用。其次是现场答疑社区会安排有生产环境经验的 contributor 轮值你有什么部署、升级、性能调优的问题直接带到现场聊比在 GitHub 上等 issue 回复要高效得多。我听说还会有针对新手的快速上手演示用 Docker 在五分钟内拉起一个本地 Pulsar 集群生产消息、消费消息、查看监控一条龙走完。对于想入门但一直没动手的人来说这种 hands-on 的体验比看文档有效多了。1.3 集市交流时最容易问出价值的问题说实话在开源集市的展台前匆匆忙忙问一句“Pulsar 和 Kafka 有什么区别”得到的答案只能是大路货。真正有价值的交流是把你自己遇到的真实场景抛出来。我建议你逛展之前先想清楚自己团队当前在消息中间件上最痛的点是什么是消费积压不可控是分区扩容要搬迁还是消息回溯的能力不够带着具体问题去你才能从 committer 嘴里听到那些写在源码注释之外的经验。比如你可以问“KeyShared 模式在消费者数量动态变化的时候哈希区间是怎么重新分配的”这个问题隔三差五就会在社区里被翻出来。你也可以问“BookKeeper 的读写线程数到底该怎么配置才能压满磁盘 IO”这种问题没有标准答案但现场能聊出来的实战调整思路比你看十篇性能调优文章都管用。后面我会详细讲的 KeyShared 不消费问题其实就是这类“问题不在文档里、只在事故复盘里”的典型案例。2. KeyShared 模式Pulsar 里最容易被误用的消费模式2.1 KeyShared 想解决什么问题要说清楚 KeyShared 不消费的 bug先得明白它到底是怎么运作的。Pulsar 默认提供了好几种订阅模式Exclusive 是独占一个订阅下只有一个消费者能拉消息其他消费者闲着Shared 是所有人都能拉消息在消费者之间均匀分发Failover 是主备模式主消费者挂了备消费者接管。KeyShared 这个名字听起来很高级简单理解就是相同 key 的消息永远只发给同一个消费者。这样做的好处是你能在多个消费者并行处理的前提下保住某个 key 维度上的消息顺序。举个例子订单系统里同一笔订单的“创建”“支付”“发货”三个事件key 都是订单号。如果你用普通的 Shared 模式这三条消息很可能被分给三个不同的消费者订单状态就乱了。用 KeyShared 模式只要 key 相同它们会一路严格有序地进入同一个消费者你的处理逻辑完全不用考虑并发竞争。很多团队一看到“保序又并行”这个卖点就迫不及待地把线上所有 topic 都切成 KeyShared。这个模式确实解决了 Shared 模式的无序问题但它背后的哈希分配机制比看起来要复杂得多一旦配置不当或者遇到特定版本的缺陷就会出现我在标题里提到的“不消费”情况。2.2 从哈希分配看 KeyShared 的实现原理KeyShared 的核心机制是对消息 key 做哈希计算然后按照哈希值的范围把消息分给不同的消费者。最常见的哈希策略有两种一种是 AUTO_SPLITBroker 会根据当前消费者的数量把整个哈希环自动切分成 N 段每个消费者负责其中一段另一种是 STICKY哈希环的切分范围比较固定消费者变更时不轻易做重新分配。AUTO_SPLIT 模式的逻辑看起来很美消费者多了分段就多消费者少了分段就少。但这里有一个隐藏的难点分段变化的时候Broker 必须把原来属于某个消费者的哈希区间完整地转移给另一个消费者。谁转移给谁、什么时候转移、转移过程中那些还没消费的消息怎么办这一套流程在 Pulsar 某些版本里并没有处理得足够健壮。KeyShared 对订阅名称也有要求。同一个订阅下的消费者如果想启用 KeyShared必须显式地在客户端配置里声明使用 KeyShared 订阅类型而且所有消费者的配置要一致。如果有一个消费者没启用Broker 端在创建订阅时会直接报错或者做出很奇怪的行为这种坑在初用者身上非常常见。2.3 什么场景才适合 KeyShared这里我建议所有团队在选型前冷静三秒钟。KeyShared 不是默认选项它的适用场景有明确边界。第一种是消息处理确实需要 key 级别的顺序保证同时单 key 的数据量又不够大不值得单独开一个分区第二种是消费者数量可以接受动态调整并且业务能容忍短暂的重新平衡。什么场景不适合高吞吐的日志传输、无顺序要求的事件流、对消息乱序不敏感的任务队列这些都应该老老实实用 Shared 模式。因为 KeyShared 的哈希分配本身有计算开销Broker 在路由时要多做一步哈希计算和区间匹配吞吐量会受一定影响。更重要的是一旦某个 key 的消息特别多它所在的哈希区间对应的消费者会成为热点其他消费者在那边闲着没有什么负载均衡机制能帮你自动拆掉这个热点。我见过不少团队把 KeyShared 当万能药最后在压测阶段就发现消费速度上不去因为数据分布天然倾斜。你去看 Pulsar 官方文档它也会反复强调 KeyShared 不是 Shared 的替代品而是为特定场景设计的一个补充选项。3. 实战复盘一次 KeyShared 模式不消费 bug 的完整排查3.1 故障现象积压和空闲同时出现上个月我们生产环境突然收到一条告警某个核心业务 topic 的消息积压量在半个小时内从几百涨到几十万。这个 topic 用的是 KeyShared 订阅模式8 个消费者在跑。按道理来说几十万积压对于 8 个消费者来说也就几分钟的事情但诡异的是消费者这边完全看不到消息在流动消费速率为零CPU 占用率几乎为 0日志里也没有任何报错。这种“Broker 端积压严重消费者端却闲得要命”的状态就是标准的不消费状态。先说清楚这不是消费者业务逻辑卡住了因为如果是业务处理慢你会在消费者日志里看到数据一直在拉取和处理只是处理不过来。而我们的现象是消费者进程完全空闲它在等数据但数据却不来。这说明问题出在 Broker 让谁来消费这一层也就是订阅模式的调度逻辑上。我当时的第一个预感就是 KeyShared 的哈希区间出了问题这种问题我在社区 issue 里见过很多次。但为了不冤枉它我还是按部就班地走了一遍完整的排查流程。3.2 确认消费状态先排除业务侧假死排查的第一步不是去看 Broker 日志而是先确认消费者到底处于什么状态。因为“不消费”这件事变量太多了有可能是网络断流、连接被 Broker 踢掉、consumer 自己进入了某种阻塞状态等等。我先在 8 个消费者节点上看了进程状态jstack 了一把发现所有消费者线程都阻塞在 Pulsar 客户端的内部拉取调用上没有活跃的业务处理线程。这个信号很关键说明消费者完全没有拿到消息。然后我检查了 Pulsar 客户端的连接状态通过 admin 接口查询这个订阅的消费者列表看到 8 个消费者全部在线而且都处于 active 状态。这就更迷惑了消费者在线、连接正常、没有报错但就是拉不到消息。从表面配置和数据上看一切正常一定是底层某个环节发生了“假活”的情况。接着我需要确认这 8 个消费者是不是都拿到了哈希区间。KeyShared 模式下broker 的抽象里每个消费者都对应一个或多个哈希区间消息只会投递给哈希区间匹配的消费者。如果某个哈希区间没有对应任何在线消费者那这个消息就会被一直挂起。3.3 从 Broker 侧看游标揪出失联消费者既然消费者侧证明不了什么我立刻切到 Broker 上去看订阅游标的状态。这里用的是 pulsar-admin 的命令行工具。我先查看了这个 topic 的订阅详情重点关注游标中记录的消息的 range 信息。在正常情况下8 个消费者的 KeyShared 订阅会把哈希区间均匀切分成 8 段每段对应一个消费者。但我在输出里看到了一个让我后背发凉的信息Broker 内部记录的哈希区间有 9 段而不是 8 段。多出来的那段区间它的 owner 是一个已经不存在的消费者。换句话说之前可能有一个第 9 个消费者因为某种原因退出了但它负责的哈希区间没有被 Broker 重新分配而是成了一个“无主区域”。消息积压的那段时间业务方正好在这个 topic 上重启过一次消费者应用。重启过程中旧的消费者连接有一个优雅退出的短暂窗口但新的消费者还没来得及建立连接。正常情况下KeyShared 模式应该感知到消费者数量变化把无主区间重新分配给现有消费者。但在我们使用的 Pulsar 版本里这个重分配动作没有正确触发。本来这个 bug 不会持续很久因为 client 端会自动重连并触发重新协商但偏偏我们某个消费者在重连的时候网络抖动导致它被 Broker 判定为异常连接被关闭哈希区间又被卡在了半路。3.4 根因定位哈希区间没有随消费者变化而重分配到这里根因已经比较清楚了Broker 的内存里有一块哈希区间它的 owner 消费者已经不存在但 Broker 既没有把它分配给别的消费者也没有触发重新平衡。结果就是任何 key 的哈希值落在这个无主区间范围内的消息都会被 Broker 判定为“没有可投递的消费者”于是这些消息就一直留在 backlog 里越积越多。为什么会出现哈希区间不重分配的问题这涉及 Pulsar 的 KeyShared 实现细节。在 AUTO_SPLIT 策略下Broker 维护了一个 Consumer 和 HashRange 的映射关系。当消费者增减时Broker 需要重新计算哈希区间的切分这个过程要加锁、要更新游标元数据、还要向所有消费者推送新的 hash range 信息。任何一步失败都可能导致区间表和实际的消费者列表不一致。而且这个不一致的状态不会自动自愈除非你重启订阅或者手动触发一次重平衡。我们用的这个版本里还有一个额外的触发条件消费者在重连过程中使用了不同的客户端版本或者配置了不同的 KeyShared 策略导致 Broker 在协商时把新连接当成了完全新的消费者而不是旧消费者的续接。这就会在消费者列表里出现一个“断开但未清理”的残留项它的哈希区间也就成了幽灵区间。3.5 修复与规避升级加配置两手都要硬定位到根因之后我做了几步处理。最直接的办法是重启所有消费者让 Broker 重新做一次 KeyShared 协商把哈希区间重新分配。这个操作在几分钟内恢复了消息消费积压以肉眼可见的速度下降业务恢复了正常。但这是治标不是治本。我随后在测试环境复现了一遍操作确认只要消费者重连时发生连接抖动故障就可能再次出现。真正的修复方案有两部分。第一升级 Pulsar 版本到包含 KeyShared 重平衡修复的版本。这个问题在社区里并不是个秘密早在 2.10 之后的一些版本中社区就针对 KeyShared 的区间管理逻辑做了很多修补尤其是在消费者异常断开后的清理机制上。第二在代码层面做防御。我给消费者客户端设置了合理的 reconnect 策略避免频繁重连同时把 KeyShared 策略固定为 STICKY 模式减少 AUTO_SPLIT 模式在消费者变化时触发重平衡的复杂度。如果你在公网或者不可靠的网络环境里使用 Pulsar我建议慎用 AUTO_SPLIT因为消费者连接越不稳定它需要做的重平衡就越多出错的概率也就越高。这个话题我在 COSCon25 展台上一定会跟社区伙伴再好好对线一次看看最新版本在这块的健壮性到底改善了多少。4. KeyShared 常见问题速查与避坑清单4.1 典型问题速查表那次故障之后我把 KeyShared 常见的坑整理成了一张速查表分享给团队和身边用 Pulsar 的朋友。这里也给正在读这篇内容的你问题现象可能原因处理建议消费者在线但消息不消费哈希区间无主消费者列表与区间表不一致重启消费者触发重协商或升级含修复的版本部分消费者负载特别高key 数据倾斜热点 key 集中在某个哈希范围先评估是否适合用 KeyShared必要时改用 Shared 业务侧排序消费者启动时报错订阅已存在但客户端未启用 KeyShared 策略所有消费者统一设置 KeyShared 订阅类型且配置一致消息偶发乱序同一个 key 的哈希落在不同区间确认是否启用 BatchingKeyShared 与批处理有交互限制积压时消费者只有个别在拉消费者数量变化后重平衡未完成检查 Broker 日志中的 KeyShared 重平衡信息手动触发或升级这张表不能解决所有问题但能够帮你在故障发生时快速收敛排查范围。Pulsar 的官方文档把 KeyShared 描述得很轻巧但你在生产环境里用它之前必须把上面这些边界情况都想清楚。4.2 关键配置检查清单除了排查问题我更推荐你在上线前就把 KeyShared 的配置检查好。首先是订阅名称同一个 KeyShared 订阅下的所有消费者必须使用同一个 subscriptionName而且建议在客户端显式传 KeyShared 策略不要依赖默认值。其次是允许消费者数量变化的频率。如果你知道自己的消费者会频繁扩缩容那最好把 KeyShared 的哈希策略配置成 STICKY并且设置合理的 hashRange 分配策略让区间变化尽可能小。还要设置消息的 key 路由规则确保业务里真正需要保序的消息 key 是有限且可控的不要让每个消息都生成一个唯一 key那样 KeyShared 就退化成了 Shared而且多付出了哈希开销。我在生产里的建议是消息 key 的基数控制在消费者数量的几十倍以内太多会导致区间重排时大规模消息重路由太少会负载不均。这个经验不一定适合所有人但你可以在测试环境通过监控消费者之间的消息量差值来验证差值长期超过两倍就该调整了。4.3 从这坑里学到的调试方法这次排查 KeyShared 不消费的过程也让我养成了几个调试 Pulsar 的好习惯。第一任何“看起来没问题但确实有问题”的场景先去看 Broker 端的订阅游标元数据而不是盯着消费者日志。消费者在线不代表它能拿到消息Broker 的视角才是最终真相。第二Pulsar 的 admin 命令是排查的利器topic stats、subscription stats、consumer 详情这三个命令要成为你的肌肉记忆。第三也是最重要的不要在生产环境使用你从来没在故障注入测试中验证过的消费模式。KeyShared 这个模式在常规操作时非常稳定但一旦发生消费者异常断连、网络分区、Broker 重启这些故障时它的表现就非常依赖版本质量。你在 COSCon25 展台前问不问这个问题社区 committer 都会告诉你同样的话新版本一定比老版本稳升级请务必重视。5. 集市现场聊技术也聊周边5.1 展台互动与主题分享这次 COSCon25 开源集市上Pulsar 展台除了常规的项目展示我了解到社区还准备了一个很有意思的互动环节叫“消息漂流记”。他们会模拟一个消息从生产者发布经过 Pulsar 集群的 Broker、BookKeeper最后到消费者消费的完整链路你在现场可以亲手点击每一步看到消息的实时状态。这种互动方式非常直观尤其适合从来没接触过 Pulsar 的新手。如果你是对 KeyShared 这类话题感兴趣的资深用户展台也会贴出过去一年社区处理的高频 issue 合集其中就包括了我这次遇到的 KeyShared 不消费问题。你完全可以指着 issue 列表里的某个问题直接问在场的 contributor 是怎么修的、修复之后如何验证的。这种程度的交流才是逛开源集市最大的收获。我也听说展台旁有专门的 One More Thing 环节每天会有一位 Pulsar 的 committer 做一次 15 分钟的闪电分享主题包括 Pulsar 在 AI 场景下的应用、Pulsar 和 Flink 的集成实践、以及云原生环境下的部署最佳实践。时间不长信息密度很高建议你提前查好日程别错过。5.2 给到访者的一些小建议作为每年都逛 COSCon 集市的老油条我给你几条实用建议。第一早点到开源集市的数量有限Pulsar 这种热门项目的周边基本第一天上午就会发光想要文化衫的千万别睡懒觉。第二带个自己的项目名片或者 GitHub 主页二维码跟 committer 交流的时候直接亮出来比口头介绍自己做了几个项目高效太多了。你甚至有可能因为一次现场交流获得一个参与社区贡献的入口。第三参与现场互动之前稍微准备一两个有深度的问题千万不要只去拍个照就走那真的浪费了这个机会。如果是刚接触 Pulsar 的新手我建议你在展台先看一轮十分钟的 demo然后拿一份社区整理的学习路线图上面会标注好推荐的文档、样例代码和入门项目。拿回去以后按照路线图走一遍基本就能在你自己的电脑上把 Pulsar 跑起来了。5.3 为什么每个开源爱好者都该逛一次开源集市很多人觉得开源大会听各种 keynote 就是全部了但我个人觉得开源集市才是开源精神的实体化体现。在集市上你看到的不只是代码和项目而是一群愿意花周末时间把技术做成有趣内容的人。Pulsar 的展台也好COSCon25 的其他项目展台也好它们都在用各自的方式告诉你开源这件事是可以很具体地参与到里面去的。你在 GitHub 上给一个项目点 star、提 issue、提交 PR可能感觉还是很遥远。但在集市上你和一个 committer 面对面聊了十分钟他说“这块代码是你提的那个 issue 改的”那种连接感是完全不同的。这也是为什么我会建议所有做技术的人都至少去逛一次开源集市——你可能不会立刻成为某个项目的核心贡献者但你会开始理解这些每天都在用的工具背后是一群什么样的人他们在想什么他们的热爱从哪里来。6. 写在最后带着问题去集市回头看我这次 KeyShared 不消费的排查过程从现象出现到最终定位前后花了几个小时。真正难的不是修复而是在“消费者在线但消息不来”这种矛盾现象面前保持冷静一层层剥开表象。Pulsar 是一个上限很高的项目它的架构设计足够先进但它对使用者的要求也不低。你需要理解它的存储、路由、订阅机制才能在遇到问题时不被表象迷惑。这次去 COSCon25 开源集市我给自己定的目标是跟 Pulsar 社区的 committer 聊透三件事KeyShared 最新版本的区间重平衡实现、Pulsar 是否计划支持更细粒度的消费路由策略、以及社区对生产环境长期运行版本的建议。如果你在集市上看到一个在展台前蹲着看监控面板的人那大概率就是我欢迎过来一起聊聊你在 Pulsar 上踩过的坑。这周末带上你的问题到开源集市走一趟吧。你带走的可能不只是贴纸和纪念品还有对某个开源项目更清晰的理解甚至是一个参与贡献的起点。