在日常开发中,即时通讯功能并不少见 ------ 电商平台的客服对话、支付完成后的实时通知、设备状态的动态同步,这些场景都需要快速、可靠的消息传递。过去我们常依赖 WebSocket,但最近发现,用 RabbitMQ 结合 MQTT 协议实现即时通讯,不仅代码量更少,带宽占用也更低,甚至无需编写后端代码就能满足基础需求。今天就带大家从零开始,掌握这套轻量级解决方案。
一、先搞懂:MQTT 是什么?核心概念有哪些?
要用好 MQTT,得先理清它的核心逻辑。MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议)是基于发布 / 订阅模式的轻量级协议,底层依赖 TCP/IP,最大优势是 "低开销、高可靠"------ 即使用有限带宽,也能为远程设备提供实时消息服务,特别适合物联网、即时通讯等场景。
它的核心组件只有 5 个,理解后就能轻松上手:
-
Publisher(发布者) :消息的发起者,负责向指定 "主题" 发送消息(比如用户发送的客服消息)。
-
Subscriber(订阅者) :消息的接收者,需先订阅某个 "主题",才能收到该主题下的所有消息(比如客服端接收用户消息)。
-
Broker(代理) :连接发布者和订阅者的 "中间件",负责转发消息,支持 MQTT 协议的消息中间件(如 RabbitMQ、EMQX)都能充当 Broker。
-
Topic(主题) :消息的 "路由标识",类似 "频道",发布者向某个 Topic 发消息,只有订阅了该 Topic 的订阅者能收到(比如 "user/123/msg" 代表用户 123 的消息主题)。
-
QoS(消息质量) :控制消息传递的可靠性,分三个等级,按需选择即可:
- QoS 0(至多一次) :只发送一次,不保证送达,可能丢失或重复(适合非关键消息,如设备心跳)。
- QoS 1(至少一次) :确保消息送达,但可能重复(适合重要消息,如通知提醒)。
- QoS 2(恰好一次) :确保消息仅送达一次,无丢失无重复(适合核心业务,如支付通知)。
二、第一步:启用 RabbitMQ 的 MQTT 功能
RabbitMQ 默认不开启 MQTT 支持,需要手动配置。这里以 Docker 环境为例,步骤简单且跨平台,几分钟就能搞定。
1. 用 Docker 启动 RabbitMQ
首先运行 Docker 命令,拉取并启动 RabbitMQ 容器,同时映射必要端口(1883 是 MQTT 默认端口,15675 是 MQTT WebSocket 端口,15672 是管理控制台端口):
bash
bash
docker run -p 5672:5672 -p 15672:15672 -p 1883:1883 -p 15675:15675 --name rabbitmq-mqtt \
-v /mydata/rabbitmq-mqtt/data:/var/lib/rabbitmq \
-d rabbitmq:3.9.11-management
- 5672:RabbitMQ 默认 AMQP 协议端口(非必需,此处为兼容其他功能);
- 15672:RabbitMQ 管理控制台端口(用于查看服务状态);
- 1883:MQTT 协议默认端口(用于 TCP 连接);
- 15675:MQTT WebSocket 端口(用于前端 / 客户端通过 WebSocket 连接)。
2. 启用 MQTT 插件
容器启动后,需要进入容器内部开启 MQTT 相关插件(开启rabbitmq_web_mqtt
会自动关联启用rabbitmq_mqtt
):
bash
bash
# 进入RabbitMQ容器
docker exec -it rabbitmq-mqtt /bin/bash
# 启用MQTT Web插件
rabbitmq-plugins enable rabbitmq_web_mqtt
3. 验证配置是否成功
打开浏览器访问 RabbitMQ 管理控制台(地址:http://你的服务器IP:15672
,默认账号密码都是guest
),在 "Listening ports" 列表中,若能看到http/web-mqtt
对应 15675 端口、mqtt
对应 1883 端口,说明配置成功。
三、用 MQTTX 客户端测试:快速验证即时通讯
在写代码前,先用 MQTTX(一款轻量的 MQTT 客户端工具)测试 RabbitMQ 的 MQTT 功能是否正常,避免后续踩坑。
1. 用 Docker 启动 MQTTX
同样用 Docker 快速部署 MQTTX Web 版,命令如下:
bash
arduino
docker run -p 80:80 --name mqttx-web -d emqx/mqttx-web
启动后访问http://你的服务器IP
,即可进入 MQTTX 控制台。
2. 创建 MQTT 连接并测试
-
点击左侧 "+" 号新建连接,配置关键参数:
- 名称:自定义(如 "test-connect");
- 服务器地址:
ws://你的服务器IP:15675/ws
(注意用 WebSocket 协议,路径为/ws
); - 端口:15675;
- 用户名 / 密码:
guest
/guest
; - MQTT 版本:选择 3.1.1(兼容性更好)。
-
连接成功后,添加订阅主题(如 "testTopicA"),QoS 选择 0 或 1;
-
再打开一个 MQTTX 窗口,同样连接 RabbitMQ,向 "testTopicA" 主题发送消息(如 "hello MQTT!");
-
此时第一个窗口会实时收到消息,说明 RabbitMQ 的 MQTT 转发功能正常。
四、前端零后端代码实现:HTML+JS 打造简单聊天
验证功能正常后,直接用前端技术(HTML+JavaScript)实现即时通讯 ------ 无需写一行后端代码,只需引入MQTT.js
库即可。
1. 核心代码实现
创建index.html
文件,核心逻辑是 "连接 MQTT 服务→订阅主题→发送 / 接收消息":
html
xml
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>MQTT前端聊天Demo</title>
<style>
.message-box { margin-top: 10px; padding: 10px; border: 1px solid #eee; height: 300px; overflow-y: auto; }
.message-item { margin: 5px 0; padding: 8px; background: #f5f5f5; }
</style>
</head>
<body>
<div>
<label>订阅主题:<span id="subscribedTopic"></span></label><br>
<label>目标主题:<input id="targetTopic" type="text" placeholder="输入要发送的主题" value="testTopicB"></label><br>
<label>消息内容:<input id="msgContent" type="text" placeholder="输入消息内容"></label><br>
<button onclick="sendMsg()">发送消息</button>
<button onclick="clearMsg()">清空记录</button>
<div class="message-box" id="messageBox"></div>
</div>
<!-- 引入MQTT.js库 -->
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<script>
// 1. 配置MQTT连接参数
const mqttUrl = 'ws://你的服务器IP:15675/ws'; // RabbitMQ的MQTT WebSocket地址
const subscribedTopic = getUrlParam('topic') || 'testTopicA'; // 从URL获取订阅主题,默认testTopicA
document.getElementById('subscribedTopic').textContent = subscribedTopic;
// 2. 连接MQTT服务
const client = mqtt.connect(mqttUrl, {
username: 'guest',
password: 'guest',
keepalive: 60 // 心跳间隔(秒)
});
// 3. 连接成功后订阅主题
client.on('connect', () => {
client.subscribe(subscribedTopic, (err) => {
if (!err) {
addMessage(`✅ 成功订阅主题:${subscribedTopic}`);
} else {
addMessage(`❌ 订阅失败:${err.message}`);
}
});
});
// 4. 接收消息并展示
client.on('message', (topic, msg) => {
addMessage(`📥 收到[${topic}]的消息:${msg.toString()}`);
});
// 5. 发送消息到指定主题
function sendMsg() {
const targetTopic = document.getElementById('targetTopic').value;
const content = document.getElementById('msgContent').value;
if (!targetTopic || !content) {
alert('请填写目标主题和消息内容!');
return;
}
// 发布消息
client.publish(targetTopic, content);
addMessage(`📤 发送到[${targetTopic}]的消息:${content}`);
document.getElementById('msgContent').value = ''; // 清空输入框
}
// 6. 工具函数:添加消息到页面
function addMessage(text) {
const msgBox = document.getElementById('messageBox');
const msgItem = document.createElement('div');
msgItem.className = 'message-item';
msgItem.textContent = `[${new Date().toLocaleTimeString()}] ${text}`;
msgBox.appendChild(msgItem);
// 滚动到最新消息
msgBox.scrollTop = msgBox.scrollHeight;
}
// 7. 工具函数:清空消息记录
function clearMsg() {
document.getElementById('messageBox').innerHTML = '';
}
// 8. 工具函数:从URL获取参数
function getUrlParam(name) {
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i');
const match = window.location.search.substr(1).match(reg);
return match ? decodeURIComponent(match[2]) : null;
}
</script>
</body>
</html>
2. 测试前端聊天功能
-
将
index.html
放在静态资源目录(如 Nginx 或 SpringBoot 的resources/static
); -
打开两个浏览器窗口,分别访问:
- 窗口 1(订阅 testTopicA):
http://你的地址/index.html?topic=testTopicA
- 窗口 2(订阅 testTopicB):
http://你的地址/index.html?topic=testTopicB
- 窗口 1(订阅 testTopicA):
-
在窗口 1 的 "目标主题" 输入
testTopicB
,发送消息;窗口 2 会实时收到,反之亦然 ------ 一个简单的跨窗口聊天功能就实现了!
五、SpringBoot 集成 MQTT:应对复杂业务场景
如果需要后端参与消息处理(如消息存储、权限校验、多主题转发),可以将 MQTT 集成到 SpringBoot 中。下面是完整的实现步骤。
1. 添加依赖
在pom.xml
中引入 Spring 集成 MQTT 的依赖:
xml
xml
<!-- Spring Integration MQTT -->
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
</dependency>
<!-- 可选:Swagger用于接口测试 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
2. 配置 MQTT 参数
在application.yml
中添加 RabbitMQ 的 MQTT 连接信息:
yaml
yaml
rabbitmq:
mqtt:
url: tcp://你的服务器IP:1883 # MQTT TCP协议地址
username: guest
password: guest
default-topic: testTopic # 默认订阅/发布的主题
server:
port: 8088 # 应用端口
3. 编写配置类
创建MqttConfig
类,读取配置文件中的参数:
java
arduino
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "rabbitmq.mqtt")
public class MqttConfig {
/** MQTT连接地址(TCP协议) */
private String url;
/** 连接用户名 */
private String username;
/** 连接密码 */
private String password;
/** 默认主题 */
private String defaultTopic;
}
4. 配置消息订阅者(接收消息)
创建MqttInboundConfig
,实现 "订阅 MQTT 主题并处理消息" 的逻辑:
java
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.mqtt.core.DefaultMqttPahoClientFactory;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.inbound.MqttPahoMessageDrivenChannelAdapter;
import org.springframework.integration.mqtt.support.DefaultPahoMessageConverter;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessagingException;
@Slf4j
@Configuration
public class MqttInboundConfig {
@Autowired
private MqttConfig mqttConfig;
/** 定义输入通道:用于接收MQTT消息 */
@Bean
public MessageChannel mqttInputChannel() {
return new DirectChannel();
}
/** 定义消息生产者:订阅指定主题,将消息转发到输入通道 */
@Bean
public MqttPahoMessageDrivenChannelAdapter inbound() {
// 参数:MQTT连接地址、客户端ID(唯一)、订阅的主题
MqttPahoMessageDrivenChannelAdapter adapter = new MqttPahoMessageDrivenChannelAdapter(
mqttConfig.getUrl(), "springboot-mqtt-subscriber", mqttConfig.getDefaultTopic()
);
adapter.setCompletionTimeout(5000); // 连接超时时间
adapter.setConverter(new DefaultPahoMessageConverter()); // 消息转换器
adapter.setQos(1); // 消息质量等级(至少一次)
adapter.setOutputChannel(mqttInputChannel()); // 绑定输入通道
return adapter;
}
/** 定义消息处理器:处理输入通道中的消息 */
@Bean
@ServiceActivator(inputChannel = "mqttInputChannel")
public MessageHandler mqttMessageHandler() {
return new MessageHandler() {
@Override
public void handleMessage(Message<?> message) throws MessagingException {
// 此处可添加业务逻辑:如消息存储、格式转换、权限校验等
log.info("📥 收到MQTT消息:{}", message.getPayload());
}
};
}
/** MQTT客户端工厂:配置连接参数 */
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
org.eclipse.paho.client.mqttv3.MqttConnectOptions options = new org.eclipse.paho.client.mqttv3.MqttConnectOptions();
options.setServerURIs(new String[]{mqttConfig.getUrl()});
options.setUserName(mqttConfig.getUsername());
options.setPassword(mqttConfig.getPassword().toCharArray());
factory.setConnectionOptions(options);
return factory;
}
}
5. 配置消息发布者(发送消息)
创建MqttOutboundConfig
,实现 "从后端发送消息到 MQTT 主题" 的逻辑:
java
kotlin
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.mqtt.core.MqttPahoClientFactory;
import org.springframework.integration.mqtt.outbound.MqttPahoMessageHandler;
import org.springframework.messaging.MessageChannel;
@Configuration
public class MqttOutboundConfig {
@Autowired
private MqttConfig mqttConfig;
@Autowired
private MqttPahoClientFactory mqttClientFactory;
/** 定义输出通道:用于发送MQTT消息 */
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
/** 定义消息处理器:将输出通道的消息发布到MQTT主题 */
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MqttPahoMessageHandler mqttOutbound() {
// 参数:客户端ID(唯一)、客户端工厂
MqttPahoMessageHandler handler = new MqttPahoMessageHandler("springboot-mqtt-publisher", mqttClientFactory);
handler.setAsync(true); // 异步发送
handler.setDefaultTopic(mqttConfig.getDefaultTopic()); // 默认发布主题
return handler;
}
}
6. 定义 MQTT 网关(简化发送逻辑)
创建MqttGateway
接口,通过注解简化 "发送消息到 MQTT" 的调用:
java
less
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.mqtt.support.MqttHeaders;
import org.springframework.messaging.handler.annotation.Header;
@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface MqttGateway {
/**
* 发送消息到默认主题
* @param payload 消息内容
*/
void sendToDefaultTopic(String payload);
/**
* 发送消息到指定主题
* @param payload 消息内容
* @param topic 目标主题
*/
void sendToTopic(String payload, @Header(MqttHeaders.TOPIC) String topic);
/**
* 发送消息到指定主题,并设置QoS
* @param topic 目标主题
* @param qos 消息质量等级
* @param payload 消息内容
*/
void sendToTopicWithQos(@Header(MqttHeaders.TOPIC) String topic,
@Header(MqttHeaders.QOS) int qos,
String payload);
}
7. 编写测试接口
创建MqttController
,提供 HTTP 接口测试 MQTT 消息发送:
java
less
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/mqtt")
@Tag(name = "MqttController", description = "MQTT消息测试接口")
public class MqttController {
@Autowired
private MqttGateway mqttGateway;
@PostMapping("/send/default")
@Operation(summary = "向默认主题发送消息")
public String sendToDefault(@RequestParam String payload) {
mqttGateway.sendToDefaultTopic(payload);
return "消息发送成功:" + payload;
}
@PostMapping("/send/custom")
@Operation(summary = "向自定义主题发送消息")
public String sendToCustom(@RequestParam String payload,
@RequestParam String topic) {
mqttGateway.sendToTopic(payload, topic);
return "向主题[" + topic + "]发送消息成功:" + payload;
}
}
8. 测试后端集成功能
- 启动 SpringBoot 应用,访问 Swagger 地址:
http://localhost:8088/swagger-ui.html
; - 调用
/mqtt/send/custom
接口,传入payload="来自后端的消息"
和topic="testTopicA"
; - 查看应用日志,会打印 "收到 MQTT 消息:来自后端的消息",同时订阅了
testTopicA
的前端窗口也会实时收到消息,说明集成成功。
六、总结:MQTT+RabbitMQ 的优势与适用场景
相比 WebSocket,MQTT+RabbitMQ 的组合有 3 个明显优势:
-
轻量高效:MQTT 协议头部仅 2 字节,带宽占用远低于 WebSocket,适合低带宽场景(如物联网设备)。
-
无需手写后端:基础即时通讯场景(如简单聊天、通知)可直接用前端 + RabbitMQ 实现,无需开发后端服务。
-
可靠性高:依赖 RabbitMQ 的消息持久化、重试机制,配合 QoS 等级,能避免消息丢失,适合关键业务。
适用场景推荐:
-
简单即时通讯:客服聊天、跨端通知;
-
物联网场景:设备状态同步、传感器数据上报;
-
轻量消息传递:支付通知、订单状态更新。
最后,附上本文实战项目的参考源码(基于 SpringBoot3,含前端 Demo):
github.com/macrozheng/...(该项目是标星 60K + 的电商实战项目,还包含微服务、Docker 部署等功能,值得学习)。
掌握这套方案,下次遇到即时通讯需求,就不用再局限于 WebSocket 啦[!))](url)