MQTT、EMQX、RabbitMQ、RocketMQ、Kafka选型避坑
刚入行那会儿,我被这几个词折腾得不轻:MQTT、EMQX、RabbitMQ、RocketMQ、Kafka,每个名字都听过,每个好像都是搞消息的,可真要选型或者跟别人聊架构的时候,一开口就露怯。后来在实际项目里一个个踩过坑、排过错,才慢慢把这条线捋清楚。这篇文章不打算给你背书式的名词解释,而是把这些东西放在同一张桌上,讲清楚它们之间的血缘关系、本质差异,以及什么场景下该请谁出场。
先说一个最容易混淆的点:这五个名字根本不是一个维度的东西。MQTT 是一种协议,EMQX 是基于 MQTT 协议的服务器产品,而后三者 RabbitMQ、RocketMQ、Kafka 则是各自独立的中间件产品,协议各不相同,设计哲学也完全不同。把这层关系搞明白,后面再看任何技术文章都不会糊。
1. 从零厘清概念:协议、代理与消息队列的关系
很多人上来就背"MQTT 是物联网协议、Kafka 是大数据消息队列",背是记住了,但换个场景就不会用。根子在于没搞懂协议和软件产品之间到底是谁依附于谁。
1.1 协议是语言,中间件是会说话的人
MQTT(Message Queuing Telemetry Transport)本质上是一套通信规约,规定消息怎么连接、怎么发布、怎么订阅、怎么确认。它不依赖任何特定软件,你可以用 EMQX 实现它,也可以用 Mosquitto,甚至自己拿代码写一个 Broker。就像"普通话"是一种语言,每个人都能说,但"某位主持人"只是说普通话的其中一个具体的人。
这个区别特别关键。你在网上搜"Maven 引入 MQTT",实际上是想找某个 MQTT 客户端库;搜"EMQX 部署",才是找一个能跑的服务器程序。把协议和实现分开,你在看资料时立马就能判断这文章到底在讲哪一层。
RabbitMQ 走的是 AMQP 协议,RocketMQ 是自研的私有协议,Kafka 用的也是自定义的 TCP 二进制协议。每一家的协议都不兼容,这也是为什么一旦选定某个中间件,想换掉往往等于重构通信层。
1.2 消息中间件的通用价值:解耦、削峰、异步
不管是哪一款,消息中间件解决的核心问题其实是同一个:让消息的发送方和接收方不必同时在线、不必知道对方在哪里。发送方只管把消息扔进中间件,接收方按自己的节奏去取。这个模型带来的好处是异步解耦,再加上缓冲区天然具备削峰填谷的能力。
我在一个设备数据采集项目里体会特别深。现场几十台设备每秒钟上报大量点位数据,如果直接让设备去请求业务接口,接口一挂设备端就会疯狂重试,雪崩几乎是必然。后来中间加了一层 MQTT 代理,设备只负责上报,业务系统自己去订阅处理,压力彻底错峰,系统稳定多了。
这个通用价值是所有中间件的根基。理解了这个,你再看后面的产品差异,就会明白它们不过是"解决同一个问题"的不同性格的人。
2. 五者定位拆解:谁在什么场景下称王称霸
每一个名字背后都有一段明确的历史背景,也对应了不同类型的问题。搞清楚它们的"出生地",你就能明白为什么它们长得如此不同。
2.1 EMQX:为物联网而生的 MQTT 网关
EMQX 是五个名字里唯一"站在 MQTT 协议背后"的产品。它最核心的定位是物联网接入层网关,长连接管理能力极强。几十万、上百万台设备同时挂在上面,每台设备维持一个 TCP 长连接,EMQX 对这类场景优化得非常好。
它的特色功能几乎每一项都是冲着物联网痛点去的:遗嘱消息能感知设备掉线,保留消息让新上线的设备马上拿到最新状态,共享订阅把消息分发到多个后端消费者。这些功能都是 MQTT 协议自带的能力,EMQX 把它们实现得最完整、最顺手。
在我用过的版本里,EMQX 的 Dashboard 真的很良心,设备上下线记录、消息流量、订阅关系都能直接在网页上看到,排查问题不用再对着命令行抓耳挠腮。对于强调可视化的场景,比如 N1 盒子这类低功耗设备上跑 EMQX,Docker 一条命令就能起来,运维负担也不会太大。
2.2 RabbitMQ:老牌的通用消息代理
RabbitMQ 诞生得早,是很多企业接触的第一款消息中间件。它把 AMQP 协议的 Exchange 路由模型做得很极致:消息先到 Exchange,再由 Exchange 根据 Binding Key 和 Routing Key 规则投递到队列。所以 RabbitMQ 的路由灵活度极高,不需要消费者提前写死消息该去哪。
这种灵活性在业务系统集成里非常吃香。比如订单系统产生的消息,既要发给库存服务,又要发给通知服务,还可能要发给数据分析服务。你不需要改上游代码,只需要在 RabbitMQ 里配置好 Exchange 和 Binding,消息就能自动分发到对应的队列。
只不过灵活也有代价。它的吞吐量上限在几万条每秒级别,相比 Kafka 动辄几十万上百万还是有差距。但绝大多数企业内部业务系统的消息量根本到不了这个瓶颈,RabbitMQ 的可靠性和功能完整性反而更实用。网上很多"RabbitMQ 启动失败"的求助帖,多半是 Erlang 版本不匹配,这一点我在后面的实操部分会详细说。
2.3 RocketMQ:阿里系分布式场景打磨出来的消息队列
RocketMQ 是阿里巴巴在自身超大规模电商业务场景下孵化的分布式消息中间件,后来捐给了 Apache。它和 RabbitMQ、Kafka 都不太一样:它的核心模型既有 Topic 又有队列,支持严格顺序消息、事务消息、延迟消息,对消息重试和死信队列的处理非常周到。
事务消息这个能力在金融级业务里堪称神器。它的设计思路是:先发一个半消息,本地事务执行成功后再提交确认,Broker 收到确认才让消息对消费者可见。如果事务失败就回滚。这解决了一个经典难题:本地数据库操作和发消息如何保证原子性,RocketMQ 给出了一个相对优雅的解法。
RocketMQ 的运维比 RabbitMQ 重一些,毕竟是分布式架构,逻辑上分 NameServer、Broker、Producer、Consumer 四大角色。很多人在 CentOS 上装 RocketMQ 单节点,最常遇到的坑是内存默认配置太大导致启动直接失败,这个后面我也给出了实操避坑的方式。
2.4 Kafka:日志和流数据的吞吐之王
Kafka 最初是 LinkedIn 为了解决日志收集问题开发的,所以它的设计目标从一开始就很明确:高吞吐、持久化、顺序读写。它以 Topic 为单位组织数据,每个 Topic 下可以分多个 Partition,Partition 内部消息严格有序。
Kafka 一条消息进来会先写磁盘,但因为用的是顺序追加写,再加上 Page Cache 和零拷贝技术,它的吞吐量依然高得吓人。这也是它在大数据领域不能撼动的核心原因。你和它讲"每秒钟一百万条消息",它可能眉头都不皱;你跟它讲"某条消息消费失败了能不能自动重试",它得想想办法。
另外 Kafka 的消费者通过维护 offset 记录消费到哪里,所以消费者宕机恢复后能接着消费,不会丢数据。这个机制也让 Kafka 成为流处理生态的事实标准,Flink、Spark Streaming 都把它当成数据源。很多人在 Windows Docker 里装 Kafka 遇到网络不通,八成是容器端口映射或者 ADVERTISED_LISTENERS 没配好。
2.5 对比表格:五大消息方案核心参数速览
| 维度 | EMQX | RabbitMQ | RocketMQ | Kafka | MQTT(协议层) |
|---|---|---|---|---|---|
| 本质定位 | MQTT Broker 物联网网关 | 通用消息代理 | 分布式消息队列 | 分布式流处理平台 | IoT 通信协议 |
| 核心协议 | MQTT over TCP/TLS | AMQP | RocketMQ 私有协议 | Kafka 自定义协议 | MQTT |
| 消息模型 | 发布/订阅 + Topic | Exchange 路由 | Topic + 队列 | Topic + Partition | 发布/订阅 + Topic |
| 吞吐量 | 十万级 | 万级 | 数十万级 | 百万级 | 取决于 Broker |
| 典型场景 | 物联网设备接入、车联网、智能家居 | 企业系统集成、任务分发、API 解耦 | 电商订单、金融事务消息 | 日志收集、埋点数据、流计算 | 受限网络下的设备通信 |
| 运维复杂度 | 低 | 低 | 中 | 中高 | 取决于实现 |
3. 核心机制对比:从架构到消息模型
五款产品光知道定位还不够,真正影响你的还是它们的内部机制。消息模型、可靠性保障、顺序性和吞吐量,这四个维度决定了一个中间件适合做什么、不适合做什么。
3.1 消息模型:队列、Topic 与发布订阅
RabbitMQ 的核心是队列,消息从 Exchange 路由进来之后就一直待在队列里,等着消费者来取。消费完之后消息就被标记为确认,或者直接删除。这种队列模型天然适合"一个任务只被一个消费者处理一次"的场景,比如把任务分发给多个 worker,谁能抢到归谁处理。
Kafka 和 RocketMQ 则是 Topic 模型下的分区消费。一个 Topic 就是一个逻辑上的"消息主题",但 Topic 下面会分成多个 Partition,每个 Partition 是一个有序的消息日志。消费者以 Consumer Group 为单位去订阅 Topic,组内每个消费者各消费一部分分区,实现并行处理。
MQTT 走的则是纯粹的发布订阅模型。客户端可以订阅某个 Topic 的通配符,例如 sensor/+/temperature 能匹配 sensor/room1/temperature 和 sensor/room2/temperature,这种灵活的层级主题结构在物联网场景里特别好用。消息没有"队列存储"的概念,Broker 只是临时中转,除了保留消息,其他消息实时推给在线的订阅者。
这里有一个非常常见的误解:Kafka 消费者组有点像队列模型,但又不一样。队列是一条消息只能被一个消费者消费,而 Kafka 的 Topic 可以被多个组重复消费,每个组看到的都是全量消息。这就好比同一份广播,A 组只听新闻,B 组只听体育,两组各自从头听。
3.2 消息可靠性:ACK、持久化与重试
可靠性是所有消息中间件的生命线。RabbitMQ 提出了生产者消息确认和消费者消息确认机制。生产端可以开启 publisher confirm,Broker 成功落盘后才回执,消费者处理完毕也要主动 ack,没 ack 就重新入队。这套双向确认机制相当成熟,代价是每次 ack 都带来一次额外的网络交互。
RocketMQ 的可靠性走的是同步刷盘加主从复制路线,消息先写 CommitLog 文件,再在内存中建立索引供快速查询。它给开发者提供了不同的刷盘策略,想要更高的可靠性就同步刷盘,想要更高的吞吐就异步刷盘,让用户在性能和安全之间自己权衡。
Kafka 的可靠性模型围绕副本机制展开。每个 Partition 有 Leader 和多个 Follower,生产者写入 Leader,Follower 从 Leader 同步。acks 参数分成三档:0 是发完不管,1 是 Leader 写成功后返回,all 是全部副本同步完成才返回。理解了这三档,你就能看懂很多 Kafka 丢数据案例的根因就是生产者侧的 acks 设置问题。
MQTT 协议本身定义了三个 QoS 等级:QoS 0 最多发一次,可能丢;QoS 1 至少到达一次,可能重复;QoS 2 恰好一次,性能开销最大。在 IoT 场景下,绝大多数传感器上报都用 QoS 0 或 1,因为重传机制带来的延迟和功耗在电池供电设备上是不能承受的。
3.3 顺序性、延迟与吞吐量之间的三角关系
这三个维度天然互相制约。Kafka 只在单 Partition 内保证顺序,如果业务要求全局有序,你就只能把消息都发到同一个分区,或者用单分区 Topic,但这样一来所有消费者都挤在一个分区上读,吞吐量直接限制了。
RocketMQ 提供的顺序消息也是类似思路:生产者指定 MessageQueue 选择器,把有顺序要求的数据发到同一个队列,消费者用单线程去消费。它能在保证顺序的同时,通过多个队列横向扩容。
RabbitMQ 本身不保证全局顺序,只保证单队列内按入队顺序投递。如果业务中牵涉跨队列投递,顺序性保证就无从谈起。要想实现严格顺序场景,RabbitMQ 并不是首选,RocketMQ 是明显更合适的。
延迟方面,RabbitMQ 的推模式延迟很低,毫秒级就能把消息推到消费者。Kafka 的拉模式会主动聚合一批数据后再拉取,牺牲了一点实时性换取吞吐。如果你对毫秒级响应有很强的需求,比如实时风控、在线变价、异步 RPC,RabbitMQ 更合适;如果你追求每秒百万级吞吐且能容忍百毫秒左右的延迟,Kafka 是标准答案。
4. 从零部署实操:每个方案都踩过的坑
光看文档和架构图永远学不会,还是得上手跑一遍。下面这些部署和操作经验,都是我实打实踩坑踩出来的,每一个都比官方文档多了几步避坑提示。
4.1 EMQX 快速部署与 MQTT 客户端联调
EMQX 的部署是我试过所有中间件里最简单的。用 Docker 直接拉镜像跑:
几个端口分别对应 MQTT 协议端口、WebSocket 端口、MQTT over TLS 端口和 Dashboard 端口。启动后浏览器访问 http://localhost:18083,默认用户名 admin 密码 public,就能看到管理后台。
联调时我推荐直接用 MQTTX 图形化客户端,比在命令行里折腾 MQTT 客户端效率高得多。连接的时候注意 Client ID 必须全局唯一,同一个 ID 两个客户端同时连会互相踢下线,这个坑我踩过不止一次。实际项目里很多人用 Vue3 做前端实时监控大屏,用 mqtt.js 在浏览器里直接通过 WebSocket 连 EMQX,一旦页面组件销毁时忘记断开连接,就会产生僵尸连接把连接池打满。所以前端接 MQTT 时务必在 onUnmounted 里调用 client.end(true)。
还有一次帮朋友排查 N1 盒子上的 EMQX,发现设备能连上但消息收不到,最后定位到是订阅 Topic 的层级关系和发布端不一致,一头用了 sensor/+/data,另一头发到 sensor/001/status,看似都带 sensor,实际根本不在一个分支上。MQTT 的 Topic 匹配是严格的,通配符只有 +(单层)和 #(多层),别指望它像正则一样模糊匹配。
4.2 RabbitMQ 安装、启动失败与权限分配
RabbitMQ 的安装最容易出问题的是 Erlang 版本不匹配。RabbitMQ 3.8.x 对应的 Erlang 版本区间并不宽,网上搜"RabbitMQ 启动失败"十有八九是这个原因。用 Docker 装则不用太担心:
5672 是 AMQP 端口,15672 是管理后台端口。镜像带 management 标签才自带 Web 管理界面。
启动成功后,第一件事就是建用户、建虚拟主机、分权限。很多新手图省事直接拿默认的 guest 账号登录,结果发现远程根本连不上,因为 guest 默认只允许 localhost 访问。正确做法是创建业务专用账号:
RabbitMQ 的权限模型分三个粒度:配置权限、写权限、读权限。配置权限管 Exchange 和 Queue 的创建删除,写权限管消息投递,读权限管消息消费。在企业里建议按业务拆分虚拟主机,不同团队各用各的 vhost,权限互不干扰,避免所有人在默认 vhost 里互相影响。
4.3 RocketMQ 单节点安装与可视化控制台
RocketMQ 单节点安装最大的坑就是默认内存配置。官方包的 runbroker.sh 和 runserver.sh 里 JVM 参数动辄 4G、8G,小机器上去直接内存溢出失败。我第一次在 CentOS 上装 RocketMQ 4.9 时就是栽在这里,后来习惯一装好就顺手注释掉大内存参数:
先启动 NameServer,再启动 Broker:
检查是否启动成功,不需要靠进程列表瞎猜,直接看端口:
9876 是 NameServer 端口,10911 是 Broker 对外服务端口。两个都在监听就稳了。
RocketMQ 自带的可视化控制台项目叫 rocketmq-dashboard,需要单独部署。装好之后你会明显感觉到,RocketMQ 的消息轨迹查询能力比 RabbitMQ 强不少,能按消息 ID 定位一条消息在全链路的状态,对排查线上问题帮助极大。
4.4 Kafka 集群搭建与生产消费命令实战
Kafka 的单机部署没多难,但很多人一上来就踩 Windows Docker 的坑。Kafka 在容器里的关键配置是 KAFKA_ADVERTISED_LISTENERS,它决定了客户端连的是哪个地址。如果配成了 localhost:9092,那容器里的 Kafka 告诉客户端连 localhost,容器外的客户端访问它当然是连接被拒。
典型的本机联调配置应该是:
Kafka 3.x 版本之后引入了 KRaft 模式,不用再单独依赖 ZooKeeper,部署复杂度确实降了不少。之前 Kafka 2.x 版本部署 ZooKeeper 集群本身就是个麻烦事,很多新同学连 broker 和 controller 的区别半天都搞不清楚。
生产消费验证是每个学 Kafka 的人都要经历的。生产者发送:
消费者消费:
这里有一个经常被误解的点:命令行启动的生产者或消费者,执行完一条 Ctrl+C 结束之后,并不会"一直运行"。消费者进程只要不退出就会持续监听新消息,这是正常行为;而生产者在输入完消息回车之后,进程也处于等待状态,等着你再输下一条,并不会因为发完一条就自动退出。很多人第一次跑命令不熟悉这个交互模式就觉得是不是卡了或者是死循环。
查看 Topic 数据量是排查问题的基本功:
这会显示每个分区的当前 offset 总和,基本就代表了这个 Topic 积压了多少条消息。Kafka 经典面试题"生产消费命令启动一次会一直运行吗",本质上就是在考你对客户端生命周期模型的理解,消费者不结束就一直拉取,这是它的正常设计。
5. 架构选型:不同业务场景该怎么选
选型没有绝对的"哪个最好",只有"哪个最合适"。但根据业务场景和团队基础,确实存在一条相对清晰的决策路径。
5.1 典型场景对照与推荐方案
| 业务场景 | 推荐方案 | 推荐理由 |
|---|---|---|
| 智能家居/工业 IoT 设备数据上报 | EMQX + MQTT | 海量长连接,协议天然适合受限设备 |
| 前后端异步任务、企业系统解耦 | RabbitMQ | 成熟稳定,路由灵活,对团队要求低 |
| 电商订单、支付回调、事务一致性业务 | RocketMQ | 事务消息、延迟消息、重试机制完善 |
| 用户行为埋点、日志收集、数据湖入湖 | Kafka | 高吞吐,数据保留时间长,生态完善 |
| 需要从 SCADA 或 OPC UA 设备上收数据 | Node-RED + MQTT | Node-RED 做协议转换,MQTT 做传输 |
车联网是 EMQX 的典型高难度场景。车辆在高速移动中会频繁切换网络,连接时断时续,EMQX 的会话恢复机制能自动把离线期间的消息补发给重连的设备。再加上 MQTT QoS 1 至少一次投递,不会因为网络闪断就丢重要定位数据。很多共享单车、物流车队的接入层就是这套组合。
RocketMQ 在 Java 技术栈的互联网公司里格外流行,因为它的客户端 API 设计得很贴近业务直觉,发送消息就是 producer.send(),消费消息就是 consumer.subscribe()。加上事务消息完美契合"先写库再发消息"的经典业务,在订单、支付、积分这些核心链路里,它几乎是确定性选项。
5.2 团队技术栈与运维能力是选型的隐形天平
很多选型文章只谈技术不谈人,但实际项目里团队的容器化水平、排查能力和语言偏好往往比中间件的纸面性能更能决定成败。一个没有任何 Kafka 运维经验的小团队,硬撑一个三节点 Kafka 集群,出了问题连监控告警都没配,比直接用 RabbitMQ 单机或者云厂商托管版要危险得多。
如果你所在公司已经全面容器化,Docker Compose 或 K8s 部署 RabbitMQ 和 EMQX 都是非常轻松的,因为它们的单节点形态就够用,集群属于"锦上添花"。而 Kafka 天生就是分布式的,最少三台 Broker,再加上监控(Kafka Exporter + Prometheus + Grafana)、Topic 治理、分区容量规划,运维心智负担完全不在一个量级。
如果让我给一个最通用的建议:中小型项目、业务系统集成优先 RabbitMQ;纯 IoT 接入选 EMQX;Java 技术栈、有强一致事务诉求选 RocketMQ;数据量大、有流计算和日志分析需求就选 Kafka。五者不冲突,大型系统完全可以多个共存,用不同中间件承担不同的职责分区。
6. 常见问题速查与避坑经验
消息中间件的问题大多隐藏得很深,不像普通 Web 开发那样报错直接定位。下面这些是我实际遇到或者帮同行排查过的高频问题,做成速查表方便你遇到同样问题的时候对着看。
6.1 高频问题排查速查表
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| MQTT 客户端连上后互发消息不成功 | Topic 层级写错或通配符使用错误 | 分别订阅 # 验证广播链路,再逐级排查精确 Topic |
| RabbitMQ 容器启动后管理后台进不去 | 用了不带 management 标签的镜像 | 换成 rabbitmq:3-management 镜像 |
| RabbitMQ 远程连接报 guest 用户权限错误 | guest 默认只允许本机访问 | 创建业务用户并分配 vhost 权限 |
| RocketMQ Broker 启动后自动退出 | JVM 默认内存超过机器可用内存 | 修改 runbroker.sh 降低 Xms、Xmx |
| Kafka 生产者报 Connection refused | ADVERTISED_LISTENERS 配成容器内地址 | 改成宿主机可达的 IP 或域名 |
| Kafka 消费者积压越来越严重 | 消费端处理慢或分区数不足 | 增加消费者实例,提升分区数(需提前规划) |
| 前端页面大量僵尸 MQTT 连接 | 组件销毁未关闭连接 | onUnmounted 里调用 client.end(true) |
| EMQX 设备频繁上下线 | 网络抖动 + 心跳超时设置太短 | 调大 keepalive 参数并开启会话恢复 |
| 消息重复消费 | QoS 1 或 at-least-once 语义导致 | 消费者侧做幂等处理 |
| Node-RED 转发 OPC UA 数据到 MQTT 乱码 | 数据没做类型转换直接发字符串 | 在 Node-RED function 节点里显式转换 |
6.2 消息中间件通用的幂等设计
很多人忽略了一个事实:想要消息不丢不重,技术层面的保证只占一半,另一半在业务层的幂等设计。因为不管选 RabbitMQ、RocketMQ 还是 Kafka,at-least-once 语义下消息重复都是常态。Kafka 的 acks=all 保证不丢,但消费者宕机重启后重复消费一样会发生。
最简单可靠的幂等方案是在消费端建一张消息去重表,把消息的唯一业务 ID 作为主键,每次消费先尝试插入记录,插入成功才执行真正的业务逻辑。这个方案在 MySQL 里实施成本极低,还能顺手记录消息的消费时间和处理结果,方便事后排查。我参与过的项目里,凡是没做幂等的,上线半年内一定出现重复扣款或重复发券的事故,无一例外。
6.3 我常用的三个排查思路
第一,看监控时段。遇到消息延迟高或者积压,先看是持续性的还是突发的。持续性积压通常是消费者端性能不足,需要加实例或优化代码;突发积压大概率是某次网络抖动或依赖接口超时导致消费线程阻塞。两个问题的解法完全不一样,别一上来就加机器。
第二,线上先复现再定位。比如 EMQX 上某类设备不上线,先在本地用 MQTTX 模拟一台设备,按同样的 Client ID 和 Topic 去连测试环境,能连上就说明 Broker 没问题,问题在设备端或网络链路。逐步缩小范围比盯着日志猜快得多。
第三,善用消息轨迹工具。RocketMQ 的消息轨迹、RabbitMQ 的 Firehose、Kafka 的 consumer group 命令,都是定位消息丢失和重复的利器。很多问题其实从消息状态流转的轨迹里一眼就能看出来,只是很多人没养成这个习惯。
最后再分享一条个人经验:刚接触消息中间件时,别一开始就扎进源码和原理里,先照着这篇文章把每个中间件跑一遍,感受它们在连通性、管理界面、报错方式上的性格差异。等你真正上手之后,再回过头去读官方文档,很多以前看不懂的底层原理就会自然通了。不同中间件之间共通的概念远多于差异,学透一个再学另一个,速度不是线性叠加而是指数增长。