消息系统的边界在哪里
最近观察到几个行业趋势,放在一起值得认真想一想。
一是越来越多的系统开始把 Lakehouse 作为消息存储的底层——把数据写进 Iceberg、Delta Lake 这类格式,消息消费和 SQL 查询共用同一份数据,希望把消息系统和数据分析的边界打通。二是 S3 对象存储正在成为很多系统的主存储选择,而不只是归档用的冷存储——成本低、天然高可用、不需要单独为跨可用区复制付出代价,Kafka、Pulsar 都在往这个方向走。三是消息系统本身开始往流语义延伸,消息不再是"发了就忘",而是可以持久化、可以回放、可以按 offset 消费。
EMQX 最新版本(6.x)是这个趋势的一个具体体现。它在 MQTT 之上引入了消息流语义——消费者可以通过 offset 控制回放起点,支持按键有序,类似 Kafka 的消费模型,但对存量设备完全透明。同时原生支持消息队列,实现生产者与消费者解耦,支持离线消息存储和灵活的分发策略,能力上对标 RabbitMQ 风格的队列语义。
这些变化放在一起,说明消息系统的边界正在模糊——或者说,每个系统都在用自己的方式回答同一个问题:消息系统应该负责多少事?
这让我重新想了想 RobustMQ 自己的答案。
行业在往哪里走
一个 MQTT broker 为什么要支持流语义?这个问题背后有一个更本质的问题:消息系统的边界应该在哪里?
IoT 数据天然是流。设备持续上报传感器数据、状态变化、事件触发,本质上就是一条无尽的时间序列。传统的消息中间件解决的是"数据怎么送达"的问题,但随着业务复杂度提升,用户越来越关心"数据送达之后怎么用"。于是消息系统开始往流处理、存储、分析方向延伸,这是整个行业自然演进的结果。
这个演进有它的合理性。当用户搭建一套 IoT 平台,他面对的不是一个孤立的 broker,而是一条完整的数据链路:设备接入、实时消费、持久化存储、历史查询,每个环节都需要工具来支撑。如果每个环节都要单独选型、单独部署、单独运维,对中小团队来说是很重的负担。所以消息系统往链路两端延伸,是一个自然的商业驱动——减少用户需要维护的系统数量,降低整体门槛。
问题在于,不同的系统扩展边界的方式不同,最终决定了系统的长期形态。一种方式是在现有协议和能力上持续叠加,流量来了加流、队列需求来了加队列,系统的功能越来越全,但内部的存储可能是分散的,协议之间需要搭桥同步。另一种方式是先想清楚底层的数据模型,再往上构建协议层——协议是数据的访问方式,而不是数据的存储方式。
RobustMQ 选择的是后者。不是在协议层叠加能力,而是从存储层重新想这个问题:如果底层只有一份数据,多种协议只是不同的读写视图,那消息系统的扩展性会完全不同。
RobustMQ 的思路
RobustMQ 的答案是:用统一的存储层承载一份数据,用多种协议提供不同的读写视图。
存储层解决的是"数据放在哪里"的问题——内存、RocksDB、File Segment,还是未来的 S3、Lakehouse,不同的场景用不同的引擎,粒度细到 topic 级别可配置。协议层解决的是"数据怎么用"的问题——MQTT 写入、Kafka 消费、AMQP 接入,不同协议访问的是同一份数据,不需要在协议之间搬运或同步。
这两件事合在一起,构成了 RobustMQ 的基本形态:一条可以被不同协议共同使用、底层存储可以按场景灵活选择的通信管道。它做的事情始终是消息系统该做的事,边界没有往数据处理、分析、可视化方向延伸。克制在这里不是口号,而是架构设计上的主动选择。
统一存储:一份数据,多种形态
RobustMQ 目前支持三种底层存储引擎:内存存储、RocksDB 和 File Segment。这三种存储各自对应不同的场景——内存存储追求极致低延迟,适合像 NATS 那样的轻量消息场景;RocksDB 提供可靠的持久化,适合 RabbitMQ 风格的队列语义;File Segment 面向高吞吐的顺序写入,是 Kafka 那类日志流场景的基础。
为什么要支持三种存储引擎,而不是只用一种?因为没有哪种存储引擎是万能的。内存快但贵,断电即失;RocksDB 持久化好,但顺序读的吞吐不如 File Segment;File Segment 适合大量数据的顺序追加,但随机访问不占优。不同的协议和场景,对存储的诉求是不一样的。强行用一种存储覆盖所有场景,要么性能妥协,要么成本过高。
但关键不在于支持多少种存储,而在于这三种存储对上层协议来说是透明的。用户根据自己的场景选择合适的存储策略,协议层不需要关心底层用的是哪种引擎。这个抽象做好了,新的存储形态接进来只是增加一个实现,不需要改动协议层。
这是存储层设计最核心的判断:存储引擎是可替换的,但上层协议对存储的依赖应该是统一的。 不是为每种协议单独维护一套存储,而是所有协议共享同一套存储抽象。
多协议:不是功能堆叠,是同一份数据的不同视图
理解了存储层的设计之后,多协议支持的价值就变得很直接:因为底层存储是统一的,所以 MQTT 写入的消息,可以直接被 Kafka 协议消费,反之亦然,不需要任何数据搬运。
这件事听起来简单,但实际上很多系统做不到。传统的多协议支持,本质上是"为每种协议维护一套独立的存储,再在协议之间建数据管道"。管道意味着延迟,意味着数据复制,意味着 MQTT 端刚写入但 Kafka 端还没同步的中间状态,意味着额外的运维成本。每增加一种协议,就多一条管道要维护。
RobustMQ 的方式不同。MQTT 进、Kafka 出,或者 Kafka 进、MQTT 出,访问的是同一份数据,中间没有任何搬运。写入即可消费,没有同步延迟,没有一致性窗口。这对用户来说不是一个抽象的架构优点,而是很实际的体验差异:数据流转的链路短了,出问题的地方少了,要调试的环节少了。
这个模型往后可以继续扩展。当 AMQP、NATS 这些协议接入进来,它们同样访问同一份底层存储。从 A 协议写入,B、C、D 协议都可以读出。每一种协议背后都是一套生态——MQTT 背后是 IoT 设备生态,Kafka 背后是流处理和数据工程生态,AMQP 背后是企业消息生态。这些生态之间原本需要用户自己搭数据管道来打通,而在 RobustMQ 里,它们天然连通,不需要任何胶水层。协议支持的越多,这个统一视图的价值就越大。
对用户来说,这不是一个抽象的架构优点,而是很实际的体验。一个典型的 IoT 数据平台,原本要维护 MQTT broker、Kafka 集群、时序数据库、数据湖四套系统,每两套之间都需要数据管道,每条管道都是运维负担和潜在故障点。用了 RobustMQ 之后,设备通过 MQTT 写入,流处理系统直接用 Kafka 协议消费同一份数据;复杂分析通过 Kafka 协议自然流出到专业工具。链路短了,接缝少了,出问题的地方也少了。
RobustMQ 不会去做 Flink 的事,也不会去做 GreptimeDB 的事。边界清楚,才能把自己的事情做到位。
功能可以被复制,但结构层面的设计一旦做好,追赶的成本是很高的。这是 RobustMQ 在多协议方向上最核心的判断:未来最有价值的消息基础设施,不是功能最全的,而是接缝最少的。
存储引擎的扩展性是长期赌注
存储层的可扩展性,是 RobustMQ 一个长期的赌注。
近几年业界有几个明显的趋势值得关注。一是越来越多的系统把数据沉降到 S3 这类对象存储,驱动力很直接:本地磁盘贵,S3 便宜,而且不需要为跨可用区的数据复制单独付通信成本——数据放在对象存储本身就是高可用的。Kafka 的 Tiered Storage、Pulsar 的 BookKeeper 分层、各种云原生消息系统都在往这个方向走。二是 Lakehouse 的崛起,把数据写入 Lakehouse(Delta Lake、Iceberg 等格式),既可以被流处理消费,也可以被 SQL 查询引擎直接查,一份数据同时满足多种消费模式。这两个方向各有局限——S3 的随机读延迟高,不适合实时消费;Lakehouse 的写入成本和查询延迟也比专用消息系统高——但它们都在解决真实的问题,有真实的用户在用。
从这里可以得出一个更深的判断:没有哪一种存储模型可以成为统一的通信管道。 内存适合低延迟场景,本地磁盘适合高吞吐流,S3 适合低成本冷数据,Lakehouse 适合需要同时消费和查询的场景。每种存储在它对应的场景里是合理的,强行用一种存储覆盖所有场景,必然要在某个维度上妥协。
这正是 RobustMQ 选择可插拔存储引擎的原因,而且粒度需要细到 topic 级别的可配置——不同的 topic 可以选择不同的存储策略,实时高频的数据用内存或 File Segment,需要长期保留的历史数据用 S3,需要分析查询的数据写 Lakehouse。用户不需要为了不同的数据类型分别部署不同的系统,消息系统本身根据配置选择合适的存储引擎,上层协议感知不到任何差异。
现在打下这个存储抽象,是为这些方向做预留。冷热分层成为刚需的时候,演进的成本会比较低,因为架构从一开始就为此留了位置。每接入一种新的存储引擎,能力就增长一块,而不是每次都要改协议层。这种扩展性不是设计过度,而是对未来趋势的一个现实判断。
用户得到的是什么
把这些放在一起,用户得到的是:搭建一套消息基础设施,不再需要在 MQTT broker、Kafka 集群、队列系统之间做选择,也不需要维护它们之间的数据同步管道。设备用 MQTT 写入,流处理系统用 Kafka 消费,企业系统用 AMQP 接入,各自用各自熟悉的方式,访问的是同一份数据。数据存在哪里、用什么介质,按 topic 配置好,系统自己处理。
运维的复杂度降低了,因为少了系统,就少了系统之间的接缝,也少了接缝出问题时的排查成本。需要对接专业工具做复杂分析,Kafka 协议本身就是出口,不需要额外适配。
这不是一个功能最全的方案,而是一个结构更简单的方案。一个统一的消息系统,能适应不同的场景,真正好用。 仅此而已。
