在 EMQX 中使用规则引擎记录 MQTT 消息日志(正确处理 Retain 参数)
在 EMQX 中使用规则引擎记录 MQTT 消息日志(正确处理 Retain 参数)
当你在 EMQX 中创建规则:
SELECT * FROM "#"并配置动作将消息转发到“日记主题”(如 journal/log)时,原始订阅者仍然应该能正常收到消息。
如果你发现“所有消息都被日记主题消费了”,很可能是对 Retain(保留消息) 参数的误解或配置不当所致。
一、核心原理澄清
✅ 规则引擎不会“消费”消息
- EMQX 的规则引擎是旁路监听机制,不会拦截或阻止原始消息投递。
- 原始订阅者(如订阅
sensor/+/temp的客户端)仍会正常收到实时消息。 如果订阅者收不到消息,请排查:
- 客户端是否真正订阅了对应主题
- 是否有通配符冲突
- 是否误用了
do_not_publish = true(默认为 false)
二、Retain 参数详解
什么是 Retain?
- MQTT 协议中的 保留消息(Retained Message) 机制。
- 当发布消息时设置
Retain = true,Broker 会为该主题保存最新一条消息。 - 新订阅者首次订阅该主题时,会立即收到这条保留消息(即使没有新发布)。
Retain 的典型用途
- 传递设备“当前状态”(如开关状态、传感器最新值)
- 不适用于日志、事件流等“流水型”数据
三、为什么 Retain 可能导致“消息被消费”的错觉?
| 现象 | 原因 |
|---|---|
| 客户端一订阅就收到一条旧消息 | republish 动作中设置了 Retain = true |
| 日志消费者处理异常或忽略消息 | 误将保留的旧日志当作实时消息 |
多个客户端订阅通配符主题(如 #) | 一次性收到大量 retain 消息,造成混乱 |
| 以为原始消息“没了” | 实际是 retain 行为干扰了预期,原始投递仍在进行 |
🔍 关键点:Retain 不会阻止原始消息投递,它只影响新订阅者的初始行为。
四、正确配置日志规则(推荐)
1. SQL 规则(可选增强)
SELECT
clientid,
username,
topic,
payload,
qos,
now as ts
FROM
"#"
WHERE
topic != 'sys/log/mqtt' -- 避免日志消息被自己再次捕获(防循环)2. 动作:消息重新发布(republish)
| 配置项 | 推荐值 | 说明 |
|---|---|---|
| 目标主题 | sys/log/mqtt | 使用独立命名空间,避免与业务主题冲突 |
| QoS | 0 或 1 | 日志通常用 QoS 0(高效),关键日志用 QoS 1 |
| Retain | false ✅ | 日志不是状态,不要保留! |
| Payload | 自定义 JSON(见下方) | 结构化便于后续处理 |
Payload 模板示例:
{
"clientid": "${clientid}",
"topic": "${topic}",
"payload": ${payload},
"qos": ${qos},
"timestamp": "${ts}"
}五、如何验证和调试
1. 测试原始消息是否正常投递
# 发布测试消息
mosquitto_pub -t "sensor/1/temp" -m '{"value": 25.5}'
# 订阅原始主题(新开终端)
mosquitto_sub -t "sensor/+/temp" -v
# ✅ 应能实时收到消息2. 检查日志主题是否收到副本
mosquitto_sub -t "sys/log/mqtt" -v
# 应收到结构化日志消息3. 清除 retain 消息(如有残留)
# 清除 journal/log 的 retain 消息
mosquitto_pub -t "journal/log" -n -r
# -n: 空 payload,-r: retain=true → 清除该主题的 retain六、最佳实践总结
| 项目 | 推荐配置 | 理由 |
|---|---|---|
| Retain | false | 日志是流水,非状态,无需保留 |
| QoS | 0(默认) | 高效;若需可靠日志,选 1 |
| 目标主题 | 独立前缀(如 sys/log/...) | 避免被 # 或 + 通配符误匹配 |
| Payload | JSON 结构化 | 包含元数据(topic、clientid、ts 等) |
| 规则 WHERE | 过滤日志主题自身 | 防止规则循环触发 |
七、常见误区排查清单
- [ ] 是否在
republish动作中误设Retain = true? - [ ] 日志消费者是否订阅了通配符(如
#),导致收到大量 retain 消息? - [ ] 原始订阅者是否真的在线并正确订阅了主题?
- [ ] 是否有多个规则或客户端意外消费了消息?
- [ ] 是否启用了共享订阅(
$share/...),导致消息被分摊?
八、结论
将
Retain设置为false是日志场景的标准做法。
正确配置后,EMQX 会:
- 正常投递原始消息给所有订阅者 ✅
- 同时复制一份结构化日志到指定主题 ✅
- 不会产生“消息被消费”的错觉 ✅
如仍有问题,建议使用 mosquitto_sub 或 MQTTX 工具分别监听原始主题和日志主题,验证消息流向。
实用