
COSCon‘25 的大幕拉开时我在会场入口看到那块写着“Make MQ Great Again”的立牌旁边摆着一排 Pulsar 的周边说实话心里是有点五味杂陈的。消息队列这个领域已经很久没有这种“主场感”了过去几年大家聊 MQ十有八九聊到最后都变成 Kafka 的部署调优好像消息中间件的可能性已经被讲完了。但这次 COSCon’25 x Pulsar Developer Day 2025 的专场从上午主会场到下午动手实验区全程都保持着极高的讨论密度。这篇文章我想以参会者视角做个回顾聊聊专场里最值得记住的技术话题也把我在现场看到的一些生产实践、IoT 场景实验和社区讨论记录下来。无论你是做后端中间件选型还是在搞 STM32 环境监测这类设备接入应该都能从中找到一些可以落地的思路。1. 开场为什么今年的主题喊出了“Make MQ Great Again”1.1 一个口号背后的行业情绪“Make MQ Great Again”这个口号挂在会场里乍一听有点玩梗的味道但真正在现场待一天就会发现这其实是在回应一种普遍焦虑消息队列技术是不是已经进入平台期了很长一段时间里大家的默认选项就是 Kafka提到 MQ 就是分区、副本、消费者组提到性能就是吞吐量数字。但 Pulsar 专场从第一场分享开始就在努力打破这种惯性认知。我对这个口号的理解是MQ 不是没有新东西可讲而是太久没有人系统性地讲这些新东西了。Pulsar 的存算分离、多租户模型、分层存储、跨地域复制这些特性单拎出来每一个都能解决实实在在的问题但过去在社区里始终没有一个足够集中的场合把它们揉在一起讲清楚。COSCon’25 把 Pulsar Developer Day 2025 放进同一个会场相当于给这些分散的话题提供了一个集中的出口。现场分享的既有 Apache Pulsar 的 PMC 成员也有在一线维护大规模集群的工程师还有从 Kafka 迁到 Pulsar 之后回来做复盘的用户。这就不只是一场“布道式”的技术分享更像是一次把底牌亮出来的同行交流。我在现场听到最多的一个观点是不要为了换而换但如果你已经被 Kafka 的分区扩容、rebalance 抖动、存储成本这些问题反复折磨Pulsar 值得认真看一眼。1.2 COSCon 与 Pulsar Developer Day 同场的原因COSCon 是开源年会Pulsar Developer Day 是围绕 Apache Pulsar 生态的开发者活动两个放在一起并不是简单的场地共享。从议程设置上能看出来Pulsar 专场的很多内容都紧扣开源协作这条主线有讲如何参与社区贡献的有讲内部实现如何反哺上游的也有讲开源项目在生产环境大规模落地经验的。这种组合对参会者的价值在于你不仅能听到“这个功能怎么用”还能听到“这个功能是怎么被设计出来、为什么被设计成这个样子”。比如现场有人直接问 PMC 成员为什么 Pulsar 的 topic 模型要塞进 tenant 和 namespace 两层为什么不直接拍平。答案是多租户隔离从来不只是权限问题还关系到配额管理、存储隔离和跨团队成本核算。这种问题如果只看文档很容易被忽略。所以这篇回顾我不打算写成流水账而是把专场里对实际工作有直接帮助的几个话题挑出来展开再补充一些我在现场记录到的细节和自己的延伸思考。2. 主会场最值得回味的三个技术话题2.1 存算分离到底解决了什么问题上午主会场的第一个高密度话题就是存算分离。这个概念的宣传很多但现场主讲人没有停留在“Broker 无状态、存储走 BookKeeper”这种一句话解释上而是用一个对比把核心痛点讲透了。Kafka 的 Broker 既要负责计算又要负责存储分区数据扩缩容的时候数据要在节点之间搬来搬去分区数一多、副本数一高rebalance 就容易成为稳定性事故的高发区。Pulsar 把这两层拆开了Broker 只负责消息的路由、权限、元数据管理等计算逻辑真正的消息数据落在 BookKeeper 节点上。这样一来计算资源不够就加 Broker存储不够就加 BookKeeper 节点两边完全独立扩缩容数据搬移被降到了最低。我自己的理解可以打个比方Kafka 像开了一家餐厅每个服务员同时兼任仓库保管员客人一多要加服务员就得把仓库里的货也一起搬过去分一遍Pulsar 则是服务员只管点菜上菜所有菜品统一放在中央厨房哪个餐厅忙不过来就派更多服务员去厨房不够用就单独扩厨房。这个类比在现场交流时得到了不少人的认同。BookKeeper 的写入机制其实也值得细说。它把一个 Topic 的数据切成若干 ledger 段每条消息写入时BookKeeper 会在一个 ensemble 里选择若干节点按 write quorum 写入副本并按 ack quorum 等待确认。简单理解就是同一份数据同时写多个节点只要法定数量的副本确认成功就可以向客户端返回成功这样单个节点故障不会丢数据也不需要像 Kafka 那样依赖分区 leader 做同步复制。现场有人问“这个设计会不会让写入延迟变高”讲师给出的实测数据是在大部分内网场景下RTT 增加在毫秒级相比它换来的运维弹性是值得的。2.2 消息轨迹排查线上问题最容易被忽略的能力第二个在会场引发激烈讨论的话题是消息轨迹。做消息中间件的人都有过这种经历消费者明明消费到了消息但业务结果不对或者消息确实发送成功了但一直卡在某个环节谁也说不清卡在哪。这时候如果中间件只能告诉你“消息在”但给不了全链路的流转记录排查就只能靠日志靠猜。Pulsar 的消息轨迹功能本质上是在消息从 producer 到 broker 再到 consumer 的整个流转路径上把关键事件记录下来包括进入时间、存储位置、投递次数、确认时间等。现场演示的场景是一个订单系统模拟了一条消息被消费者处理失败后进入重试队列的全过程。打开消息轨迹查询界面就能清楚看到这条消息在哪个时间点被消费了两次第一次没确认、第二次被成功消费并确认顺着这个线索很快就定位到了业务代码里的幂等逻辑缺陷。这个功能在 Kafka 生态里相对要费劲一些通常需要自己埋点或者靠外部系统去审计Pulsar 把它做成了内置能力而且在 OpenTelemetry 的整合上也已经比较成熟可以把消息轨迹导出到 APM 系统做统一观测。我做中间件运维这几年的体会是消息轨迹这种东西平时用不上一旦用到就是救命的建议所有上了消息队列的项目都从一开始就把它打开。2.3 分层存储与容量规划的账要怎么算分层存储是另一个大家问得最多的话题。Pulsar 的分层存储可以把 BookKeeper 里比较老的数据自动卸载到 S3、GCS 或者自建的对象存储上Broker 仍然能看到这些数据消费者需要消费老消息时再把它加载回来但存储成本可以大幅下降。现场讲师算了一笔很直观的账假设每天消息增量 1 TB保留 7 天BookKeeper 里大约需要 7 TB副本 x3 就是 21 TB如果打开分层存储把超过 1 天的数据卸载到对象存储BookKeeper 只需要承载 1 天的热数据剩下的 6 TB 放到对象存储里。同样是 3 副本热数据用 SSD 按 GB 计费冷数据用对象存储按 TB 计费成本相差可能接近一个数量级。容量规划的现实问题在于很多团队的 Topic 数量是持续增长的消息量也是持续增长的但存储预算不会跟着线性涨。分层存储并不能解决所有问题它解决的是“历史数据还要不要留、留多久”的取舍问题。我建议在做容量规划时别只盯存储总量更要关注消费滞后时间——如果消费者已经落后好几个小时说明要么消费端能力不足要么 Topic 的 key 设计导致热点严重这些问题不是加存储能解决的。3. 动手实验区STM32 环境监测与 MQTT 接入的那些事3.1 摆满开发板的桌子DHT11、BH1750、MQ-2 和 OLED 的组合下午的动手实验区有一张桌子特别热闹一排 STM32 开发板上面插着 DHT11 温湿度传感器、BH1750 光照传感器、MQ-2 气体传感器还挂着一块 0.96 寸的 OLED 屏。第一眼看到这个组合很多做后端的人可能觉得走错片场了但恰恰是这套环境监测系统把“Make MQ Great Again”里“MQ”的另一层含义串起来了——MQ-2 是测气体的传感器MQ 是消息队列两者在这个场景里构成了完整的数据链路。这套硬件的分工清晰得很STM32 负责采集和逻辑控制DHT11 通过单总线协议返回温度和湿度BH1750 通过 I2C 返回光照强度MQ-2 输出的是模拟电压信号要用 STM32 的 ADC 读取再换算成气体浓度OLED 屏负责把实时数据直接显示出来。现场实验的目标很直接让这堆传感器数据不仅能看还能通过网络传出去最终落到消息队列里。我在旁边看了一会儿发现大部分人来动手区之前都以为难的是硬件接线实际跑起来才发现真正让现场一片“翻车”声的反而是数据采集之后的网络上报环节。传感器采集是确定性的本地操作串口一开、数据一读结果就出来了但一旦涉及网络上传、协议转换、消息队列的 topic 设计各种问题就开始冒头。3.2 传感器数据如何真正进入消息队列先从传感器读取说起。DHT11 是典型的单总线器件时序要求很严格我整理了一下实验现场最稳定的读取流程STM32 的 GPIO 先拉低 18 ms 触发启动信号然后拉高并释放总线等待 DHT11 响应设备会在总线上输出 40 bit 的数据16 bit 湿度、16 bit 温度、8 bit 校验以下是现场示例的关键代码用的是标准库方式// STM32F103 读取 DHT11 的核心流程 GPIO_Mode_Input(GPIOB, GPIO_Pin_11); DHT11_Start(); // 拉低18ms后释放 DHT11_WaitResponse(); // 等待80us低电平 80us高电平 uint8_t humi DHT11_ReadByte(); // 湿度整数部分 uint8_t temp DHT11_ReadByte(); // 温度整数部分 DHT11_ReadByte(); // 跳过小数部分DHT11小数位常为0 uint8_t checksum DHT11_ReadByte();BH1750 走的是标准 I2CSTM32 硬件 I2C 直接读就行注意首次上电后需要发送一次 Power On 命令否则读回来的全是 0。MQ-2 更简单传感器模块输出模拟量接到 STM32 的 ADC 引脚用公式把 ADC 值换算成电压再映射到气体浓度的相对值。OLED 用 I2C 接口所有数据汇总之后直接刷到屏幕上就能在本地先确认采集无误。接下来就是数据上报。现场实验链路是STM32 通过 WiFi 模块连上局域网用 MQTT 协议发布消息到本地 Broker再由 Broker 通过 Pulsar 的 MQTT Protocol Handler 直接把消息桥接到 Pulsar 集群。这样做的原因很简单Pulsar 的核心定位是服务端消息中间件设备端协议太重而 MQTT 是为物联网设计的轻量协议天然适合传感器这种低功耗、低带宽的场景。如果把 Pulsar 的 MQTT 接入打开设备侧其实可以不做任何代理直接用 MQTT 客户端连接 Pulsar 的 Broker 端口认证之后往指定 topic 发消息就行。现场演示用的是这种最直接的方式STM32 发布到persistent://iot/device/temperature消费端直接用 Python 的 Pulsar 客户端订阅一个基于环境监测的完整消息链路就跑通了。import pulsar client pulsar.Client(pulsar://192.168.1.100:6650) consumer client.subscribe( persistent://iot/device/temperature, subscription_namestm32-sub, consumer_typepulsar.ConsumerType.Shared ) while True: msg consumer.receive() data msg.data().decode() print(f收到传感器数据: {data}) consumer.acknowledge(msg)这条链路跑通的瞬间实验区里有一种“原来如此”的氛围。很多人搞完了才反应过来消息队列的价值恰恰体现在这种跨协议、跨层级的整合上——设备端不用关心数据最终去哪队列层也不用关心设备长什么样。3.3 现场最常见的三个翻车点实验区翻车最多的三个问题我特意记了下来给以后自己做环境监测系统的人提个醒。第一个是 DHT11 读不到数据。排查下来发现八成是引脚接触不良或者时序里缺少上拉电阻。DHT11 的数据线需要外接一个 4.7kΩ 左右的上拉电阻不然高电平信号容易被干扰。现场有几个板子直接靠模块自带上拉才没事一旦你单独买裸的 DHT11 元件自己接忘了上拉就是大概率翻车。第二个是 MQ-2 初始读数直接爆表。这不是代码问题MQ-2 通电之后需要预热传感器内部有一根加热丝刚上电的那几十秒输出电压会虚高。现场很多人接好之后立刻测试ADC 读回来的数值直接拉满就以为是模块坏了其实只要等大概一分钟再读就正常了。第三个是消息丢失和重复消费。有一组实验设置了 QoS 0 上报偶尔把 WiFi 一断再连消息就丢了另一组用了 QoS 2现场离线和重连之后消息倒是没丢但消费端出现了重复原因是没有做去重。这正好引出下面这个话题在真实 IoT 场景里可靠性和代价之间怎么权衡。4. 从传感器到队列IoT 场景下消息选型的真实考量4.1 数据量小也要用消息队列吗这是动手区讨论最热烈的问题没有之一。一个 STM32 环境监测系统每秒或者每几秒才上报一条温湿度数据这种量级用 HTTP 直接 POST 到后端也完全够用为什么还要中间塞一个消息队列我当时的回答是数据量小不代表不需要消息队列关键在于你是否需要削峰填谷、可靠投递和多下游消费。单机实验确实用不上但一旦设备数量从 1 变成 1000采集频率从每秒一次变成每秒十次网络抖动和下游处理瓶颈就会陆续出现。消息队列在 IoT 场景里最大的价值类比过来就像一个快递中转站。设备是一条条快递线路上的发货点后端服务是最终收件人。如果没有中转站快递员必须亲手把每个包裹交到收件人手里收件人一忙快递就得排队有了中转站发货点只管按规则把包裹送到站里收件人什么时候有空什么时候来取。削峰填谷、解耦、流量控制就这么一次性解决了。另外还有一个经常被忽略的审计价值。传感器数据进了消息队列天然就有留存、有消费记录、有消息轨迹将来要排查“昨天某段时间某个设备的数据为什么没进库”至少能查得到。用 HTTP 直连数据到了服务端之后如果处理失败可能连影子都找不着。4.2 MQTT Broker 与 Pulsar 的角色划分实验区用 Pulsar 原生 MQTT 接入很顺畅但真实生产环境里我更倾向于把 MQTT Broker 和 Pulsar 划分成两层。角色协议核心职责典型部署设备接入层MQTT 3.1.1 / 5.0设备鉴权、连接保持、QoS、遗嘱消息EMQX / Mosquitto靠近设备侧消息服务层Pulsar持久化、转发、多租户隔离、流处理、投递给业务后端Pulsar 集群靠近数据中心分工的理由在于设备侧连接数量大、长连接多、断线重连频繁MQTT Broker 对这一类场景做了大量优化比如连接保活、会话恢复、遗嘱消息而 Pulsar 的核心优势是服务端的吞吐、持久化、多租户和流式处理生态。设备接入层负责把海量连接管好Pulsar 负责把数据可靠地分发给下游各干各擅长的事情。而且这种分层还有一个操作上的好处将来设备侧协议升级比如从 MQTT 换到 CoAP 或者私有协议只需要动接入层Pulsar 的 Topic 和消费端完全不用变。反过来业务消费方想从流式处理改成批量分析也只需要在 Pulsar 这边增加一个消费组或者开启 Pulsar IO设备侧完全无感。4.3 端侧到云端的时延与可靠性实测实验区现场对端到端时延做了简单测试STM32 发布消息到 PulsarPython 客户端收到消息中间过了 MQTT 协议转换整个链路在局域网环境下基本在 20-100 毫秒之间。这个延迟对环境监测来说绰绰有余现场甚至有人开始讨论能不能用这套链路做设备控制那就需要引入另一个话题QoS 的取舍。MQTT 接入层通常有三种 QoSQoS 0至多一次可能丢消息传感器周期性上报时可用QoS 1至少一次保证消息到达但可能重复接收方要幂等QoS 2恰好一次消息不丢不重但开销最大适合控制指令我自己的想法是环境监测这类周期性上报数据用 QoS 1 就够了重复几条没关系反正下一轮数据马上又来消费端做好按设备 ID 和时间戳的去重就可以控制类指令建议上 QoS 2但需要后端从 Pulsar 里消费数据后做二次确认确保指令只执行一次。现场有人问“Pulsar 本身有没有恰好一次语义”答案是 Pulsar 支持事务消息可以配合消息轨迹和消费端幂等设计实现端到端的可靠处理但要把它用好需要从消息键到消费逻辑全链路一起规划不是开个开关就能搞定的事。5. 从硬件到生产我印象最深的现场瞬间与给后来者的实在建议5.1 一个让我印象深刻的现场问答动手区接近尾声的时候有一个做嵌入式开发背景的参会者问了个问题当时全场安静了一下。他问的是STM32 这种资源受限的设备为什么要对接 Pulsar 这种重量级的服务端消息中间件是不是有点杀鸡用牛刀这个问题的潜台词其实很多人都有只不过没直接表达出来。现场做后端的人给的回答很实在设备端永远只是整个系统的一环单看一个 STM32 节点数据量确实小但如果你是一个做智慧农业或者智慧楼宇的项目几百个节点的数据都要汇聚、存储、分析消息队列的价值就会显现。另外Pulsar 可以选择性压缩消息gzip、zstd、lz4、snappy 都支持。传感器上报的数据都是 JSON 文本压缩比例通常能达到 70% 以上存储成本大幅下降。设备端和中间件看起来“完全不对等”但它们之间是分工关系不是替代关系——设备负责采数Pulsar 负责把数据变成可供分析和检索的资源各自做好自己的角色。这让我想到技术选型里最容易犯的错误不是选错某个组件而是用单一的视角去看整个系统只站在自己的那一段里做判断。这次 COSCon’25 x Pulsar Developer Day 专场之所以让我觉得值得记录就是因为它把“设备端采集”和“服务端消息中间件”两个平时各说各话的世界放到了一起让硬件工程师和后端工程师有机会在同一张桌子上讨论一条完整的数据通道。5.2 给打算实践的人的三个提醒如果看完这篇回顾你也想在项目里尝试这条路——用 Pulsar 作为消息底座或者用 STM32 采集环境数据接入消息服务——我有几个从现场和过往实操中总结出来的建议第一启动项目时就把消息轨迹和死信队列配好。很多消息中间件的故障最后追溯起来都是“没留证据”。Pulsar 的消息轨迹不算复杂但一旦业务上线后再去补成本和难度都会大很多。死信队列DLQ同理消费失败的消息如果只是反复重试积压迟早会把下游打爆。第二学会用多租户模型而不是一把梭。Pulsar 的 tenant 和 namespace 是层级隔离的建议直接用 tenant 隔开不同业务线团队用 namespace 区分环境配额管理和权限控制都要落到这个模型上。这不是多此一举后期容量规划和成本核算都依赖这一层。第三从第一天就把 QoS、幂等和连接鉴权的设计一起想清楚而不是先打通再说。传感器接入 MQTT 的时候要规划好 topic 命名规范、设备 ID 体系和 QoS 选择这些基础设计一旦定了后面很难改。先打通链路并无不妥但“打通之后返工”的成本往往比一开始多想一步高得多。5.3 一点个人体会回顾这一天的感受“Make MQ Great Again”之所以能引起这么多人共鸣在于它抓住了大家心里一直存在的那个问题消息队列不该只是一个“吞吐量很大”的管道它应该有能力在不同场景下做出正确的取舍。Pulsar 能在这个会场里成为主角是因为它的架构思路确实提供了不少破局的可能性而 COSCon 这种开源社区氛围又让技术讨论不再是厂商单方面的输出。我个人最受益的还是动手实验区里 STM32 接入 Pulsar 那套流程。它把硬件采集、协议转换、消息队列、消费应用串成了一条完整的链跑通之后你就不会再觉得“消息队列是后端的事”了。后来我在自己项目里改 IoT 上报链路时也在 DHT11 的采集逻辑里加了预热处理和异常重采样这些小细节都是在现场讨论中得到的启发。技术文章的价值正在于此你在自己的工程里踩过坑再从别人的实践中获得新的视角很多东西就真正通了。