EMQX 实战进阶博客:从入门到物联网架构(60讲精编版)
博客定位 :面向物联网开发者、后端工程师、DevOps、架构师
写作风格 :场景引入 → 核心概念 → 实战演示 → 配置详解 → 避坑指南
实验环境 :华为云 ECS 集群 ecs-58cd (Ubuntu 24.04),Docker 部署 EMQX 5.5.0
客户端工具 :mosquitto-clients + MQTTX + Python paho-mqtt 2.1.0
学习前提:具备 Linux 基础,了解 Docker 基本操作
目录
- [第一章:EMQX 基础入门(10讲)](#第一章:EMQX 基础入门(10讲))
- [第1讲:什么是 EMQX?为什么选择它?](#第1讲:什么是 EMQX?为什么选择它?)
- [第2讲:MQTT 协议快速入门](#第2讲:MQTT 协议快速入门)
- [第3讲:5分钟快速安装 EMQX](#第3讲:5分钟快速安装 EMQX)
- [第4讲:EMQX Dashboard 详解](#第4讲:EMQX Dashboard 详解)
- [第5讲:EMQX 目录结构与配置文件](#第5讲:EMQX 目录结构与配置文件)
- [第6讲:MQTT 客户端连接与认证](#第6讲:MQTT 客户端连接与认证)
- 第7讲:发布订阅实战
- [第8讲:EMQX 监听器配置](#第8讲:EMQX 监听器配置)
- 第9讲:ACL(访问控制列表)配置
- [第10讲:EMQX 日志与监控](#第10讲:EMQX 日志与监控)
第一章:EMQX 基础入门(10讲|认识 MQTT 与 EMQX)
学习目标 :从零开始掌握 EMQX 平台的核心功能,能够独立部署、配置和管理 EMQX 消息中间件。
实验环境 :华为云 ECS git-01 (121.36.109.127) | Docker 部署 EMQX 5.5.0
辅助环境:git-02 (121.36.18.75) 作为 MQTT 客户端 | git-03 (121.36.44.45) 备选客户端
第1讲:什么是 EMQX?为什么选择它?
📍 你是否遇到过?
"公司要做物联网项目,几十万台设备要实时通信,传统的 HTTP 接口扛不住了。"
"我在用 Mosquitto,但它不支持集群,单节点也撑不住 10 万连接。"
"MQTT Broker 选型看了 HiveMQ 和 VerneMQ,但价格太贵或者社区不够活跃。"
如果你也面临这些问题,这一讲带你了解 EMQX------目前全球最流行的开源 MQTT Broker。
🔬 原理解析:EMQX 是什么?
EMQX = Erlang MQTT Broker,是一个基于 Erlang/OTP 语言开发的开源、高性能、分布式的 MQTT 消息中间件。
它的核心使命很简单:
┌──────────┐ ┌──────────┐
│ 设备 A │────── publish ───────▶│ │────── subscribe ───▶│ 设备 C │
│ (传感器) │ │ EMQX │ │ (监控屏) │
└──────────┘ │ │ └──────────┘
┌─────│ Broker │─────┐
┌──────────┐ │ │ │ │ ┌──────────┐
│ 设备 B │── publish ──────┘ └──────────┘ └────── subscribe │ 设备 D │
│ (控制器) │ │ (数据存储) │
└──────────┘ └──────────┘
EMQX 支持 MQTT 3.1 / 3.1.1 / 5.0 全部协议版本,兼容 CoAP、LwM2M、WebSocket、HTTP 等多种协议。
📊 EMQX vs 其他 MQTT Broker
| Broker | 最大连接数 | 集群支持 | 规则引擎 | 开源 | 适用场景 |
|---|---|---|---|---|---|
| EMQX | 1亿+ | ✅ | ✅ | ✅ | 大规模物联网(推荐!) |
| Mosquitto | 10万 | ❌ | ❌ | ✅ | 小规模、单机测试 |
| HiveMQ | 1000万 | ✅ | ✅ | ❌(商业) | 企业级(预算充足时) |
| VerneMQ | 100万 | ✅ | ✅ | ✅ | 中等规模替代 |
| NanoMQ | 1000万 | ❌ | ❌ | ✅ | 边缘计算、嵌入式 |
💡 为什么 EMQX 能支持 1 亿+ 连接? 答案在于 Erlang/OTP 的 Actor 模型------每个 MQTT 连接就是一个轻量级进程(Green Process),天然支持海量并发。
🚀 EMQX 核心优势
| 优势 | 说明 | 技术支撑 |
|---|---|---|
| 海量连接 | 单节点百万级,集群亿级 | Erlang Actor 模型 + Mnesia 分布式数据库 |
| 低延迟 | 毫秒级消息路由 | 内存级消息路由 + 零拷贝优化 |
| 多协议 | MQTT / CoAP / LwM2M / WebSocket | 内置协议网关 |
| 规则引擎 | 内置 SQL 语法数据处理 | 实时数据过滤、转换、转发 |
| 集群 | 水平扩展、高可用 | RLOG 复制 + Core-Replicant 架构 |
| 安全 | TLS / ACL / 多认证 | 支持 JWT / LDAP / OAuth2 / 数据库认证 |
💻 命令实操:第一次接触 EMQX
在我们已部署的环境中验证:
bash
# 查看 EMQX 版本和运行状态
$ docker exec emqx emqx ctl broker
sysdescr : EMQX
version : 5.5.0
datetime : 2026-06-05T15:43:54.057653440+00:00
uptime : 2 minutes, 18 seconds
说明:
sysdescr: EMQX--- 确认是 EMQXversion: 5.5.0--- 当前版本号(2026年6月最新版)uptime: 2 minutes--- 启动后的运行时长
🏭 典型应用场景
| 场景 | 连接规模 | 消息特点 | EMQX 优势 |
|---|---|---|---|
| 智能家居 | 万级 | 低频控制指令 | 低延迟、多协议支持 |
| 车联网 | 百万级 | 高频 GPS + 状态上报 | 海量连接、规则引擎实时处理 |
| 工业物联网 | 十万级 | 传感器定时上报 | 集群高可用、数据持久化 |
| 移动推送 | 亿级 | APP 消息推送 | 超高并发的连接管理 |
| 边缘计算 | 千级/节点 | 云边协同 | NanoMQ 边缘轻量版 |
🛡️ 避坑指南
-
"EMQX 只支持 MQTT?"------错!
EMQX 内置多协议网关,同时支持 CoAP、LwM2M、HTTP/WebHook。通过 Gateway 插件可以接入更多协议。
-
开源版 vs 企业版
功能 开源版 企业版 核心 MQTT Broker ✅ ✅ 集群 ✅ ✅ 规则引擎 ✅ ✅ 数据桥接到 40+ 数据库 ❌ ✅ LDAP / OAuth2 认证 ❌ ✅ 审计日志与合规 ❌ ✅ -
EMQX ≠ EMQ X
EMQX 是 EMQ 公司的旗舰产品。旧版本(4.x)叫 EMQ X,5.0 起改名 EMQX。见到
emqx和EMQ X都是指同一个产品。
📋 附录速查
| 需求 | 链接/方法 |
|---|---|
| EMQX 官网 | https://www.emqx.com/zh |
| GitHub 开源仓库 | https://github.com/emqx/emqx |
| 在线文档(v5.5) | https://docs.emqx.com/zh/emqx/v5.5/ |
| 版本历史 | https://www.emqx.com/zh/changelogs |
| Docker Hub | https://hub.docker.com/r/emqx/emqx |
第2讲:MQTT 协议快速入门
📍 你是否遇到过?
"我听说过 MQTT,但不太理解发布/订阅模式和传统的 HTTP 请求/响应有什么区别。"
"主题(Topic)里的通配符 # 和 + 到底怎么用?QoS 0/1/2 如何选择?"
"保留消息和遗嘱消息分别是什么场景?"
这一讲带你 15 分钟快速掌握 MQTT 协议核心概念,并且全部有真实命令验证。
🔬 原理解析:MQTT 的三要素
MQTT(Message Queuing Telemetry Transport)是一种基于发布/订阅模式的轻量级消息协议,专为低带宽、高延迟、不可靠网络下的物联网场景设计。
MQTT 架构三要素
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Publisher │ │ Broker │ │ Subscriber │
│ (发布者) │ ───▶ │ (代理) │ ───▶ │ (订阅者) │
│ │ │ │ │ │
│ 温度传感器 │ │ EMQX │ │ 监控面板 │
│ pub temp=25 │ │ 消息路由 │ │ sub 收到25 │
└───────────────┘ └───────────────┘ └───────────────┘
与 HTTP 的核心区别:
| 维度 | MQTT 发布/订阅 | HTTP 请求/响应 |
|---|---|---|
| 通信方向 | 一对多(发布一次,多订阅者收到) | 一对一(请求一次,响应一次) |
| 连接方式 | 长连接(持久 TCP) | 短连接(每次请求新建) |
| 消息发起 | 发布者主动推送 | 客户端主动拉取 |
| 服务端推送 | ✅ 原生支持 | ❌ 需要 WebSocket/SSE |
| 协议开销 | 最小 2 字节头部 | 典型 500+ 字节头部 |
| 适用场景 | 物联网、消息推送 | Web API、网页请求 |
💻 命令实操
1. 发布/订阅基本操作
bash
# 终端1(git-01):订阅主题
$ mosquitto_sub -t 'test/hello' -h 127.0.0.1 -p 1883 -v
# ^^
# 显示主题名
# 终端2(git-01):发布消息
$ mosquitto_pub -t 'test/hello' -m 'Hello EMQX!' -h 127.0.0.1 -p 1883
# 终端1 输出:
test/hello Hello EMQX!
跨服务器订阅(git-02 订阅 git-01 的 EMQX):
bash
# git-02 上操作(121.36.18.75)
$ mosquitto_sub -t 'sensor/temp' -h 121.36.109.127 -p 1883 -C 1 &
$ mosquitto_pub -t 'sensor/temp' -m '25.5' -h 121.36.109.127 -p 1883
# 输出:
25.5
✅ 跨服务器发布订阅成功!git-02 通过内网(192.168.0.x)直连 git-01 的 EMQX。
2. MQTT 主题(Topic)详解
MQTT 主题采用层级结构,用 / 分隔:
# 单层主题
sensor/temperature # 温度传感器
# 多层主题(推荐:有组织地设计)
factory/line1/sensor/temperature # 工厂1号线温度
factory/line1/sensor/humidity # 工厂1号线湿度
factory/line2/actuator/motor # 工厂2号线电机
# 通配符 #
sensor/# # 匹配 sensor/ 下所有层级
# 匹配: sensor/temp、sensor/line1/temp、sensor/a/b/c/d ...
# 通配符 +
factory/+/sensor/+ # 匹配单层级
# 匹配: factory/line1/sensor/temp、factory/line2/sensor/hum
通配符实战:
bash
# 发布各类传感器数据
$ mosquitto_pub -t 'sensor/line1/temp' -m '26' -h 127.0.0.1
$ mosquitto_pub -t 'sensor/line2/temp' -m '28' -h 127.0.0.1
$ mosquitto_pub -t 'sensor/line1/hum' -m '60' -h 127.0.0.1
# 使用 # 订阅所有传感器数据
$ mosquitto_sub -t 'sensor/#' -h 127.0.0.1 -v -C 3
sensor/line1/temp 26
sensor/line2/temp 28
sensor/line1/hum 60
3. QoS(服务质量)等级
MQTT 定义了三个 QoS 等级,在可靠性、延迟和开销之间取得平衡:
| QoS | 含义 | 保证 | 重传 | 适用场景 |
|---|---|---|---|---|
| QoS 0 | At most once | 最多一次 | ❌ | 高频传感器数据,允许偶尔丢失 |
| QoS 1 | At least once | 至少一次 | ✅(可能重复) | 重要数据上报,接受去重处理 |
| QoS 2 | Exactly once | 恰好一次 | ✅(无重复) | 计费信息、关键控制指令 |
bash
# QoS 1 测试 ------ 确认机制
$ mosquitto_sub -t 'qos/test' -h 127.0.0.1 -q 1 -C 1 &
$ mosquitto_pub -t 'qos/test' -m 'QoS1_OK' -h 127.0.0.1 -q 1
# 输出:
QoS1_OK
# 区别:QoS 1 的 PUBLISH 报文包含 PacketId,Broker 会回复 PUBACK
4. 保留消息(Retained Message)
保留消息让后订阅的客户端也能收到最新的状态:
bash
# 发布一条保留消息(-r 标志)
$ mosquitto_pub -t 'status/device1' -m 'online' -h 127.0.0.1 -r
# 新订阅者上线 ------ 立即收到保留消息(不用等下一次发布!)
$ mosquitto_sub -t 'status/device1' -h 127.0.0.1 -C 1
online
# 查看 EMQX 中保留消息数量
$ docker exec emqx emqx ctl retainer info
Number of retained messages: 4
经典用途:设备状态(在线/离线)、固件版本、配置信息等。
5. 遗嘱消息(Last Will)
设备意外断线时,Broker 自动发布遗嘱消息通知其他客户端:
bash
# 订阅遗嘱主题(监听设备状态)
$ mosquitto_sub -t 'client/status' -h 127.0.0.1 -v &
client/status online # ← 上线消息
# 设备连接时声明遗嘱
$ mosquitto_sub -t 'test' -h 127.0.0.1 \
-i 'device-will-demo' \
--will-topic 'client/status' \
--will-payload 'offline' \
--will-qos 1
# 按下 Ctrl+C 断开...
# 订阅端输出:
client/status offline # ← Broker 代发了遗嘱消息!
🛡️ 避坑指南
-
Topic 不要以
/开头bash❌ /sensor/temperature # 开头多了一个空层级 ✅ sensor/temperature -
#只能放在最后bash✅ sensor/# # 匹配所有子主题 ❌ sensor/#/alert # # 后面不能再有层级 ❌ # # 匹配所有主题(慎用!负载杀手) -
QoS 降级原则
订阅者的 QoS ≤ 发布者的 QoS。如果发布者用 QoS 2,订阅者用 QoS 0,实际投递是 QoS 0!
-
保留消息不是"数据库"
一个 Topic 只有一条保留消息(最新的那条)。不适合做历史数据存储。
-
遗嘱消息的触发条件
只有非正常断开 时才触发。正常调用
disconnect()不会触发遗嘱。
📋 附录速查
| 需求 | mosquitto 命令 |
|---|---|
| 订阅 | mosquitto_sub -t 主题 -h 地址 -p 端口 |
| 发布 | mosquitto_pub -t 主题 -m 消息 -h 地址 -p 端口 |
| 保留消息 | mosquitto_pub -t 主题 -m 消息 -r |
| 遗嘱消息 | mosquitto_sub --will-topic 主题 --will-payload 消息 --will-qos 1 |
| QoS 级别 | 添加 -q 0/1/2 |
通配符 # |
mosquitto_sub -t "sensor/#" |
通配符 + |
mosquitto_sub -t "factory/+/sensor/+" |
| 指定客户端ID | -i client_id |
第3讲:5分钟快速安装 EMQX
📍 你是否遇到过?
"想试试 EMQX,但不知道安装哪种方式最好。"
"官网看了半天,Docker、源码编译、二进制包...到底选哪个?"
"安装完打不开 Dashboard,一片茫然。"
这一讲带你走通 5 分钟 安装 EMQX 的完整流程,每条命令都是真实可执行的。
🔬 原理解析:安装方式对比
| 安装方式 | 难度 | 耗时 | 推荐场景 |
|---|---|---|---|
| Docker | ⭐ 极简 | 2 分钟 | 开发/测试环境(推荐!) |
| 二进制包(deb/rpm) | ⭐⭐ 简单 | 5 分钟 | 生产环境物理机 |
| Kubernetes (Helm) | ⭐⭐⭐ 中等 | 10 分钟 | 云原生生产环境 |
| 源码编译 | ⭐⭐⭐⭐⭐ 困难 | 30 分钟+ | 定制开发 |
💻 命令实操
步骤1:安装 Docker(如果还没有)
bash
# 在 git-01 (Ubuntu 24.04) 上安装 Docker
$ apt-get update
$ apt-get install -y ca-certificates curl gnupg
# 添加 Docker 官方 GPG 密钥(华为云 ECS 用国内镜像)
$ curl -fsSL https://repo.huaweicloud.com/docker-ce/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.asc
$ chmod a+r /etc/apt/keyrings/docker.asc
# 添加 Docker 源
$ echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.asc] \
https://repo.huaweicloud.com/docker-ce/linux/ubuntu noble stable" \
> /etc/apt/sources.list.d/docker.list
# 安装 Docker
$ apt-get update && apt-get install -y docker-ce docker-ce-cli \
containerd.io docker-buildx-plugin docker-compose-plugin
# 验证
$ docker --version
Docker version 29.5.2, build 79eb04c
步骤2:配置镜像加速器(国内必配)
bash
# 创建 daemon.json 配置
$ mkdir -p /etc/docker
$ cat > /etc/docker/daemon.json << 'EOF'
{
"registry-mirrors": [
"https://docker.1ms.run",
"https://docker.xuanyuan.me"
]
}
EOF
$ systemctl daemon-reload && systemctl restart docker
# 验证配置生效
$ docker info 2>&1 | grep -A3 'Registry Mirrors'
Registry Mirrors:
https://docker.1ms.run/
https://docker.xuanyuan.me/
⚠️ 踩坑记录 :华为云 ECS 直接从 docker.io 拉取镜像会超时。中科大镜像
docker.mirrors.ustc.edu.cn也可能 DNS 解析失败。实测docker.1ms.run和docker.xuanyuan.me可用。
步骤3:拉取并启动 EMQX
bash
# 拉取 EMQX 5.5.0 镜像
$ docker pull emqx/emqx:5.5.0
5c1db8ac0ad6: Download complete
62aa9fff265f: Download complete
c91b8ebc1f24: Download complete
...
Digest: sha256:d5703ac4b6cdba024657428661a6334fa4807705286036634ab8ccb73f3e1aef
Status: Downloaded newer image for emqx/emqx:5.5.0
# 启动 EMQX 容器
$ docker run -d --name emqx \
-p 1883:1883 \ # MQTT TCP
-p 8883:8883 \ # MQTT SSL
-p 8083:8083 \ # WebSocket
-p 8084:8084 \ # WebSocket SSL
-p 18083:18083 \ # Dashboard
--restart=always \
emqx/emqx:5.5.0
46e4fa4f0aede1bbb61588fa0c750f2ac1599b356dc65317bc7b3efc877a9cfd
端口说明:
| 端口 | 协议 | 用途 |
|---|---|---|
| 1883 | TCP | MQTT 标准端口(最常用) |
| 8883 | TCP/SSL | MQTT over SSL |
| 8083 | WebSocket | MQTT over WS |
| 8084 | WebSocket/SSL | MQTT over WSS |
| 18083 | HTTP | Dashboard 管理界面 |
步骤4:验证启动
bash
# 查看容器状态
$ docker ps --filter name=emqx --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
NAMES STATUS PORTS
emqx Up 5 seconds 0.0.0.0:1883->1883/tcp, 0.0.0.0:8883->8883/tcp,
0.0.0.0:8083-8084->8083-8084/tcp, 0.0.0.0:18083->18083/tcp
# 查看启动日志
$ docker logs emqx --tail 15
Listener ssl:default on 0.0.0.0:8883 started.
Listener tcp:default on 0.0.0.0:1883 started.
Listener ws:default on 0.0.0.0:8083 started.
Listener wss:default on 0.0.0.0:8084 started.
Listener http:dashboard on :18083 started.
EMQX 5.5.0 is running now!
✅ 所有监听器启动成功!看到
EMQX 5.5.0 is running now!即完成。
步骤5:访问 Dashboard
地址:http://121.36.109.127:18083
用户名:admin
密码:public
首次登录请立即修改密码!
bash
# 也可以创建新管理员用户
$ docker exec emqx emqx ctl admins add iot_user iot_pass123 'IoT Test User'
ok
步骤6:验证 MQTT 功能
bash
# 安装 mosquitto-clients
$ apt-get install -y mosquitto-clients
# 订阅测试
$ mosquitto_sub -t 'test/hello' -h 127.0.0.1 -p 1883 -C 1 &
$ mosquitto_pub -t 'test/hello' -m 'Hello EMQX!' -h 127.0.0.1 -p 1883
# 输出:
Hello EMQX!
🎉 发布订阅成功!EMQX 已就绪。
🛡️ 避坑指南
-
Docker 镜像拉取失败 / 超时
原因 解决方案 华为云 ECS 访问 docker.io 被阻断 使用国内镜像加速器(如上配置) DNS 解析失败 尝试不同镜像源: docker.1ms.run/docker.xuanyuan.me镜像源全部不可用 手动导出→传输→导入镜像 -
端口被占用
bash# 检查端口占用 $ ss -tlnp | grep -E '1883|8083|18083' # 如果被占用,更换宿主机端口映射 $ docker run -d --name emqx -p 1884:1883 -p 18084:18083 emqx/emqx:5.5.0 -
Dashboard 密码忘记
bash# 重置 admin 密码 $ docker exec emqx emqx ctl admins passwd admin new_password -
--restart=always别忘了!没有这个参数,服务器重启后 EMQX 不会自动启动。
📋 附录速查
| 需求 | Docker 命令 |
|---|---|
| 启动 EMQX | docker run -d --name emqx -p 1883:1883 -p 18083:18083 emqx/emqx:5.5.0 |
| 停止 | docker stop emqx |
| 重启 | docker restart emqx |
| 查看日志 | docker logs -f emqx |
| 进入容器 | docker exec -it emqx bash |
| 配置管理 | docker exec emqx emqx ctl |
| 卸载(危险) | docker stop emqx && docker rm emqx |
第4讲:EMQX Dashboard 详解
📍 你是否遇到过?
"登录了 EMQX Dashboard,但面对一堆菜单不知道从哪里看起。"
"想监控消息吞吐量,不知道 Dashboard 怎么看。"
"配置认证和授权难道都要改配置文件?Dashboard 能搞定吗?"
Dashboard 是管理 EMQX 的 图形化中枢。这一讲带你逐模块扫一遍。
🔬 原理解析:Dashboard 架构
Dashboard (:18083)
│
┌───────────┼───────────┐
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 监控模块 │ │ 配置模块 │ │ 系统模块 │
└─────────┘ └─────────┘ └─────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────┐
│ EMQX 核心引擎 │
│ 认证 / 授权 / 规则引擎 / 集群 / 日志 │
└─────────────────────────────────────┘
Dashboard 通过 REST API 与 EMQX 核心通信,所有配置变更实时生效。
💻 命令实操
1. 访问 Dashboard
URL: http://121.36.109.127:18083
登录: admin / public
2. 仪表盘(Overview)------ 全局快照
首页「监控 → 概览」显示核心指标:
| 指标 | 当前值(演示环境) | 说明 |
|---|---|---|
| Connections | 2 | 当前 TCP 连接数 |
| Subscriptions | 2 | 活跃订阅数 |
| Topics | 2 | 活跃主题数 |
| Messages In/Out | 动态 | 每秒入站/出站消息数 |
| Bytes In/Out | 动态 | 每秒入站/出站字节数 |
通过 emqx ctl 查看:
bash
$ docker exec emqx emqx ctl broker stats
channels.count : 2
connections.count : 2
live_connections.count : 2
retained.count : 4
sessions.count : 2
subscribers.count : 2
subscriptions.count : 2
topics.count : 2
3. 客户端(Clients)------ 谁在使用?
Dashboard →「监控 → 客户端」查看所有连接设备:
客户端列表包含的信息:
├── Client ID → 设备唯一标识
├── Username → 认证用户名
├── IP Address → 设备 IP
├── Connected At → 连接时间
├── Keep Alive → 心跳间隔
├── Subscriptions → 订阅的主题列表
└── Action → 踢出客户端(慎用!)
4. 订阅与主题(Subscriptions & Topics)
Dashboard →「监控 → 订阅」查看当前所有订阅关系:
- 哪个客户端订阅了哪个主题
- QoS 等级
- 共享订阅组(
$share/)
Dashboard →「监控 → 主题」查看当前活跃主题的统计。
5. 配置模块详解
认证(Authentication) --- 入口:配置 → 认证
认证器类型:
├── Built-in Database → 内置数据库(Docker 默认)
├── MySQL → 外部 MySQL 数据库
├── PostgreSQL → 外部 PostgreSQL
├── MongoDB → 外部 MongoDB
├── Redis → 外部 Redis
├── HTTP Server → 自定义认证 HTTP API
├── JWT → JWT Token 认证
└── LDAP → 企业 AD/LDAP
授权(Authorization) --- 入口:配置 → 授权
授权方式:
├── File → 静态文件 ACL
├── Built-in Database → 内置数据库动态 ACL
├── MySQL / PostgreSQL → 外部数据库
└── HTTP → 自定义授权 HTTP API
规则引擎(Rules) --- 入口:配置 → 规则
sql
-- Dashboard 中可以直接编写 SQL 规则
SELECT
payload.temp as temperature,
payload.hum as humidity,
timestamp() as ts
FROM "sensor/#"
WHERE payload.temp > 30
监听器(Listeners) --- 入口:配置 → 监听器
展示当前所有协议监听器:
bash
$ docker exec emqx emqx ctl listeners
tcp:default
listen_on : 0.0.0.0:1883
acceptors : 16
running : true
current_conn : 2
max_conns : infinity
ssl:default
listen_on : 0.0.0.0:8883
running : true
ws:default
listen_on : 0.0.0.0:8083
running : true
wss:default
listen_on : 0.0.0.0:8084
running : true
6. 系统模块(System)
| 子模块 | 功能 |
|---|---|
| 集群(Cluster) | 查看集群节点状态、拓扑 |
| 告警(Alarms) | 高水位、内存、CPU 异常告警 |
| 日志(Logs) | 在线查看和搜索日志 |
| 插件(Plugins) | 管理扩展插件 |
🛡️ 避坑指南
-
Dashboard 只能看当前节点的统计
集群模式下,Dashboard 默认显示所在节点的数据。查看全集群需要切换到「集群概览」视图。
-
Dashboard 监控数据有延迟
指标刷新间隔约 5-10 秒,不要用 Dashboard 做精确的实时监控(用 Prometheus + Grafana)。
-
不要用默认密码跑生产
bash# 立即修改密码! $ docker exec emqx emqx ctl admins passwd admin YourStrongP@ssw0rd! -
Dashboard 端口不要暴露到公网
bash# Docker 启动时绑定到 127.0.0.1 或内网 IP $ docker run -d -p 127.0.0.1:18083:18083 emqx/emqx:5.5.0
📋 附录速查
| 模块 | Dashboard 路径 | emqx ctl 命令 |
|---|---|---|
| 概览 | 监控 → 概览 | emqx ctl broker stats |
| 客户端 | 监控 → 客户端 | emqx ctl clients list |
| 订阅 | 监控 → 订阅 | emqx ctl subscriptions list |
| 主题 | 监控 → 主题 | emqx ctl topics list |
| 认证 | 配置 → 认证 | Dashboard |
| 授权 | 配置 → 授权 | Dashboard |
| 监听器 | 配置 → 监听器 | emqx ctl listeners |
| 规则 | 配置 → 规则 | emqx ctl rules list |
| 集群 | 系统 → 集群 | emqx ctl cluster status |
| 日志 | 系统 → 日志 | docker logs emqx |
第5讲:EMQX 目录结构与配置文件
📍 你是否遇到过?
"EMQX 配置文件改了半天不生效,不知道应该改哪个文件。"
"
emqx.conf和cluster.hocon是什么关系?""想备份配置,不知道该备份哪些文件。"
理解 EMQX 的目录结构和配置文件体系是运维的基础。这一讲讲清楚。
🔬 原理解析:配置体系三合一
EMQX 5.x 的配置来源于三个渠道,按优先级合并:
优先级 高 ┌─────────────────────┐
↑ │ 环境变量 │ EMQX_NODE__NAME=emqx@192.168.1.1
│ │ (Environment) │
│ ├─────────────────────┤
│ │ API/Dashboard 变更 │ data/configs/cluster.hocon
│ │ (Runtime) │ (集群自动同步)
│ ├─────────────────────┤
│ │ 静态配置文件 │ etc/emqx.conf
│ │ (Bootstrap) │ (集群节点首次启动使用)
优先级 低 └─────────────────────┘
⚠️ 关键规则 :运行时通过 Dashboard/API 修改的配置存储在
data/configs/cluster.hocon,同一配置不要同时写在emqx.conf和cluster.hocon中,否则可能冲突。
💻 命令实操
步骤1:了解核心目录结构
bash
# 查看 EMQX 容器内的目录
$ docker exec emqx ls -la /opt/emqx/
total 52
drwxr-xr-x 1 emqx emqx 4096 May 12 23:13 bin # 启动脚本
drwxr-xr-x 1 emqx emqx 4096 Jun 5 15:34 data # 运行时数据(Mnesia 数据库、日志)
drwxr-xr-x 1 emqx emqx 4096 May 12 23:13 etc # 配置文件目录
drwxr-xr-x 1 emqx emqx 4096 May 12 23:13 lib # Erlang 运行时库
drwxr-xr-x 1 emqx emqx 4096 May 12 23:13 log # 日志文件
drwxr-xr-x 1 emqx emqx 4096 May 12 23:13 releases # 版本发布文件
完整目录说明:
| 路径 | 用途 | 持久化建议 |
|---|---|---|
/opt/emqx/etc/ |
配置文件 | 挂载到宿主机 |
/opt/emqx/data/ |
Mnesia 数据 + 规则引擎状态 | 强烈建议持久化 |
/opt/emqx/log/ |
运行日志 | 可挂载或输出到 stdout |
/opt/emqx/bin/ |
启动/管理脚本 | 不需要修改 |
生产环境 Docker 挂载建议:
bash
docker run -d --name emqx \
-v /data/emqx/etc:/opt/emqx/etc \
-v /data/emqx/data:/opt/emqx/data \
-v /data/emqx/log:/opt/emqx/log \
-p 1883:1883 -p 18083:18083 \
emqx/emqx:5.5.0
步骤2:读取主配置文件
bash
$ docker exec emqx head -40 /opt/emqx/etc/emqx.conf
## NOTE:
## This config file overrides data/configs/cluster.hocon,
## and is merged with environment variables which start with 'EMQX_' prefix.
##
## Config changes made from EMQX dashboard UI, management HTTP API, or CLI
## are stored in data/configs/cluster.hocon.
## To avoid confusion, please do not store the same configs in both files.
node {
name = "emqx@127.0.0.1" # 节点名称
cookie = "emqxsecretcookie" # 集群 Cookie
data_dir = "data" # 数据目录
}
cluster {
name = emqxcl # 集群名称
discovery_strategy = manual # 集群发现策略
}
dashboard {
listeners.http {
bind = 18083 # Dashboard 端口
}
}
步骤3:核心配置项详解
| 配置块 | 关键项 | 说明 | 默认值 |
|---|---|---|---|
node.name |
节点名称 | name@host 格式,集群内唯一 |
emqx@127.0.0.1 |
node.cookie |
集群密钥 | 所有集群节点必须相同 | emqxsecretcookie |
cluster.name |
集群名 | 节点发现时按名称加入 | emqxcl |
cluster.discovery_strategy |
发现策略 | manual/static/dns/etcd/k8s |
manual |
dashboard.listeners.http.bind |
Dashboard 端口 | 建议内网绑定 | 18083 |
listeners.tcp.default.bind |
MQTT 监听地址 | 0.0.0.0:1883 |
默认 |
步骤4:环境变量覆盖配置
bash
# 用环境变量覆盖节点名称
$ docker run -d --name emqx-custom \
-e EMQX_NODE__NAME="emqx@192.168.0.100" \
-e EMQX_DASHBOARD__DEFAULT_PASSWORD="admin123" \
-p 1884:1883 -p 18084:18083 \
emqx/emqx:5.5.0
环境变量命名规则:
配置路径中的 . → __ (双下划线)
示例:
node.cookie → EMQX_NODE__COOKIE
dashboard.listeners.http.bind → EMQX_DASHBOARD__LISTENERS__HTTP__BIND
🛡️ 避坑指南
-
改了配置不生效?三个检查
bash# ① 检查是否改了正确的文件 $ docker exec emqx cat /opt/emqx/etc/emqx.conf | grep node.name # ② 检查 cluster.hocon 是否有冲突配置 $ docker exec emqx cat /opt/emqx/data/configs/cluster.hocon | grep node.name # ③ 检查环境变量是否覆盖 $ docker exec emqx env | grep EMQX_NODE -
node.cookie必须一致集群中所有节点的
node.cookie必须完全相同,否则无法加入集群。日志中看到incompatible cookie就是这个原因。 -
Docker 容器删除后数据丢失
默认
docker run不加-v挂载,删除容器后所有配置和数据都丢失。务必在生产环境挂载 data 目录!
📋 附录速查
| 需求 | 方法 |
|---|---|
| 查看当前有效配置 | docker exec emqx emqx ctl conf show |
| 重载配置 | docker exec emqx emqx ctl conf reload |
| 导出全部配置 | docker exec emqx emqx ctl conf show > all.conf |
| 导入配置 | docker exec emqx emqx ctl conf load /path/config.hocon |
| 检查配置语法 | docker exec emqx emqx ctl conf check_config |
| 查看配置文件 | docker exec emqx cat /opt/emqx/etc/emqx.conf |
第6讲:MQTT 客户端连接与认证
📍 你是否遇到过?
"任何人都能连上我的 EMQX,没有账号密码都能发消息,太不安全了!"
"配置了用户名密码认证,但客户端还是匿名就连接成功了。"
"生产环境需要 N 种认证方式(数据库 + JWT + LDAP),该怎么设计?"
认证是 EMQX 安全体系的第一道防线。这一讲带你从无认证 → 有认证 → 多认证。
🔬 原理解析:认证流程
┌──────────┐ CONNECT (username, password) ┌──────────┐
│ 客户端 │ ─────────────────────────────────▶ │ EMQX │
│ │ │ │
│ │ ┌───────▶│ 认证链 │
│ │ │ │ │
│ │ CONNACK (rc=0 成功) │ │ ├── 内置DB│
│ │ ◀───────────────────────────┘ │ ├── MySQL │
│ │ │ ├── JWT │
└──────────┘ │ └── LDAP │
└──────────┘
EMQX 5.x 支持认证链 ------按顺序依次尝试多个认证器,成功一个即可通过。也可以设置 disconnect_on_failure = true 让认证失败直接断开。
💻 命令实操
步骤1:基础连接测试
bash
# 匿名连接(无认证)
$ mosquitto_sub -t 'test' -h 127.0.0.1 -p 1883 -C 1 &
$ mosquitto_pub -t 'test' -m 'anonymous_ok' -h 127.0.0.1 -p 1883
anonymous_ok
# 此时任何人都能连接------不安全!
步骤2:配置内置数据库认证(核心)
EMQX 内置的 Built-in Database 认证器支持在 Dashboard 中直接管理用户。
方式一:Dashboard 配置
1. 登录 Dashboard → 配置 → 认证
2. 点击「创建」→ 选择「Built-in Database」
3. 认证器名:password_based
4. 用户管理 →「添加」
- 用户名:sensor_01
- 密码:sensor_pass_123
- 点击「添加」
方式二:API 创建
bash
# 添加用户到内置数据库
# 注意:需要通过 Dashboard API (使用 API Key)
# 这里先用 emqx ctl 演示
方式三:创建认证器后验证
创建认证器后,匿名连接将被拒绝,必须提供用户名密码:
bash
# 不带认证 → 失败
$ mosquitto_sub -t 'test' -h 127.0.0.1 -p 1883 -C 1
# 连接超时或被拒绝
# 带用户名密码 → 成功
$ mosquitto_sub -t 'test' -h 127.0.0.1 -u 'sensor_01' -P 'sensor_pass_123' -C 1 &
$ mosquitto_pub -t 'test' -h 127.0.0.1 -u 'sensor_01' -P 'sensor_pass_123' -m 'auth_ok'
auth_ok
步骤3:多种客户端连接 EMQX
Python (paho-mqtt) 连接:
bash
$ python3 << 'PYEOF'
import paho.mqtt.client as mqtt
import time
def on_connect(client, userdata, flags, rc, props=None):
print(f"Connected: rc={rc}")
client.subscribe("python/test")
def on_message(client, userdata, msg):
print(f"Received: {msg.topic} = {msg.payload.decode()}")
client = mqtt.Client(client_id="python-demo", protocol=mqtt.MQTTv5)
client.username_pw_set("sensor_01", "sensor_pass_123") # ← 认证!
client.on_connect = on_connect
client.on_message = on_message
client.connect("127.0.0.1", 1883, 60)
client.loop_start()
time.sleep(1)
client.publish("python/test", "Hello from Python!")
time.sleep(1)
client.loop_stop()
client.disconnect()
PYEOF
# 输出:
Connected: rc=Success
Received: python/test = Hello from Python!
MQTTX(图形化客户端):
下载:https://mqttx.app/zh
1. 新建连接 → 主机:121.36.109.127:1883
2. 用户名:sensor_01,密码:sensor_pass_123
3. 连接 → 添加订阅 → 发布测试消息
步骤4:认证器类型选择指南
| 认证方式 | 管理复杂度 | 灵活度 | 适用场景 |
|---|---|---|---|
| Built-in Database | ⭐ 低 | 中 | 少量设备(<1万),开发/测试 |
| MySQL / PostgreSQL | ⭐⭐ 中 | 高 | 设备管理已有数据库 |
| HTTP Server | ⭐⭐ 中 | 极高 | 自定义认证逻辑 |
| JWT | ⭐⭐ 中 | 高 | 微服务架构、API 网关集成 |
| LDAP | ⭐⭐⭐ 高 | 中 | 企业 AD/LDAP 统一认证 |
| Redis | ⭐⭐ 中 | 高 | 高性能缓存 + 临时凭证 |
🛡️ 避坑指南
-
创建认证器后匿名仍可连接
原因 :Dashboard 创建认证器后需重新连接客户端,已建立的连接不受影响。
解决:Dashboard → 客户端 → 踢出所有匿名客户端。
-
密码管理:不要明文存密码
bash# EMQX 5.x 内置数据库密码经过 PBKDF2 加密 # 但 MySQL 外部认证表的密码字段建议也用加密存储 -
认证失败排查
bash# 查看 EMQX 日志中认证失败的记录 $ docker logs emqx | grep -i 'auth\|login\|authenticate' | tail -20 -
不要只配一个认证器
建议至少配两个:数据库认证器 + HTTP 备用认证器。一个挂了至少有备用的。
📋 附录速查
| 需求 | 方法 |
|---|---|
| Dashboard 创建用户 | 配置 → 认证 → Built-in Database → 用户管理 → 添加 |
| API 创建用户 | POST /api/v5/authentication/{id}/users |
| 踢出客户端 | 监控 → 客户端 → 选择 → 踢出 |
| Python 连接(带认证) | client.username_pw_set("user", "pass") |
| 查看认证器列表 | 配置 → 认证 |
| 查看认证日志 | `docker logs emqx |
第7讲:发布订阅实战
📍 你是否遇到过?
"MQTT 基础概念看懂了,但实际写代码时不知道如何组织 Topic 结构。"
"多个设备同时发消息,怎么确保消息不会互相干扰?"
"想用 Python 做一个完整的 MQTT 采集程序,从连接、订阅、处理到断开。"
这一讲用三个完整的实战案例带你真正理解发布订阅。
💻 命令实操
案例1:多主题订阅 + 通配符
场景:一个监控中心需要同时接收所有工厂、所有产线的温度和湿度数据。
bash
# 模拟 4 台设备发布数据
$ mosquitto_pub -t 'factory/line1/sensor/temperature' -m '26' -h 127.0.0.1 -u sensor_01 -P sensor_pass_123
$ mosquitto_pub -t 'factory/line1/sensor/humidity' -m '60' -h 127.0.0.1 -u sensor_01 -P sensor_pass_123
$ mosquitto_pub -t 'factory/line2/sensor/temperature' -m '28' -h 127.0.0.1 -u sensor_01 -P sensor_pass_123
$ mosquitto_pub -t 'factory/line2/sensor/humidity' -m '55' -h 127.0.0.1 -u sensor_01 -P sensor_pass_123
# 监控中心订阅所有工厂传感器数据
$ mosquitto_sub -t 'factory/#' -h 127.0.0.1 -u sensor_01 -P sensor_pass_123 -v -C 4
factory/line1/sensor/temperature 26
factory/line1/sensor/humidity 60
factory/line2/sensor/temperature 28
factory/line2/sensor/humidity 55
bash
# 只订阅所有产线的温度(用 + 匹配单层)
$ mosquitto_sub -t 'factory/+/sensor/temperature' -h 127.0.0.1 -u sensor_01 -P sensor_pass_123 -v -C 2
factory/line1/sensor/temperature 26
factory/line2/sensor/temperature 28
案例2:Python 完整数据采集程序
场景:用 Python 订阅所有传感器数据,解析 JSON 格式消息,写入日志。
bash
$ cat > /tmp/mqtt_collector.py << 'PYEOF'
"""
MQTT 传感器数据采集器
订阅 factory/# ,解析 JSON 格式消息
"""
import paho.mqtt.client as mqtt
import json
import time
from datetime import datetime
# 统计数据
stats = {"temperature": 0, "humidity": 0, "total": 0}
def on_connect(client, userdata, flags, rc, props=None):
if rc == 0:
print(f"[{datetime.now():%H:%M:%S}] ✅ 已连接 EMQX")
client.subscribe("factory/#")
print(f"[{datetime.now():%H:%M:%S}] 📡 订阅: factory/#")
else:
print(f"[ERROR] 连接失败: rc={rc}")
def on_message(client, userdata, msg):
stats["total"] += 1
payload = msg.payload.decode()
topic_parts = msg.topic.split("/")
print(f"[{datetime.now():%H:%M:%S}] 📩 {msg.topic} = {payload}")
# 尝试解析 JSON
try:
data = json.loads(payload)
if "temp" in data:
stats["temperature"] += 1
if "hum" in data:
stats["humidity"] += 1
except json.JSONDecodeError:
pass
# 创建客户端(MQTT v5 + 认证)
client = mqtt.Client(client_id="collector-01", protocol=mqtt.MQTTv5)
client.username_pw_set("sensor_01", "sensor_pass_123")
client.on_connect = on_connect
client.on_message = on_message
# 连接并进入消息循环(阻塞等待消息)
client.connect("127.0.0.1", 1883, 60)
print(f"[{datetime.now():%H:%M:%S}] 🚀 数据采集器启动...")
print()
try:
client.loop_forever()
except KeyboardInterrupt:
print(f"\n[{datetime.now():%H:%M:%S}] 📊 统计:")
print(f" 总消息: {stats['total']}")
print(f" 温度消息: {stats['temperature']}")
print(f" 湿度消息: {stats['humidity']}")
client.disconnect()
PYEOF
# 后台启动采集器
$ python3 /tmp/mqtt_collector.py &
在另一个终端发布模拟数据:
bash
# 模拟传感器上报 JSON 格式数据
$ mosquitto_pub -t 'factory/line1/sensor/data' \
-m '{"temp": 26.5, "hum": 60, "ts": "2026-06-05T15:45:00Z"}' \
-h 127.0.0.1 -u sensor_01 -P sensor_pass_123
$ mosquitto_pub -t 'factory/line2/sensor/data' \
-m '{"temp": 28.1, "hum": 55, "ts": "2026-06-05T15:45:05Z"}' \
-h 127.0.0.1 -u sensor_01 -P sensor_pass_123
# 采集器输出:
[15:45:00] 📩 factory/line1/sensor/data = {"temp": 26.5, "hum": 60, ...}
[15:45:05] 📩 factory/line2/sensor/data = {"temp": 28.1, "hum": 55, ...}
案例3:跨服务器发布订阅
bash
# git-02 发布(121.36.18.75)
$ mosquitto_pub -t 'cross/server' -m 'Hello from git-02!' \
-h 121.36.109.127 -p 1883 -u sensor_01 -P sensor_pass_123
# git-03 订阅(121.36.44.45)
$ mosquitto_sub -t 'cross/server' -h 121.36.109.127 -p 1883 \
-u sensor_01 -P sensor_pass_123 -C 1
Hello from git-02!
✅ 跨服务器发布订阅成功!内网环境下延迟通常在 1-5ms。
🛡️ 避坑指南
-
Topic 设计原则
原则 好例子 坏例子 层级合理 factory/line1/sensor/tempfactoryline1sensortemp有命名规范 {source}/{line}/{type}/{field}随意起名 不包含敏感信息 device/datadevice/192.168.1.100/admin/root123从一般到具体 china/gd/sz/factory1/line1line1/factory1/sz/gd/china -
#订阅的陷阱bash# ❌ 订阅所有主题------CPU 和带宽杀手! $ mosquitto_sub -t '#' -h broker # ✅ 只订阅需要的内容 $ mosquitto_sub -t 'factory/#' -h broker -
Python paho-mqtt 回调版本
paho-mqtt 2.x 默认使用 Callback API v1(已弃用),新项目请用 v2(带
props参数的回调)。示例代码已更新为 v2 兼容写法。
📋 附录速查
| 需求 | Python paho-mqtt 代码 |
|---|---|
| 创建客户端(v5) | mqtt.Client(client_id="x", protocol=mqtt.MQTTv5) |
| 设置认证 | client.username_pw_set("user", "pass") |
| 连接 | client.connect("host", 1883, 60) |
| 订阅 | client.subscribe("topic", qos=1) |
| 发布 | client.publish("topic", "msg", qos=1) |
| 阻塞循环 | client.loop_forever() |
| 非阻塞循环 | client.loop_start() + loop_stop() |
| 断开 | client.disconnect() |
第8讲:EMQX 监听器配置
📍 你是否遇到过?
"我想让浏览器也连接 EMQX,但不知道 WebSocket 怎么配。"
"生产环境要求加密传输,SSL/TLS 证书怎么配置?"
"不同网络区域(内网、外网)需要不同的监听端口。"
监听器决定了设备如何连接 EMQX。正确配置监听器是 EMQX 运维的核心技能。
🔬 原理解析:四种监听器
EMQX 监听器体系
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
┌───────────┐ ┌───────────┐ ┌───────────┐
│ TCP 监听 │ │ SSL/TLS │ │ WebSocket │
│ :1883 │ │ :8883 │ │ :8083/8084│
├───────────┤ ├───────────┤ ├───────────┤
│ 设备直连 │ │ 加密传输 │ │ 浏览器/APP│
│ 最常用 │ │ 证书认证 │ │ MQTT over │
│ 性能最高 │ │ 安全必配 │ │ WS/WSS │
└───────────┘ └───────────┘ └───────────┘
💻 命令实操
步骤1:查看当前监听器状态
bash
$ docker exec emqx emqx ctl listeners
ssl:default
listen_on : 0.0.0.0:8883
acceptors : 16
running : true
current_conn : 0
max_conns : infinity
tcp:default
listen_on : 0.0.0.0:1883
acceptors : 16
running : true
current_conn : 2 # ← 当前有 2 个连接
max_conns : infinity
ws:default
listen_on : 0.0.0.0:8083
acceptors : 16
running : true
current_conn : 0
wss:default
listen_on : 0.0.0.0:8084
acceptors : 16
running : true
current_conn : 0
步骤2:配置文件方式添加监听器
bash
# 在 emqx.conf 中添加自定义 TCP 监听器
$ docker exec emqx cat >> /opt/emqx/etc/emqx.conf << 'EOF'
# 自定义内网监听器(仅内网访问)
listeners.tcp.internal {
bind = "192.168.0.1:1883"
max_connections = 100000
proxy_protocol = false
}
# 自定义外网监听器(限制连接数)
listeners.tcp.external {
bind = "0.0.0.0:3883"
max_connections = 1000
proxy_protocol = false
}
EOF
步骤3:SSL/TLS 监听器配置(核心)
生成自签名证书(仅用于测试):
bash
# 生成 CA 私钥和证书
$ openssl genrsa -out ca.key 2048
$ openssl req -x509 -new -nodes -key ca.key -sha256 -days 365 \
-subj "/CN=EMQX-CA" -out ca.crt
# 生成服务器私钥和证书请求
$ openssl genrsa -out server.key 2048
$ openssl req -new -key server.key -subj "/CN=121.36.109.127" -out server.csr
# 用 CA 签发服务器证书
$ openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 365 -sha256
将证书拷贝到 EMQX:
bash
$ docker cp ca.crt emqx:/opt/emqx/etc/certs/
$ docker cp server.crt emqx:/opt/emqx/etc/certs/
$ docker cp server.key emqx:/opt/emqx/etc/certs/
# 在 emqx.conf 中配置 SSL 监听器
listeners.ssl.default {
bind = "0.0.0.0:8883"
keyfile = "etc/certs/server.key"
certfile = "etc/certs/server.crt"
cacertfile = "etc/certs/ca.crt"
verify = verify_peer # 要求客户端也提供证书
fail_if_no_peer_cert = true
}
步骤4:监听器管理命令
bash
# 停止监听器
$ docker exec emqx emqx ctl listeners stop 'tcp:internal'
# 启动监听器
$ docker exec emqx emqx ctl listeners start 'tcp:internal'
# 重启监听器
$ docker exec emqx emqx ctl listeners restart 'tcp:default'
步骤5:端口速查表
| 端口 | 协议 | 默认用途 | 安全建议 |
|---|---|---|---|
| 1883 | TCP → MQTT | 设备直连 | 内网使用,外网禁止 |
| 8883 | TCP → MQTT + TLS | 加密 MQTT | 外网唯二入口之一 |
| 8083 | WebSocket → MQTT | 浏览器/Web App | 内网或配合 Nginx 反代 |
| 8084 | WebSocket + TLS | 安全 WebSocket | 外网唯二入口之二 |
| 18083 | HTTP → Dashboard | 管理界面 | 绝对不要暴露到公网! |
🛡️ 避坑指南
-
生产环境必须用 TLS
不要用 1883 明文端口!MQTT 消息(包括密码)在网络上明文传输,任何人都能截获。
-
Dashboard 端口安全
bash# ✅ 正确:只绑定内网 IP dashboard.listeners.http.bind = "192.168.0.1:18083" # ❌ 错误:绑定所有网卡 dashboard.listeners.http.bind = "0.0.0.0:18083" -
max_connections要合理设置bash# 预设资源下限。值太大可能导致 OOM listeners.tcp.default.max_connections = 100000 -
监听器重启会断开现有连接
使用 Dashboard 修改监听器配置会热重载(不断连),用 CLI
restart会断开现有连接。
📋 附录速查
| 需求 | 方法 |
|---|---|
| 查看所有监听器 | emqx ctl listeners |
| 停止指定监听器 | emqx ctl listeners stop 'tcp:default' |
| 启动指定监听器 | emqx ctl listeners start 'tcp:default' |
| 重启指定监听器 | emqx ctl listeners restart 'tcp:default' |
| 配置文件路径 | /opt/emqx/etc/emqx.conf |
| 证书推荐路径 | /opt/emqx/etc/certs/ |
| Dashboard 配置 | 配置 → 监听器 → 添加监听器 |
第9讲:ACL(访问控制列表)配置
📍 你是否遇到过?
"认证只是回答了'你是谁',但没回答'你能做什么'。"
"传感器只能发温度数据,但有人恶意往控制主题发消息。"
"不同部门的设备不能互相看到对方的消息。"
ACL(Access Control List)是 EMQX 安全体系的第二道防线------在认证之后,控制每个设备可以发布/订阅哪些主题。
🔬 原理解析:认证 vs 授权
┌──────────┐ 认证(AuthN) ┌──────────┐ 授权(AuthZ/ACL) ┌──────────┐
│ 你是谁? │ ────────────────▶ │ EMQX │ ──────────────────▶ │ 你能做什么?│
│ │ │ │ │ │
│ sensor01 │ ✅ 身份验证通过 │ │ ✅ 可以 pub sensor/# │ │
│ │ │ │ ❌ 不能 pub control/#│ │
└──────────┘ └──────────┘ └──────────┘
💻 命令实操
步骤1:ACL 规则设计
推荐设计思路:按设备类型划分权限。
┌──────────────────────────────────────────────────────────────┐
│ ACL 规则表 │
├──────────────┬──────────┬────────────────┬──────────────────┤
│ 设备类型 │ 用户名 │ 允许发布 Topic │ 允许订阅 Topic │
├──────────────┼──────────┼────────────────┼──────────────────┤
│ 温度传感器 │ sensor_* │ sensor/+/temp │ --- │
│ 智能网关 │ gateway │ gateway/data │ control/+/cmd │
│ 监控大屏 │ monitor │ --- │ sensor/# │
│ 管理员 │ admin │ # │ # │
│ 默认(拒绝) │ * │ --- │ --- │
└──────────────┴──────────┴────────────────┴──────────────────┘
💡 白名单原则:默认拒绝一切,只显式放行需要的权限。
步骤2:配置文件方式 ACL(静态 ACL)
bash
# 在 EMQX 中编写 ACL 文件
$ docker exec emqx cat > /opt/emqx/etc/acl.conf << 'EOF'
%% 管理员可以发布和订阅所有主题
{allow, {username, "admin"}, pubsub, ["#"]}.
%% 传感器只能发布自己的温度数据
{allow, {username, "sensor_01"}, publish, ["sensor/+/temp"]}.
%% 传感器只能订阅自己相关的配置命令
{allow, {username, "sensor_01"}, subscribe, ["config/sensor_01/#"]}.
%% 监控端可以订阅所有传感器数据
{allow, {username, "monitor"}, subscribe, ["sensor/#"]}.
%% 显式拒绝:任何人不能操作 $SYS 系统主题
{deny, all, pubsub, ["$SYS/#"]}.
%% 默认拒绝(放在最后)
{deny, all}.
EOF
ACL 规则匹配逻辑 :从上到下匹配,命中即停止。所以必须把 {deny, all}. 放最后。
步骤3:Dashboard 配置 ACL
1. 配置 → 授权 → 创建授权器
2. 选择「File」类型
3. 填写 ACL 文件路径:/opt/emqx/etc/acl.conf
4. 保存 → 测试权限
Dashboard 中测试权限:
配置 → 授权 → 授权器 → 授权测试
- Client ID: sensor_01
- Username: sensor_01
- Topic: sensor/line1/temp
- Action: publish → ✅ allow
- Topic: control/motor/cmd
- Action: publish → ❌ deny
步骤4:数据库动态 ACL(MySQL 示例)
sql
-- MySQL ACL 表结构
CREATE TABLE mqtt_acl (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(100) NOT NULL,
topic VARCHAR(256) NOT NULL,
action ENUM('publish','subscribe','pubsub') NOT NULL,
permission ENUM('allow','deny') NOT NULL DEFAULT 'allow',
INDEX idx_username (username)
);
-- 传感器:允许发布温度数据
INSERT INTO mqtt_acl VALUES
(1, 'sensor_01', 'sensor/+/temp', 'publish', 'allow'),
(2, 'sensor_02', 'sensor/+/temp', 'publish', 'allow');
-- 监控端:允许订阅所有传感器数据
INSERT INTO mqtt_acl VALUES
(3, 'monitor', 'sensor/#', 'subscribe', 'allow');
-- 默认拒绝已通过 ACL 配置实现
🛡️ 避坑指南
-
ACL 规则顺序很重要!
erlang%% ❌ 错误顺序------默认拒绝放在开头,后面规则都不生效 {deny, all}. {allow, {username, "admin"}, pubsub, ["#"]}. %% ✅ 正确顺序------具体规则在前,默认规则在后 {allow, {username, "admin"}, pubsub, ["#"]}. {deny, all}. -
永远拒绝
$SYS/#主题$SYS/#暴露了 EMQX 的内部运行状态,绝不能让普通设备读取。 -
别忘了测试
每次修改 ACL 后,立即用 Dashboard 的「授权测试」功能验证规则是否正确。不要等到上线被攻击才发现。
-
ACL 缓存
EMQX 会缓存 ACL 查询结果。如果修改了外部数据库的 ACL,需要手动清除缓存:
bash$ docker exec emqx emqx ctl authz cache-clean all
📋 附录速查
| 操作 | 权限值 |
|---|---|
| 只允许发布 | {allow, {user, "x"}, publish, ["topic"]}. |
| 只允许订阅 | {allow, {user, "x"}, subscribe, ["topic"]}. |
| 允许发布和订阅 | {allow, {user, "x"}, pubsub, ["topic"]}. |
| 拒绝一切 | {deny, all}. |
| 拒绝特定主题 | {deny, {user, "x"}, publish, ["$SYS/#"]}. |
第10讲:EMQX 日志与监控
📍 你是否遇到过?
"EMQX 运行了一段时间,客户端突然连不上了,不知道发生了什么。"
"想监控消息吞吐量,但不知道 Prometheus 怎么集成。"
"日志文件越来越大,怎么设置日志轮转?"
日志告诉你发生了什么 ,监控告诉你正在发生什么。两项能力加在一起,你才算真正掌控了 EMQX。
🔬 原理解析:日志与监控体系
EMQX 可观测性体系
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 日志 │ │ Dashboard│ │ Prometheus│
│ (控制台) │ │ (实时) │ │ (时序) │
├──────────┤ ├──────────┤ ├──────────┤
│ error级别 │ │ 概览数据 │ │ 趋势分析 │
│ 诊断问题 │ │ 当前状态 │ │ 容量规划 │
│ 安全审计 │ │ 快速排查 │ │ Grafana │
└──────────┘ └──────────┘ └──────────┘
💻 命令实操
步骤1:Docker 容器日志查看
bash
# 实时查看 EMQX 日志
$ docker logs -f emqx
# 查看最近 20 行
$ docker logs --tail 20 emqx
# 查看最近 5 分钟内的日志
$ docker logs --since 5m emqx
# 启动时的关键日志
$ docker logs emqx 2>&1 | tail -12
WARNING: Default (insecure) Erlang cookie is in use.
WARNING: Configure node.cookie in /opt/emqx/etc/emqx.conf
EMQX_RPC__PORT_DISCOVERY [rpc.port_discovery]: manual
EMQX_NODE__NAME [node.name]: emqx@172.17.0.2
Listener ssl:default on 0.0.0.0:8883 started.
Listener tcp:default on 0.0.0.0:1883 started.
Listener ws:default on 0.0.0.0:8083 started.
Listener wss:default on 0.0.0.0:8084 started.
Listener http:dashboard on :18083 started.
EMQX 5.5.0 is running now!
步骤2:日志级别管理
bash
# 查看当前日志级别
$ docker exec emqx emqx ctl log primary-level
# 默认: warning
# 设置日志级别(调试时常用 debug)
$ docker exec emqx emqx ctl log primary-level debug
# EMQX 支持的日志级别(由低到高):
# debug < info < notice < warning < error < critical < alert < emergency
各级别含义:
| 级别 | 用途 | 示例 |
|---|---|---|
debug |
开发调试 | 每条消息的详细路由信息 |
info |
一般信息 | 客户端连接/断开 |
notice |
值得注意 | 配置变更 |
warning |
警告 | Cookie 不安全、接近上限 |
error |
错误 | 认证失败、数据库连接断开 |
critical |
严重 | 磁盘满、内存溢出 |
alert |
需要人工处理 | 集群分裂 |
emergency |
系统不可用 | 进程崩溃 |
步骤3:实时监控指标
bash
# 查看 Broker 统计
$ docker exec emqx emqx ctl broker stats
channels.count : 2
connections.count : 2
retained.count : 4
sessions.count : 2
subscribers.count : 2
subscriptions.count : 2
topics.count : 2
关键指标解读:
| 指标 | 说明 | 告警阈值 |
|---|---|---|
connections.count |
当前连接数 | 接近 max_connections 时告警 |
subscriptions.count |
活跃订阅数 | 突然下降可能大量设备离线 |
retained.count |
保留消息数量 | 超过 10 万建议清理 |
topics.count |
活跃主题数 | 异常增长可能是主题风暴 |
步骤4:查看 Erlang VM 信息
bash
# 内存使用
$ docker exec emqx emqx ctl vm memory
# memory/processes : 25% # 进程内存使用率
# memory/system : 30% # 系统总内存使用率
# CPU 负载
$ docker exec emqx emqx ctl vm load
# cpu/load1 : 0.15 # 1分钟平均负载
# cpu/load5 : 0.10 # 5分钟平均负载
# cpu/load15 : 0.08 # 15分钟平均负载
步骤5:集成 Prometheus(企业监控)
bash
# EMQX 5.x 默认启用了 Prometheus 指标端点
# 访问:http://localhost:18083/api/v5/prometheus/stats
# 示例 Prometheus 配置 (prometheus.yml)
scrape_configs:
- job_name: 'emqx'
static_configs:
- targets: ['121.36.109.127:18083']
metrics_path: '/api/v5/prometheus/stats'
basic_auth:
username: 'admin'
password: 'public'
重要 Prometheus 指标:
| 指标 | 说明 |
|---|---|
emqx_connections_count |
当前连接数 |
emqx_messages_received |
累计接收消息数 |
emqx_messages_sent |
累计发送消息数 |
emqx_messages_qos0/1/2_received |
各 QoS 消息数 |
emqx_bytes_received/sent |
网络流量 |
emqx_sessions_count |
当前会话数 |
步骤6:保留消息管理
bash
# 查看保留消息数量
$ docker exec emqx emqx ctl retainer info
Number of retained messages: 4
# 查看保留消息的主题列表
$ docker exec emqx emqx ctl retainer topics 1 100
# 清理指定主题的保留消息
$ docker exec emqx emqx ctl retainer clean "status/#"
# 清理所有保留消息(危险!)
$ docker exec emqx emqx ctl retainer clean
🛡️ 避坑指南
-
生产环境不要长期开 debug 日志
debug 级别日志量极大,严重影响性能且快速填满磁盘。排查完问题后立即改回
warning。 -
Docker 日志驱动
bash# 限制 Docker 日志大小(防止撑满磁盘) $ docker run -d --name emqx \ --log-driver json-file \ --log-opt max-size=100m \ --log-opt max-file=3 \ -p 1883:1883 -p 18083:18083 \ emqx/emqx:5.5.0 -
保留消息不是无限的
EMQX 默认保留消息数量上限很大(取决于内存),但建议设置上限:
bash# enqx.conf retainer.max_payload_size = 1MB # 单条最大 1MB retainer.max_retained_messages = 10000 # 最多保留 1 万条 -
cookie 不安全的警告可以忽略吗?
WARNING: Default (insecure) Erlang cookie is in use.生产环境不能忽略 !修改
emqx.conf中的node.cookie为随机字符串。
📋 附录速查
| 需求 | 命令 |
|---|---|
| 实时日志 | docker logs -f emqx |
| 设置日志级别 | emqx ctl log primary-level <level> |
| Broker 统计 | emqx ctl broker stats |
| VM 内存 | emqx ctl vm memory |
| VM 负载 | emqx ctl vm load |
| 保留消息数量 | emqx ctl retainer info |
| 清理保留消息 | emqx ctl retainer clean |
| Prometheus 指标 | GET /api/v5/prometheus/stats |
| Dashboard 监控 | http://ip:18083 → 监控 → 概览 |
🎯 第一章小结
10 讲带你从零掌握 EMQX 基础:
| 讲次 | 主题 | 核心要点 |
|---|---|---|
| 第1讲 | EMQX 是什么 | Erlang 构建,开源高性能,1 亿+连接,vs HiveMQ/Mosquitto |
| 第2讲 | MQTT 协议 | 三要素(Publisher/Broker/Subscriber)、Topic、QoS 0/1/2、保留/遗嘱消息 |
| 第3讲 | 安装 EMQX | Docker 5 分钟部署,镜像加速配置,端口说明完整 |
| 第4讲 | Dashboard | 概览/客户端/订阅/主题/配置/系统,6 大模块逐一讲解 |
| 第5讲 | 目录与配置 | 三合一配置体系,emqx.conf、cluster.hocon、环境变量 |
| 第6讲 | 认证 | Built-in DB 认证,Python paho-mqtt 连接,6 种认证方式 |
| 第7讲 | 发布订阅 | 3 个完整案例:通配符、Python 采集器、跨服务器通信 |
| 第8讲 | 监听器 | TCP/SSL/WS/WSS 4 种监听器,TLS 证书配置,端口速查 |
| 第9讲 | ACL | 认证 vs 授权,静态文件 ACL + 数据库动态 ACL,白名单原则 |
| 第10讲 | 日志监控 | 日志级别管理、VM 监控、Prometheus 集成、保留消息管理 |
核心心法:
| 心法 | 说明 |
|---|---|
| 认证 + 授权 = 安全底线 | 先回答"你是谁",再控制"你能做什么" |
| Docker 部署 = 简单高效 | 5 分钟上线,挂载数据目录保平安 |
| Topic 设计 = 架构基础 | 好的 Topic 命名让规则引擎和 ACL 事半功倍 |
| 日志 + 监控 = 运维之眼 | 不监控等于瞎跑,不记日志等于摸黑 |
| Dashboard + CLI 配合使用 | Dashboard 看趋势,CLI 做精确控制和自动化 |
🎓 第一章完成!你已经掌握了 EMQX 的基础操作。下一章将深入到规则引擎------EMQX 最强大的数据处理能力。
第二章:规则引擎实战(12讲)------ 从零实现 IoT 消息流处理
第11讲:规则引擎核心概念------EMQX 的数据处理大脑
一、场景引入
假设你正在开发一个智慧工厂的物联网平台。工厂中有上千个传感器,每分钟产生数十万条消息。你需要:
- 将温度超过 50°C 的数据实时告警到运维平台
- 把原始数据持久化到 MySQL 供 BI 分析
- 将经过清洗的数据转发到 Kafka 供流式计算
如果让你自己写代码实现这些------监听 MQTT 主题 + 解析 JSON + 条件判断 + 调用多个外部系统------工作量巨大且容易出错。
EMQX 的规则引擎就是为解决这个问题而设计的。它让你用一条 SQL 语句就能完成数据过滤、转换和路由。
二、规则引擎是什么
规则引擎是 EMQX 内置的消息流处理引擎,工作流程如下:
MQTT 消息 → [SQL 匹配] → [条件过滤] → [消息转换] → [动作执行]
↓ ↓ ↓ ↓
规则触发 WHERE 子句 SELECT 字段 Webhook/DB/Kafka
核心思想:用 SQL 描述"当什么消息到来时,做什么事"。
-- 伪代码理解
SELECT 你想要处理的字段
FROM 消息来源(主题匹配)
WHERE 消息满足的条件
ACTION 做什么(Webhook、存数据库、重发布)
三、规则引擎的三大核心组件
| 组件 | 说明 | 类比 |
|---|---|---|
| 消息源(Source) | MQTT 消息的来源主题 | 数据输入管道 |
| 规则(Rule) | SQL 语句,定义匹配哪些消息 | 过滤和转换逻辑 |
| 动作(Action) | 匹配后执行的操作 | 数据输出目标 |
在 EMQX 5.x 中还有一个重要的概念 --- 连接器(Connector),它是动作访问外部资源的桥梁。
Topic → Rule (SQL) → Action → Connector → 外部系统
↓
Webhook / MySQL / Kafka / MongoDB / ...
踩坑提醒 🔥:EMQX 5.x CE(社区版)的 Connector 类型只有
http和mqtt。如果要连接 MySQL、Kafka 等,需要结合 HTTP API 服务做桥接,或者使用企业版。
四、规则引擎架构(5.x vs 4.x)
EMQX 5.x 对规则引擎进行了重大重构:
| 对比维度 | EMQX 4.x | EMQX 5.x |
|---|---|---|
| 架构 | Rule → Resource → Action 紧耦合 | Rule → Connector → Action 松耦合 |
| Connector 概念 | 无(Resource 承担连接器角色) | 有(独立管理连接配置) |
| 配置方式 | ini 风格配置文件 | Dashboard 可视化 + REST API |
| Action 复用 | 不支持 | Connector 可被多个 Action 共享 |
| SQL 引擎 | 基于内置规则引擎 | 增强的 SQL 规则引擎 |
5.x 架构模型:
┌─────────────┐
│ Connector │ ← URL、认证信息等连接参数
│ (HTTP/MySQL) │
└──────┬──────┘
│ 引用
┌──────▼──────┐
│ Action │ ← path、method、body模板
│ (send_data) │
└──────┬──────┘
│ 绑定
┌──────▼──────┐
│ Rule │ ← SQL + Actions 列表
│ (rule_01) │
└─────────────┘
一个 Connector 可以被多个 Action 引用,一个 Rule 可以绑定多个 Action。
五、规则引擎的消息处理流程
1. MQTT Client → Publish(temperature/office, {"temp": 45})
2. EMQX 接收消息后检查所有启用的 Rules
3. Rule SQL: SELECT * FROM "temperature/#"
→ 匹配到 topic: temperature/office ✅
4. WHERE 条件: payload.temp > 40
→ 45 > 40 ✅,符合过滤条件
5. 执行绑定的 Action 列表:
├─ action_alert: 发送 Webhook 告警
└─ action_store: 存入 MySQL
6. 完成!整个流程在毫秒级内完成
六、本机实验环境
bash
# 环境确认
$ docker ps | grep emqx
46e4fa4f0aed emqx/emqx:5.5.0 ... 0.0.0.0:1883->1883/tcp emqx
# EMQX Dashboard
URL: http://121.36.109.127:18083
用户名: admin
密码: public
版本: EMQX 5.5.0 (Community Edition)
# API Token(每次使用需重新获取)
$ curl -s -X POST http://localhost:18083/api/v5/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"public"}'
{
"license": {"edition": "ce"},
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"version": "5.5.0"
}
# MQTT 客户端
$ mosquitto_pub -h localhost -t "test/topic" -m '{"key":"value"}'
$ mosquitto_sub -h localhost -t "test/#" -v
重要 :Dashboard API Token 有有效期。笔者实验中发现 Token 会过期,如果 API 返回
HTTP_CODE:400,请重新获取 Token。
七、本讲小结
| 知识点 | 核心要点 |
|---|---|
| 规则引擎定位 | 消息流处理引擎,用 SQL 描述处理逻辑 |
| 三大组件 | Source(消息源) → Rule(SQL规则) → Action(动作) |
| 5.x 架构升级 | 引入 Connector 概念,Action 可复用 |
| 处理流程 | 消息到达 → SQL匹配 → 条件过滤 → 执行动作 |
下一步:第 12 讲将深入规则引擎的 SQL 语法------如何写出第一条 SELECT 语句。
第12讲:SQL 语法与 SELECT 语句------从消息中提取数据
一、为什么要用 SQL 处理 MQTT 消息
MQTT 消息的本质是一段字节流。要从杂乱的消息中提取有价值的数据,我们需要:
- 字段选择 --- 只提取需要的数据(SELECT)
- 条件过滤 --- 只处理感兴趣的消息(WHERE)
- 类型转换 --- 把字符串转成数字(CAST)
- 内置函数 --- 时间格式化、字符串处理等
EMQX 规则引擎的 SQL 语法与标准 SQL 高度相似,但有一些物联网特有的扩展。
二、SELECT 基本语法
sql
SELECT
<字段1> AS <别名1>,
<字段2> AS <别名2>,
...
FROM
"<主题过滤>"
最简单的规则:
sql
SELECT
*
FROM
"sensor/#"
这条 SQL 匹配所有 sensor/ 开头的主题消息,并将全部字段传递给后续动作。
三、可用的消息字段
EMQX 为每条 MQTT 消息自动提取以下字段,可在 SQL 中直接使用:
| 字段名 | 类型 | 说明 | 示例值 |
|---|---|---|---|
topic |
string | 消息主题 | sensor/temperature |
qos |
int | QoS 级别 | 0, 1, 2 |
payload |
string | 消息负载(原始字符串) | {"temp":25.5} |
clientid |
string | 发布方客户端ID | mqttx_abc123 |
username |
string | 发布方用户名 | sensor_user |
timestamp |
int | 消息到达时间戳(ms) | 1749139278263 |
peerhost |
string | 发布方 IP 地址 | 192.168.0.123 |
flags |
map | 消息标志位 | {retain: false, dup: false} |
id |
string | MQTT 消息 ID | 10 |
实际测试:
bash
# 发布一条消息
$ mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"sensor01","value":28.3,"unit":"C"}'
# 在规则引擎 SQL 测试中,使用以下变量可以看到完整上下文
SELECT
topic as 主题,
qos as 质量,
clientid as 客户端,
peerhost as 来源IP,
timestamp as 时间戳
FROM
"sensor/#"
这条 SQL 会在规则触发时输出 5 个字段:主题、QoS 级别、客户端 ID、来源 IP、时间戳。
四、payload 字段的 JSON 操作
MQTT 消息的 payload 通常是 JSON 格式。通过点号 (.) 访问 JSON 对象的属性:
payload.device → "sensor01"
payload.value → 28.3
payload.unit → "C"
示例:
sql
SELECT
payload.device AS device_id,
payload.value AS temperature,
payload.unit AS unit
FROM
"sensor/#"
输出结果(SQL 测试模式下):
| device_id | temperature | unit |
|---|---|---|
| sensor01 | 28.3 | C |
五、常用的 SQL 函数
EMQX 规则引擎支持丰富的内置函数:
1. 类型转换函数
| 函数 | 说明 | 示例 |
|---|---|---|
CAST(val AS TYPE) |
类型转换 | CAST(payload.value AS FLOAT) |
STRING(val) |
转字符串 | STRING(25.5) → "25.5" |
2. 数学函数
| 函数 | 说明 | 示例 |
|---|---|---|
ABS(val) |
绝对值 | ABS(-10) → 10 |
ROUND(val) |
四舍五入 | ROUND(25.567, 2) → 25.57 |
FLOOR(val) |
向下取整 | FLOOR(25.9) → 25 |
3. 字符串函数
| 函数 | 说明 | 示例 |
|---|---|---|
CONCAT(s1, s2, ...) |
字符串拼接 | CONCAT("sensor_", payload.device) |
LENGTH(str) |
字符串长度 | LENGTH(payload.device) |
SUBSTRING(str, start, len) |
截取子串 | SUBSTRING(topic, 7, 4) |
UPPER(str) / LOWER(str) |
大小写转换 | UPPER(topic) |
4. 时间函数
| 函数 | 说明 | 示例 |
|---|---|---|
NOW() |
当前时间 | NOW() → 1749139278263 |
FORMAT_DATE(ts, "format") |
格式化时间 | FORMAT_DATE(NOW(), "%Y-%m-%d") |
5. 条件函数
| 函数 | 说明 | 示例 |
|---|---|---|
COALESCE(val1, val2, ...) |
返回第一个非空值 | COALESCE(payload.temp, 0) |
实战示例 --- 组装上报数据:
sql
SELECT
payload.device AS device_id,
payload.value AS raw_value,
CAST(payload.value AS FLOAT) AS temperature,
CONCAT(payload.value, ' ', payload.unit) AS display,
FORMAT_DATE(NOW(), '%Y-%m-%d %H:%i:%s') AS report_time,
topic AS source_topic
FROM
"sensor/#"
六、SQL 测试功能(Dashboard 实操)
EMQX Dashboard 提供了 SQL 测试功能,可以在不发布真实消息的情况下验证 SQL 语句是否正确:
操作步骤:
- 打开 Dashboard → 数据集成 → 规则
- 点击「创建规则」
- 输入 SQL 语句
- 在右侧的「SQL 测试」区域输入测试数据:
json
{
"topic": "sensor/temperature",
"qos": 1,
"payload": "{\"device\":\"sensor01\",\"value\":28.3,\"unit\":\"C\"}",
"clientid": "mqttx_test",
"timestamp": 1749139278263
}
- 点击「测试」,查看 SQL 执行结果
测试结果预览:
| device_id | raw_value | temperature | display | report_time | source_topic |
|---|---|---|---|---|---|
| sensor01 | 28.3 | 28.3 | 28.3 C | 2026-06-05 23:50:00 | sensor/temperature |
七、本讲小结
| 知识点 | 核心要点 |
|---|---|
| SELECT 语法 | SELECT 字段列表 FROM "主题过滤" |
| 内置字段 | topic, qos, payload, clientid, timestamp 等 10+ 个字段 |
| JSON 操作 | payload.device, payload.value 点号访问 |
| SQL 函数 | 类型转换、数学、字符串、时间、条件等 20+ 个函数 |
| SQL 测试 | Dashboard 内置,无需发布真实消息即可验证 SQL |
下一步:第 13 讲将介绍 WHERE 条件过滤和 CASE 表达式------如何精准筛选消息。
第13讲:WHERE 条件过滤与 CASE 表达式------精准筛选消息
一、场景引入
智慧工厂中有 500+ 传感器,但我们只关心温度超过 50°C 的告警消息。如果没有条件过滤,所有传感器消息都会触发动作,不仅浪费资源,还会导致告警平台被淹没。
WHERE 子句就是用来解决这个问题的。
二、WHERE 基本语法
sql
SELECT <字段>
FROM "<主题>"
WHERE <条件表达式>
三、比较运算符
| 运算符 | 说明 | 示例 |
|---|---|---|
= |
等于 | payload.device = 'sensor01' |
<> / != |
不等于 | payload.status <> 'offline' |
> |
大于 | payload.value > 50 |
< |
小于 | payload.value < 0 |
>= |
大于等于 | payload.value >= 30 |
<= |
小于等于 | payload.value <= 100 |
实战示例 1 --- 温度告警过滤:
sql
SELECT
payload.device AS device,
payload.value AS temperature,
topic AS source
FROM
"sensor/#"
WHERE
payload.value > 50
只有 payload.value > 50 的消息才会触发后续动作。
实战示例 2 --- 多条件组合:
sql
SELECT
payload.device AS device,
payload.value AS temperature,
payload.unit AS unit
FROM
"sensor/#"
WHERE
payload.value > 50
AND payload.unit = 'C'
四、逻辑运算符
| 运算符 | 说明 | 优先级 |
|---|---|---|
AND |
逻辑与(所有条件都满足) | 中 |
OR |
逻辑或(任一条件满足) | 低 |
NOT |
逻辑非(取反) | 高 |
实战示例 3 --- 复杂逻辑:
sql
-- 场景:温度 > 50°C 或 湿度 > 90% 的告警
SELECT
payload.device AS device,
payload.value AS metric_value,
topic AS metric_type
FROM
"sensor/#"
WHERE
(payload.value > 50 AND topic = 'sensor/temperature')
OR
(payload.value > 90 AND topic = 'sensor/humidity')
五、字符串匹配
| 函数 | 说明 | 示例 |
|---|---|---|
LIKE |
通配符匹配 | topic LIKE 'sensor/temp%' |
NOT LIKE |
取反匹配 | topic NOT LIKE 'sensor/internal%' |
IN |
值在列表中 | payload.device IN ('s01','s02','s03') |
通配符说明:
| 通配符 | 含义 | 示例 |
|---|---|---|
% |
匹配任意多个字符 | topic LIKE 'sensor/%' |
_ |
匹配单个字符 | payload.level LIKE 'L_' |
六、NULL 值处理
| 运算符 | 说明 | 示例 |
|---|---|---|
IS NULL |
值为空 | payload.value IS NULL |
IS NOT NULL |
值不为空 | payload.device IS NOT NULL |
sql
-- 过滤掉没有 value 字段的消息
SELECT * FROM "sensor/#"
WHERE payload.value IS NOT NULL
七、CASE 表达式 --- 条件分支
CASE 表达式让你在 SQL 中实现 if-else 逻辑:
语法:
sql
CASE
WHEN <条件1> THEN <结果1>
WHEN <条件2> THEN <结果2>
ELSE <默认结果>
END
实战示例 4 --- 温度分级:
sql
SELECT
payload.device AS device,
payload.value AS temperature,
CASE
WHEN payload.value > 80 THEN 'CRITICAL'
WHEN payload.value > 50 THEN 'WARNING'
WHEN payload.value > 30 THEN 'NORMAL'
ELSE 'LOW'
END AS alert_level,
CASE
WHEN payload.value > 80 THEN 1
WHEN payload.value > 50 THEN 2
WHEN payload.value > 30 THEN 3
ELSE 0
END AS alert_priority
FROM
"sensor/#"
WHERE
topic = 'sensor/temperature'
输出示例:
| device | temperature | alert_level | alert_priority |
|---|---|---|---|
| sensor01 | 28.3 | LOW | 0 |
| sensor01 | 55.0 | WARNING | 2 |
| sensor01 | 82.1 | CRITICAL | 1 |
实战示例 5 --- 动态目标 URL:
sql
SELECT
payload.device AS device,
payload.value AS temperature,
CASE
WHEN payload.value > 80 THEN '/alarm/critical'
WHEN payload.value > 50 THEN '/alarm/warning'
ELSE '/alarm/info'
END AS target_path
FROM
"sensor/temperature"
通过 CASE 表达式动态生成 Webhook URL 路径,实现消息分级路由。
八、实战:条件过滤完整测试
bash
# 发布不同温度的消息
$ mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"sensor01","value":28.3,"unit":"C"}' # NORMAL → 不触发告警
$ mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"sensor02","value":55.0,"unit":"C"}' # WARNING → 触发告警
$ mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"sensor03","value":82.1,"unit":"C"}' # CRITICAL → 触发告警
对应的规则 SQL:
sql
SELECT
payload.device AS device,
payload.value AS temperature,
CASE
WHEN payload.value > 80 THEN 'CRITICAL'
WHEN payload.value > 50 THEN 'WARNING'
ELSE 'NORMAL'
END AS alert_level
FROM
"sensor/temperature"
WHERE
CAST(payload.value AS FLOAT) > 50
踩坑提醒 🔥:payload.value 默认是字符串类型,数值比较时建议使用
CAST(payload.value AS FLOAT)确保正确比较。否则"55" > "100"在字符串比较下为true(按字典序),这显然不是你期望的。
九、本讲小结
| 知识点 | 核心要点 |
|---|---|
| 比较运算符 | =, <>, >, <, >=, <= |
| 逻辑运算符 | AND, OR, NOT |
| 字符串匹配 | LIKE, IN |
| CASE 表达式 | CASE WHEN ... THEN ... ELSE ... END |
| 类型转换 | CAST(payload.value AS FLOAT) 避免字符串比较问题 |
下一步:第 14 讲将介绍 FROM 子句与事件表------规则引擎的事件源机制。
第14讲:FROM 子句与事件表------理解消息源匹配机制
一、FROM 子句的作用
FROM 子句决定了哪些消息会触发这条规则 。它的核心是主题过滤(Topic Filter)。
sql
SELECT * FROM "传感器主题" WHERE 条件过滤
二、主题通配符
EMQX 继承了 MQTT 协议的通配符规则:
| 通配符 | 含义 | 匹配示例 |
|---|---|---|
# |
匹配所有子层级 | sensor/# 匹配 sensor/temp, sensor/floor1/temp |
+ |
匹配单个层级 | sensor/+/temp 匹配 sensor/room1/temp,不匹配 sensor/room1/floor1/temp |
对比示例:
topic: sensor/floor1/room1/temperature
匹配规则:
✅ "sensor/#" → # 匹配 floor1/room1/temperature
✅ "sensor/+/+/temperature" → 第一个 + 匹配 floor1,第二个 + 匹配 room1
✅ "sensor/floor1/+/temperature" → + 匹配 room1
❌ "sensor/+/temperature" → + 只匹配一层,但这里有 floor1/room1 两层
❌ "sensor/floor1/temperature" → 缺少 room1 这一层
三、FROM 子句的高级用法
1. 匹配多个主题
sql
-- 同时监听温度和湿度传感器
SELECT * FROM
"sensor/temperature",
"sensor/humidity",
"sensor/pressure"
等同于多个独立的 FROM 子句(逗号分隔 = OR 关系)。
2. 使用逗号分隔 vs 使用 #
sql
-- 方式1: 精确匹配(性能更好)
FROM "sensor/temperature", "sensor/humidity"
-- 方式2: 通配符匹配(更简洁)
FROM "sensor/#"
建议 :如果能确定主题列表,使用精确匹配性能更好 。只有需要匹配大量动态主题时才使用
#通配符。
3. 排除特定主题
EMQX SQL 本身不支持直接排除主题,但可以结合 WHERE 子句实现:
sql
SELECT *
FROM "sensor/#"
WHERE topic <> 'sensor/internal/debug'
四、事件表(Event Table)
除了 MQTT 消息主题,EMQX 还支持系统事件作为触发源:
| 事件 | FROM 语法 | 触发时机 |
|---|---|---|
| 消息发布 | FROM "topic" |
任意 MQTT 消息发布到匹配主题 |
| 客户端连接 | FROM "$events/client_connected" |
客户端成功连接 |
| 客户端断开 | FROM "$events/client_disconnected" |
客户端断开连接 |
| 会话创建 | FROM "$events/session_created" |
新会话创建 |
| 会话终止 | FROM "$events/session_terminated" |
会话终止 |
| 消息投递 | FROM "$events/message_delivered" |
消息成功投递 |
| 消息确认 | FROM "$events/message_acked" |
消息被确认 |
| 消息丢弃 | FROM "$events/message_dropped" |
消息被丢弃 |
实战示例 6 --- 监控客户端上线/下线:
sql
-- 客户端连接事件
SELECT
clientid,
username,
peerhost,
connected_at,
'online' AS status
FROM
"$events/client_connected"
-- 客户端断开事件
SELECT
clientid,
username,
reason,
'offline' AS status
FROM
"$events/client_disconnected"
实战示例 7 --- 监控消息丢弃:
sql
SELECT
topic,
qos,
clientid,
reason,
payload
FROM
"$events/message_dropped"
五、事件字段说明
客户端连接事件 ($events/client_connected) 的可用字段:
| 字段 | 类型 | 说明 |
|---|---|---|
clientid |
string | 客户端ID |
username |
string | 用户名 |
peerhost |
string | 客户端IP |
sockport |
int | 客户端端口 |
proto_name |
string | 协议名称 |
proto_ver |
int | 协议版本 |
keepalive |
int | 心跳间隔 |
clean_start |
bool | 是否干净连接 |
connected_at |
int | 连接时间 |
客户端断开事件 ($events/client_disconnected) 的可用字段:
| 字段 | 类型 | 说明 |
|---|---|---|
clientid |
string | 客户端ID |
username |
string | 用户名 |
reason |
string | 断开原因(normal/keepalive_timeout/taken_over等) |
disconnected_at |
int | 断开时间 |
六、FROM 子句最佳实践
| 实践 | 说明 |
|---|---|
| ✅ 精确匹配优先 | 能用 sensor/temp 就不用 sensor/# |
| ✅ 避免规则重叠 | 不要让两条规则的 FROM 子句完全重叠(浪费计算) |
| ✅ 利用事件表监控 | 客户端连接/断开事件可用于设备在线统计 |
| ✅ 逗号表示 OR | FROM "a","b" 等价于匹配 a 或 b |
✅ # 慎用 |
# 匹配所有层级,在大型系统中可能产生性能影响 |
七、本讲小结
| 知识点 | 核心要点 |
|---|---|
| FROM 子句 | 定义规则触发的消息来源(主题过滤) |
# 通配符 |
匹配所有子层级 |
+ 通配符 |
匹配单个层级 |
| 多主题匹配 | 逗号分隔实现 OR 逻辑 |
| 事件表 | $events/* 系统事件触发规则 |
| 客户端事件 | client_connected / client_disconnected 监控上下线 |
下一步:第 15 讲将介绍规则动作(Action)配置------如何定义"做什么"。
第15讲:规则动作(Action)配置------定义消息处理的行为
一、Action 在规则引擎中的角色
回顾规则引擎的三层架构:
Connector → Action → Rule
(怎么连) (做什么) (何时触发)
| 层级 | 职责 | 示例 |
|---|---|---|
| Connector | 定义连接参数 | URL、认证、超时、连接池 |
| Action | 定义请求模板 | HTTP 方法、路径、Body、Headers |
| Rule | 定义触发条件 | SQL 语句 |
一个 Connector 可以被多个 Action 共享,一个 Rule 可以绑定多个 Action。
二、EMQX 5.5 CE 支持的 Action 类型
通过 EMQX API 查看可用类型:
bash
$ curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:18083/api/v5/action_types
["http", "mqtt"]
CE 版支持的两种 Action:
| Action 类型 | 用途 | 适用场景 |
|---|---|---|
http |
发送 HTTP 请求 | Webhook、调用 REST API、数据转发 |
mqtt |
重新发布 MQTT 消息 | 消息格式转换后二次发布、桥接转发 |
说明:商业版额外支持 Kafka、MySQL、PostgreSQL、MongoDB、Redis、Cassandra、RabbitMQ、Pulsar 等 20+ 种 Action。CE 版用户可以通过 HTTP Action 调用自定义 API 服务,由 API 服务负责写入数据库或消息队列(桥接模式)。
三、HTTP Action 完整配置参数
根据 EMQX 5.5.0 的 API Schema,HTTP Action 的完整参数:
| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
path |
string | 是 | - | 请求路径(会拼接在 Connector URL 后面) |
method |
enum | 否 | post |
HTTP 方法:post, put, get, delete |
body |
string | 否 | - | 请求体模板(支持占位符) |
headers |
object | 否 | 预设值 | 自定义 HTTP 请求头 |
max_retries |
int | 否 | 2 |
失败重试次数 |
request_timeout |
duration | 否 | 15s |
请求超时时间 |
Resource Opts(动作资源选项):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
worker_pool_size |
int | 16 | 工作线程池大小 |
query_mode |
enum | async |
查询模式:sync / async |
request_ttl |
duration | 45s |
请求生存时间 |
inflight_window |
int | 100 | 在途请求窗口大小 |
max_buffer_bytes |
byteSize | 256MB |
最大缓冲区大小 |
health_check_interval |
duration | 15s |
健康检查间隔 |
四、MQTT Action 配置参数
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
topic |
string | 是 | 目标主题(支持占位符 ${variable}) |
qos |
int | 否 | QoS 级别:0/1/2 |
retain |
bool | 否 | 是否保留消息 |
payload |
string | 是 | 消息模板(支持占位符) |
五、创建 Action 的正确步骤
第1步:创建 Connector
bash
curl -X POST http://localhost:18083/api/v5/connectors \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"type": "http",
"name": "webhook_connector",
"url": "http://172.17.0.1:8080"
}'
响应:
json
{
"type": "http",
"name": "webhook_connector",
"status": "connected",
"url": "http://172.17.0.1:8080"
}
关键 :Connector 的 URL 应该只包含基础地址 (如
http://host:port),不要带路径。具体路径由 Action 的path参数决定。
第2步:创建 Action
bash
curl -X POST http://localhost:18083/api/v5/actions \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"type": "http",
"name": "send_to_webhook",
"connector": "webhook_connector",
"parameters": {
"path": "/webhook",
"method": "post",
"body": "{\"topic\":\"${topic}\",\"payload\":${payload}}",
"max_retries": 2,
"request_timeout": "5s"
}
}'
响应:
json
{
"name": "send_to_webhook",
"status": "connected",
"connector": "webhook_connector",
"parameters": {
"path": "/webhook",
"method": "post",
"body": "{\"topic\":\"${topic}\",\"payload\":${payload}}"
}
}
第3步:创建 Rule(绑定 Action)
bash
curl -X POST http://localhost:18083/api/v5/rules \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"id": "rule_sensor_forward",
"sql": "SELECT * FROM \"sensor/#\"",
"actions": ["http:send_to_webhook"],
"description": "Forward sensor data to webhook"
}'
踩坑提醒 🔥:
- Action 引用格式必须是
"类型:名称"(如"http:send_to_webhook"),不能只写名称- Connector URL 和 Action path 会拼接为最终请求 URL
- 如果 Connector URL 是
http://host:8080/webhook且 Action path 是/api,最终 URL 为http://host:8080/webhook/api- 建议 Connector URL 只写到基础地址,路径放到 Action 的
path中
六、完整实操测试
1. 准备 Webhook 接收服务:
python
# webhook_server.py
from flask import Flask, request, jsonify
import json, datetime
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
timestamp = datetime.datetime.now().isoformat()
print(f'[{timestamp}] Received: {json.dumps(data, indent=2)}')
return jsonify({"status": "ok"}), 200
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
2. 创建规则引擎(Connector → Action → Rule):如上文 API 步骤所示。
3. 发送 MQTT 消息触发测试:
bash
# 发送测试消息
$ mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"sensor01","value":28.3,"unit":"C"}'
$ mosquitto_pub -h localhost -t 'sensor/humidity' \
-m '{"device":"sensor02","value":55.7,"unit":"%"}'
$ mosquitto_pub -h localhost -t 'sensor/alert' \
-m '{"device":"sensor01","type":"overheat","value":55.0}'
$ mosquitto_pub -h localhost -t 'sensor/pressure' \
-m '{"device":"sensor03","value":1013.25,"unit":"hPa"}'
4. 验证结果:
bash
# 查看 Webhook 日志
$ python3 -c "
with open('/tmp/webhook.log','rb') as f:
data = f.read().decode('utf-8', errors='replace')
lines = [l for l in data.split('\n') if l.strip()]
for l in lines[-8:]:
print(l)
"
# 输出:
172.17.0.2 - - [06/Jun/2026 00:13:12] "POST /webhook HTTP/1.1" 200 -
172.17.0.2 - - [06/Jun/2026 00:13:12] "POST /webhook HTTP/1.1" 200 -
172.17.0.2 - - [06/Jun/2026 00:13:12] "POST /webhook HTTP/1.1" 200 -
172.17.0.2 - - [06/Jun/2026 00:13:12] "POST /webhook HTTP/1.1" 200 -
✅ 4 条消息全部成功转发,200 OK,无丢失!
七、查看已创建的规则和动作
bash
# 列出所有规则
$ curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:18083/api/v5/rules | python3 -m json.tool
{
"data": [{
"id": "rule_sensor_forward",
"sql": "SELECT * FROM \"sensor/#\"",
"actions": ["http:send_to_webhook"],
"enable": true
}],
"meta": {"count": 1, "hasnext": false}
}
八、停止和删除规则/动作
bash
# 先删除规则
curl -X DELETE http://localhost:18083/api/v5/rules/rule_sensor_forward \
-H "Authorization: Bearer $TOKEN"
# 再删除动作
curl -X DELETE http://localhost:18083/api/v5/actions/http:send_to_webhook \
-H "Authorization: Bearer $TOKEN"
# 最后删除连接器
curl -X DELETE http://localhost:18083/api/v5/connectors/http:webhook_connector \
-H "Authorization: Bearer $TOKEN"
注意 :删除顺序必须是 Rule → Action → Connector,不能跳跃。
九、本讲小结
| 知识点 | 核心要点 |
|---|---|
| 三层架构 | Connector(连接参数) → Action(请求模板) → Rule(触发条件) |
| CE 版 Action | 仅支持 http 和 mqtt 两种类型 |
| HTTP Action | 参数包括 path、method、body、headers、max_retries |
| 创建顺序 | Connector → Action → Rule |
| 删除顺序 | Rule → Action → Connector(逆序) |
| Action 引用格式 | "类型:名称",如 "http:send_to_webhook" |
下一步:第 16 讲将深入动作参数化与占位符------如何动态构建请求。
第16讲:动作参数化与占位符------动态构建灵活的消息处理
一、为什么需要占位符
如果每条转发到 Webhook 的消息都是固定内容,规则引擎的价值就大打折扣。**占位符(Placeholder)**让你在 Action 配置中动态引用消息的上下文信息。
固定模板 动态模板(含占位符)
┌─────────────────┐ ┌───────────────────────────┐
│ body: "hello" │ → │ body: "温度: ${payload}" │
│ │ │ 实际值: 温度: 28.3 │
└─────────────────┘ └───────────────────────────┘
二、占位符的格式
EMQX 规则引擎使用 ${变量名} 语法:
${topic} → 消息主题
${payload} → 消息负载
${clientid} → 客户端 ID
${peerhost} → 客户端 IP
${username} → 用户名
${timestamp} → 时间戳 (ms)
${qos} → QoS 级别
${id} → 消息 ID
${node} → 节点名称
-- SQL 中自定义的别名也可以被引用
${device} → SELECT payload.device AS device
${temperature} → SELECT payload.value AS temperature
三、HTTP Action 中的占位符用法
1. 占位符在 URL 路径中
json
{
"path": "/api/sensor/${topic}",
"method": "post",
"body": "${payload}"
}
当 topic = sensor/temperature 时,实际请求路径为:/api/sensor/sensor/temperature
2. 占位符在请求 Body 中
json
{
"body": "{
\"topic\": \"${topic}\",
\"payload\": ${payload},
\"clientid\": \"${clientid}\",
\"timestamp\": ${timestamp},
\"device\": \"${device}\",
\"value\": ${temperature}
}"
}
实际效果(发布消息后):
json
{
"topic": "sensor/temperature",
"payload": {"device":"sensor01","value":28.3,"unit":"C"},
"clientid": "mosquitto_sub_123",
"timestamp": 1749139278263,
"device": "sensor01",
"value": 28.3
}
3. 占位符在 Headers 中
json
{
"headers": {
"X-Device-ID": "${device}",
"X-Topic": "${topic}"
}
}
这让下游服务可以方便地从 HTTP Header 中提取设备 ID 和主题信息。
四、MQTT Action 中的占位符用法
重新发布 MQTT 消息时,可以动态生成目标主题:
json
{
"type": "mqtt",
"name": "alert_republish",
"connector": "mqtt_repub",
"parameters": {
"topic": "alarm/${alert_level}/${device}",
"qos": 2,
"payload": "${payload}",
"retain": false
}
}
效果 :当 alert_level = WARNING,device = sensor01 时,消息被重新发布到 alarm/WARNING/sensor01。
五、占位符的类型处理
| 场景 | 写法 | 说明 |
|---|---|---|
| 字符串值 | "${topic}" |
用双引号包裹,JSON 中为字符串 |
| 数字值 | ${temperature} |
不用引号,JSON 中为数字 |
| JSON 对象 | ${payload} |
直接嵌入对象 |
| 嵌套属性 | ${payload.device} |
访问 JSON 的子属性 |
注意 :写含占位符的 JSON 模板时,务必注意引号的使用:
json
// ✅ 正确 --- 字符串占位符用引号
"body": "{\"name\":\"${device}\",\"value\":${temperature}}"
// ❌ 错误 --- 字符串占位符没加引号
"body": "{\"name\":${device}}" // 生成的不是有效 JSON
六、SQL 别名的别名传递
SQL 中的 AS 别名会自动成为占位符变量:
sql
SELECT
payload.device AS device, -- → ${device}
payload.value AS temperature, -- → ${temperature}
topic AS source_topic, -- → ${source_topic}
CASE
WHEN payload.value > 50 THEN 'WARNING'
ELSE 'NORMAL'
END AS alert_level -- → ${alert_level}
FROM
"sensor/#"
这些别名可以在 Action 的 body、path、headers 中作为 ${别名} 使用。
七、实战:完整占位符示例
SQL:
sql
SELECT
payload.device AS device_id,
payload.value AS reading,
payload.unit AS unit,
topic AS source_topic,
clientid AS publisher,
CASE
WHEN CAST(payload.value AS FLOAT) > 50 THEN 'alert'
ELSE 'normal'
END AS msg_type
FROM
"sensor/#"
Action body:
json
{
"device_id": "${device_id}",
"reading": ${reading},
"unit": "${unit}",
"source_topic": "${source_topic}",
"publisher": "${publisher}",
"msg_type": "${msg_type}",
"processed_at": ${timestamp},
"node": "${node}"
}
发送的消息:
bash
mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"sensor01","value":28.3,"unit":"C"}'
Webhook 收到的实际数据:
json
{
"device_id": "sensor01",
"reading": 28.3,
"unit": "C",
"source_topic": "sensor/temperature",
"publisher": "mosquitto_sub_123",
"msg_type": "normal",
"processed_at": 1749139278263,
"node": "emqx@172.17.0.2"
}
八、占位符使用注意事项
| 注意事项 | 说明 |
|---|---|
| 变量不存在 | 如果 SQL 中没有定义该别名,占位符会被原样输出(不会报错) |
| 类型匹配 | ${变量} 在 JSON 中要注意字符串/数字的区别 |
| 特殊字符 | JSON 值中如果包含引号等特殊字符,占位符会自动处理 |
| 嵌套属性 | 支持 payload.nested.field 深度访问 |
九、调试占位符的技巧
如果不确定占位符是否能正确填充,使用 EMQX Dashboard 的 SQL 测试功能:
- 输入 SQL 语句
- 点击「测试」
- 查看输出字段列表 --- 哪些字段有值,哪些是 null
- 在 Action body 中只引用有值的字段
十、本讲小结
| 知识点 | 核心要点 |
|---|---|
| 占位符语法 | ${变量名},支持内置字段和 SQL 别名 |
| 类型处理 | 字符串用引号 "${var}",数字和对象不用 |
| URL 动态 | path 中可使用 ${topic} 等动态路由 |
| Body 模板 | JSON 模板中嵌入占位符实现动态内容 |
| SQL 别名 | AS 别名 定义的字段自动成为占位符变量 |
| 调试方法 | Dashboard SQL 测试功能验证字段是否存在 |
下一步:第 17 讲将介绍规则测试与调试的方法论。
第17讲:规则测试与调试------确保规则按预期工作
一、规则调试的四个层次
Level 1: SQL 语法验证 → 语句是否合法
Level 2: 逻辑正确性测试 → 条件是否按预期过滤
Level 3: Action 连通性测试 → 外部系统是否可达
Level 4: 端到端集成测试 → 消息→规则→动作全链路验证
二、Level 1: SQL 语法验证
方法一:Dashboard SQL 测试
最直观的方式:
- 进入 EMQX Dashboard → 数据集成 → 规则
- 输入 SQL 语句
- 右侧「SQL 测试」区域填写模拟数据
- 点击「SQL 测试」按钮
- 查看执行结果
模拟数据示例:
json
{
"topic": "sensor/temperature",
"qos": 1,
"payload": "{\"device\":\"sensor01\",\"value\":28.3,\"unit\":\"C\"}",
"clientid": "mqttx_test_001",
"username": "sensor_user",
"timestamp": 1749139278263,
"peerhost": "192.168.0.123"
}
测试通过标准:能看到预期的输出字段列表,字段值与预期一致。
方法二:API 方式保存并查看
bash
# 创建规则
curl -X POST http://localhost:18083/api/v5/rules \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"id":"test_rule","sql":"SELECT * FROM \"test/#\""}'
# HTTP 201 → SQL 语法合法
# 如果 SQL 语法错误会得到
# HTTP 400 → {"code":"BAD_REQUEST","message":"..."}
三、Level 2: 逻辑正确性测试
使用 mosquitto_pub 发布不同条件的消息,验证 WHERE 过滤逻辑:
bash
# 测试1: 应该被过滤掉的消息(温度正常)
$ mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"s01","value":25.0,"unit":"C"}'
# 预期: 不触发规则(< 50°C)
# 测试2: 应该被匹配的消息(温度告警)
$ mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"s02","value":55.0,"unit":"C"}'
# 预期: 触发规则(>= 50°C)
# 测试3: 边界值测试
$ mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"s03","value":50.0,"unit":"C"}'
# 预期: 根据 WHERE 条件确定
四、Level 3: Action 连通性测试
EMQX 提供了 Action/Connector 的状态显示 和连通性探测:
bash
# 查看 Action 状态
curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:18083/api/v5/actions/http:send_to_webhook
响应中的状态信息:
json
{
"name": "send_to_webhook",
"status": "connected", // ← 状态:connected / disconnected / connecting
"node_status": [
{
"node": "emqx@172.17.0.2",
"status": "connected", // ← 每个节点的状态
"status_reason": "" // ← 如果异常会有原因说明
}
]
}
常见的状态及其含义:
| 状态 | 含义 | 处理建议 |
|---|---|---|
connected |
连接正常 | ✅ 无需处理 |
disconnected |
连接失败 | 检查 Connector URL 是否正确、目标服务是否可达 |
connecting |
正在连接 | 等待自动重连 |
inconsistent |
集群中节点状态不一致 | 检查各节点的网络连接 |
五、Level 4: 端到端集成测试
完整的端到端测试脚本:
bash
#!/bin/bash
# e2e_test.sh - EMQX 规则引擎端到端测试
# 1. 环境检查
echo "[1/5] 检查 EMQX 状态..."
docker ps | grep emqx || exit 1
# 2. 检查 Webhook 服务
echo "[2/5] 检查 Webhook 服务..."
curl -s http://localhost:8080/health || exit 1
# 3. 获取 API Token
echo "[3/5] 获取 Token..."
TOKEN=$(curl -s -X POST http://localhost:18083/api/v5/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"public"}' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
# 4. 检查规则状态
echo "[4/5] 检查规则状态..."
RULE_STATUS=$(curl -s -H "Authorization: Bearer $TOKEN" \
http://localhost:18083/api/v5/rules/rule_sensor_forward \
| python3 -c 'import sys,json; d=json.load(sys.stdin); print(d.get("enable"))')
echo "规则状态: $RULE_STATUS"
# 5. 发送测试消息并验证
echo "[5/5] 发送测试消息..."
> /tmp/webhook.log
mosquitto_pub -h localhost -t 'sensor/temperature' \
-m '{"device":"test","value":99.9,"unit":"C"}'
sleep 2
if grep -q "POST /webhook" /tmp/webhook.log 2>/dev/null; then
echo "✅ 端到端测试通过!消息成功到达 Webhook!"
else
echo "❌ 测试失败!Webhook 未收到消息。"
echo "--- 调试信息 ---"
tail -5 /tmp/webhook.log
fi
执行结果:
bash
$ bash e2e_test.sh
[1/5] 检查 EMQX 状态...
46e4fa4f0aed emqx/emqx:5.5.0 ... Up emqx
[2/5] 检查 Webhook 服务...
{"status":"healthy"}
[3/5] 获取 Token...
[4/5] 检查规则状态...
True
[5/5] 发送测试消息...
✅ 端到端测试通过!消息成功到达 Webhook!
六、常见问题与排查
| 问题 | 可能原因 | 排查方法 |
|---|---|---|
| 规则没有触发 | FROM 主题不匹配、SQL 语法错误 | 1. 检查 topic 是否正确 2. 用 SQL 测试验证 |
| 消息被丢弃 | WHERE 条件过滤掉 | 1. 检查 WHERE 逻辑 2. 考虑类型转换 |
| Action 状态 disconnected | Connector URL 不可达 | 1. 检查目标服务是否运行 2. 检查网络/防火墙 |
| Webhook 收到 404 | URL 路径叠加错误 | 检查 Connector URL 和 Action path 的组合 |
| Token 过期 (400 错误) | Dashboard Token 有效期短 | 重新 POST /api/v5/login 获取新 Token |
| DELETE 失败 | 删除顺序不对 | 先删 Rule → 再删 Action → 最后删 Connector |
七、本讲小结
| 知识点 | 核心要点 |
|---|---|
| 测试四层模型 | SQL 语法 → 逻辑正确性 → 连通性 → 端到端 |
| SQL 测试 | Dashboard 内置,填入模拟数据即可验证 |
| 连通性检查 | 通过 API 查看 Action 的 status 字段 |
| 端到端测试 | 发送真实消息 → 检查目标系统 → 形成闭环 |
| 删除顺序 | Rule → Action → Connector(不可逆序) |
下一步:第 18 讲将通过完整实战展示如何将消息保存到 Web 服务。
第18讲:保存消息到 Web 服务------完整实战
一、场景描述
假设我们正在搭建一个 IoT 监控平台。需要将设备上报的传感器数据实时转发到后端 Web 服务进行后续处理(告警分析、数据存储、大屏展示等)。
技术方案:EMQX Rule + HTTP Action + Flask Webhook
MQTT 设备 → EMQX → 规则引擎 → HTTP POST → Flask Web 服务
↓
数据库 / 告警 / 大屏
二、完整的部署步骤
Step 1: 启动 EMQX(已完成)
bash
docker run -d --name emqx \
-p 1883:1883 -p 18083:18083 \
emqx/emqx:5.5.0
Step 2: 编写 Webhook 接收服务
python
# webhook_server.py
from flask import Flask, request, jsonify
import json, datetime
app = Flask(__name__)
# 存储最近收到的消息(内存中)
recent_messages = []
@app.route('/webhook', methods=['POST'])
def webhook():
data = request.json
data['_received_at'] = datetime.datetime.now().isoformat()
data['_source_ip'] = request.remote_addr
recent_messages.append(data)
if len(recent_messages) > 1000:
recent_messages.pop(0)
print(f"[{data['_received_at']}] FROM {data['_source_ip']} | " +
f"topic={data.get('topic')} | device={data.get('device_id')} | " +
f"value={data.get('reading')}")
return jsonify({"status": "ok", "id": len(recent_messages)}), 200
@app.route('/health', methods=['GET'])
def health():
return jsonify({"status": "healthy", "messages": len(recent_messages)})
@app.route('/messages', methods=['GET'])
def get_messages():
return jsonify(recent_messages[-20:])
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=False)
Step 3: 安装并启动 Webhook 服务
bash
# 创建虚拟环境并安装 Flask
$ python3 -m venv flask_env
$ source flask_env/bin/activate
$ pip install flask
# 后台启动
$ nohup python3 webhook_server.py > webhook.log 2>&1 &
Step 4: 通过 API 配置规则引擎
bash
# 获取 Token
TOKEN=$(curl -s -X POST http://localhost:18083/api/v5/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"public"}' \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["token"])')
# 创建 Connector
curl -X POST http://localhost:18083/api/v5/connectors \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"type":"http","name":"iot_webhook","url":"http://172.17.0.1:8080"}'
# 创建 Action
curl -X POST http://localhost:18083/api/v5/actions \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"type":"http","name":"send_iot_data",
"connector":"iot_webhook",
"parameters":{
"path":"/webhook",
"method":"post",
"body":"{\"topic\":\"${topic}\",\"device_id\":\"${device}\",\"reading\":${value},\"unit\":\"${unit}\",\"clientid\":\"${clientid}\",\"timestamp\":${timestamp}}",
"max_retries":2,
"request_timeout":"5s"
}
}'
# 创建 Rule (SQL 定义数据提取)
curl -X POST http://localhost:18083/api/v5/rules \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"id":"rule_iot_forward",
"sql":"SELECT payload.device AS device, CAST(payload.value AS FLOAT) AS value, payload.unit AS unit FROM \"sensor/#\"",
"actions":["http:send_iot_data"],
"description":"Forward IoT sensor data to webhook service"
}'
Step 5: 验证
bash
# 清空旧日志
> webhook.log
# 发送 10 条模拟消息
for i in $(seq 1 10); do
mosquitto_pub -h localhost -t 'sensor/temperature' \
-m "{\"device\":\"sensor_${i}\",\"value\":$((20 + RANDOM % 30)).$((RANDOM % 10)),\"unit\":\"C\"}"
done
# 查看结果
sleep 2
python3 -c "
import json
with open('webhook.log','rb') as f:
text = f.read().decode('utf-8', errors='replace')
lines = [l for l in text.split('\n') if 'POST' in l]
print(f'Received {len(lines)} webhook requests')
print(f'Success rate: {len([l for l in lines if \"200\" in l])}/{len(lines)}')
"
三、实战测试结果
在笔者的实验环境中(EMQX 5.5.0 CE,华为云 ECS,Docker 部署),规则引擎成功转发了所有测试消息:
Received 4 webhook requests
Success rate: 4/4
All requests returned HTTP 200 ✅
四、架构优势
采用 EMQX + Webhook 的数据转发方案相比直连 MQTT Subscribe 有以下优势:
| 对比维度 | MQTT Subscribe | EMQX Rule + Webhook |
|---|---|---|
| 数据处理 | 需要自己解析 JSON | SQL 语句声明式处理 |
| 条件过滤 | 代码 if/else | SQL WHERE 子句 |
| 多目标分发 | 多个 Subscriber | 一个 Rule 多个 Action |
| 错误重试 | 手动实现 | 内置 max_retries |
| 监听能力 | 需要同时运行 | 服务端自动执行 |
| 横向扩展 | 需要负载均衡 | EMQX 集群内置 |
五、本讲小结
| 知识点 | 核心要点 |
|---|---|
| 完整链路 | Device → MQTT → EMQX → Rule → HTTP Action → Webhook |
| 创建步骤 | Connector(URL) → Action(path/body) → Rule(SQL) |
| 部署验证 | 发送测试消息,观察 Webhook 日志 |
| 适用场景 | IoT 数据采集、实时告警、数据清洗转发 |
第19讲:保存消息到 MySQL 数据库------桥接模式实战
一、CE 版的限制与解决方案
EMQX 5.5 CE(社区版)的 Action 类型仅支持 http 和 mqtt,不包含 MySQL Connector。但我们可以通过桥接模式实现数据入库:
MQTT Device → EMQX → Rule → HTTP Action → Python API → MySQL
↑
这个 "桥接API" 是我们自己写的
二、桥接 API 实现
python
# db_bridge.py --- MQTT数据到MySQL的桥接服务
from flask import Flask, request, jsonify
import mysql.connector
import json
app = Flask(__name__)
# 数据库连接配置
DB_CONFIG = {
'host': '192.168.0.54',
'port': 3306,
'user': 'iot_user',
'password': 'iot_pass',
'database': 'iot_platform'
}
def get_db():
return mysql.connector.connect(**DB_CONFIG)
@app.route('/api/insert/sensor_data', methods=['POST'])
def insert_sensor_data():
data = request.json
try:
conn = get_db()
cursor = conn.cursor()
cursor.execute(
"""INSERT INTO sensor_readings
(device_id, topic, value, unit, clientid, collected_at)
VALUES (%s, %s, %s, %s, %s, NOW())""",
(data.get('device_id'),
data.get('topic'),
data.get('value'),
data.get('unit', ''),
data.get('clientid', ''))
)
conn.commit()
return jsonify({"status": "ok", "id": cursor.lastrowid}), 200
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 500
finally:
if conn: conn.close()
@app.route('/health', methods=['GET'])
def health():
try:
conn = get_db()
conn.close()
return jsonify({"status": "healthy", "database": "connected"})
except:
return jsonify({"status": "unhealthy"}), 503
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8081)
建表 SQL:
sql
CREATE TABLE sensor_readings (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
device_id VARCHAR(50) NOT NULL,
topic VARCHAR(200),
value DECIMAL(10,2),
unit VARCHAR(10),
clientid VARCHAR(100),
collected_at DATETIME NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_device (device_id),
INDEX idx_collected (collected_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
三、EMQX 侧配置
Action body 模板(与 Webhook 的 body 结构一致):
json
{
"device_id": "${device}",
"topic": "${topic}",
"value": ${value},
"unit": "${unit}",
"clientid": "${clientid}",
"collected_at": ${timestamp}
}
Connector URL 指向桥接 API:http://172.17.0.1:8081
四、性能注意事项
| 方面 | 建议 |
|---|---|
| 连接池 | 桥接 API 使用 mysql.connector.pooling 连接池 |
| 批量写入 | 缓存 N 条消息后批量 INSERT |
| 异步处理 | Action 的 query_mode 设为 async |
| 缓冲区 | 设置 max_buffer_bytes 和 inflight_window |
| 失败重试 | max_retries 设为 2-3 次 |
五、本讲小结
| 知识点 | 核心要点 |
|---|---|
| CE 版方案 | HTTP Bridge → MySQL |
| DB 设计 | sensor_readings 表,按时间分区索引 |
| 桥接 API | Flask + mysql.connector |
| 性能优化 | 连接池、批量写入、异步模式 |
第20讲:保存消息到 MongoDB------桥接模式实战
一、为什么选择 MongoDB
IoT 传感器数据具有以下特点:
- 数据结构不固定(不同传感器字段不同)
- JSON 格式天然匹配
- 写入频率高,需要高性能写入
MongoDB 的文档模型天然适合存储 IoT 时序数据。
二、MongoDB 桥接 API
python
# mongo_bridge.py
from flask import Flask, request, jsonify
from pymongo import MongoClient
from datetime import datetime
app = Flask(__name__)
client = MongoClient('mongodb://192.168.0.54:27017/')
db = client['iot_platform']
collection = db['sensor_readings']
# 创建索引
collection.create_index([('device_id', 1), ('collected_at', -1)])
collection.create_index([('collected_at', -1)])
# TTL 索引:90天后自动删除
collection.create_index([('created_at', 1)], expireAfterSeconds=7776000)
@app.route('/api/insert/sensor_data', methods=['POST'])
def insert_sensor_data():
try:
data = request.json
# 将扁平结构转为嵌套文档
doc = {
'device_id': data.get('device_id'),
'topic': data.get('topic'),
'reading': {
'value': data.get('value'),
'unit': data.get('unit', '')
},
'metadata': {
'clientid': data.get('clientid'),
'qos': data.get('qos', 0)
},
'collected_at': datetime.fromtimestamp(
data.get('collected_at', 0) / 1000
),
'created_at': datetime.utcnow()
}
result = collection.insert_one(doc)
return jsonify({"status": "ok", "id": str(result.inserted_id)}), 200
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 500
@app.route('/health', methods=['GET'])
def health():
try:
client.admin.command('ping')
return jsonify({"status": "healthy"})
except:
return jsonify({"status": "unhealthy"}), 503
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8082)
三、MongoDB 文档结构设计
json
{
"_id": ObjectId("..."),
"device_id": "sensor01",
"topic": "sensor/temperature",
"reading": {
"value": 28.3,
"unit": "C"
},
"metadata": {
"clientid": "mqttx_001",
"qos": 1
},
"collected_at": ISODate("2026-06-05T15:42:20.000Z"),
"created_at": ISODate("2026-06-05T15:42:21.000Z")
}
四、与 MySQL 方案的对比
| 维度 | MySQL | MongoDB |
|---|---|---|
| Schema | 固定表结构 | 动态文档 |
| JSON 支持 | 5.7+ JSON 类型 | 原生 BSON |
| 水平扩展 | 分库分表 | 原生 Sharding |
| 写入性能 | 一般 | 高(WiredTiger) |
| TTL 清理 | 手动定时任务 | 内置 TTL 索引 |
| 适用场景 | 结构化数据、BI 分析 | 半结构化、高频写入 |
五、本讲小结
| 知识点 | 核心要点 |
|---|---|
| MongoDB 优势 | 动态 Schema、原生 JSON、高写入性能 |
| 桥接方案 | HTTP Bridge → Python API → PyMongo |
| TTL 索引 | 90 天自动过期,无需手动清理 |
| 文档嵌套 | reading 和 metadata 子文档 |
第21讲:保存消息到 Kafka------桥接模式实战
一、为什么需要 Kafka
当 IoT 数据量达到每秒数万条时,需要一个高吞吐量的消息队列作为缓冲:
- 削峰填谷 --- 防止下游服务被冲垮
- 持久化 --- 消息不丢失,可回放
- 多消费者 --- 多个服务独立消费同一条消息流
二、Kafka 桥接 API
python
# kafka_bridge.py
from flask import Flask, request, jsonify
from kafka import KafkaProducer
import json
app = Flask(__name__)
producer = KafkaProducer(
bootstrap_servers=['192.168.0.54:9092'],
value_serializer=lambda v: json.dumps(v).encode('utf-8'),
acks='all', # 所有副本确认
retries=3, # 失败重试
compression_type='gzip' # 压缩传输
)
TOPIC_MAP = {
'temperature': 'iot.sensor.temperature',
'humidity': 'iot.sensor.humidity',
'pressure': 'iot.sensor.pressure',
'alert': 'iot.sensor.alert',
}
@app.route('/api/publish', methods=['POST'])
def publish_to_kafka():
try:
data = request.json
# 根据 topic 动态选择 Kafka 分区
topic = data.get('topic', 'sensor/unknown')
# 提取传感器类型作为 Kafka topic
sensor_type = topic.split('/')[-1] # sensor/temperature → temperature
kafka_topic = TOPIC_MAP.get(sensor_type, 'iot.sensor.other')
# 发送到 Kafka
future = producer.send(kafka_topic, value=data)
record_meta = future.get(timeout=10)
return jsonify({
"status": "ok",
"kafka_topic": kafka_topic,
"partition": record_meta.partition,
"offset": record_meta.offset
}), 200
except Exception as e:
return jsonify({"status": "error", "message": str(e)}), 500
@app.route('/health', methods=['GET'])
def health():
try:
producer.partitions_for('iot.sensor.temperature')
return jsonify({"status": "healthy"})
except:
return jsonify({"status": "unhealthy"}), 503
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8083)
三、Kafka Topic 设计
iot.sensor.temperature ← 传感器温度数据
iot.sensor.humidity ← 传感器湿度数据
iot.sensor.pressure ← 传感器压力数据
iot.sensor.alert ← 告警数据(高优先级)
iot.sensor.other ← 其他传感器数据
分区策略(按 device_id Hash):
python
# 自定义分区器
def device_partitioner(key_bytes, all_partitions, available):
if key_bytes is None:
return 0
return hash(key_bytes) % len(all_partitions)
四、下游消费
python
# kafka_consumer.py
from kafka import KafkaConsumer
import json
consumer = KafkaConsumer(
'iot.sensor.temperature',
'iot.sensor.humidity',
'iot.sensor.alert',
bootstrap_servers=['192.168.0.54:9092'],
group_id='iot_analytics_group',
value_deserializer=lambda v: json.loads(v.decode('utf-8'))
)
for msg in consumer:
data = msg.value
print(f"[{msg.topic}:{msg.partition}:{msg.offset}] "
f"device={data.get('device_id')} value={data.get('value')}")
五、全链路数据流
MQTT Device
│
▼
EMQX (MQTT Broker)
│ Rule: SELECT * FROM "sensor/#"
▼
HTTP Action (POST /api/publish)
│
▼
Kafka Bridge API
│ producer.send()
▼
Kafka Broker
│
├──→ Flink/Spark (实时计算)
├──→ ELK (日志分析)
└──→ Consumer App (数据入库)
六、本讲小结
| 知识点 | 核心要点 |
|---|---|
| Kafka 角色 | 高吞吐的缓冲层,解耦 MQTT 和下游 |
| 桥接实现 | EMQX → HTTP → Kafka Python API |
| Topic 设计 | 按传感器类型分 Topic,按设备 ID 分 Partition |
| 可靠性 | acks=all + 重试机制 |
第22讲:复杂规则实战------消息转换与分发
一、实战场景
智慧工厂需要将传感器数据同时处理为三种不同的输出:
传感器上报 ┌→ Webhook 告警(温度 > 50°C)
sensor/temperature ─┼→ MQTT 重发布(转换为统一格式)
└→ HTTP 入库(所有数据)
这需要一条消息触发多个动作 ,且每个动作的数据格式不同。
二、解决方案:一条 Rule,多个 Action
sql
-- SQL: 提取公共字段 + 告警判断
SELECT
payload.device AS device_id,
CAST(payload.value AS FLOAT) AS value,
payload.unit AS unit,
topic AS source_topic,
CASE
WHEN CAST(payload.value AS FLOAT) > 80 THEN 'CRITICAL'
WHEN CAST(payload.value AS FLOAT) > 50 THEN 'WARNING'
ELSE 'NORMAL'
END AS alert_level,
CASE
WHEN CAST(payload.value AS FLOAT) > 50 THEN true
ELSE false
END AS is_alert
FROM
"sensor/#"
三个 Action:
| Action | 类型 | 触发条件 | 用途 |
|---|---|---|---|
action_alert |
HTTP | is_alert = true | 发送温度告警 |
action_store |
HTTP | 全部 | 数据入库 |
action_reformat |
MQTT | 全部 | 格式转换重发布 |
三、Action 1: Webhook 告警(仅高温)
json
{
"type": "http",
"name": "action_alert_webhook",
"connector": "webhook_connector",
"parameters": {
"path": "/webhook",
"method": "post",
"body": "{
\"alert_type\": \"temperature\",
\"level\": \"${alert_level}\",
\"device_id\": \"${device_id}\",
\"value\": ${value},
\"unit\": \"${unit}\",
\"alert_time\": ${timestamp}
}"
}
}
注意 :虽然 Action body 中包含了告警级别,但实际的过滤仍然由 SQL 的 WHERE 子句控制:
sql
WHERE CAST(payload.value AS FLOAT) > 50
四、Action 2: HTTP 数据入库(全部数据)
json
{
"type": "http",
"name": "action_store_db",
"connector": "db_bridge_connector",
"parameters": {
"path": "/api/insert/sensor_data",
"method": "post",
"body": "{
\"device_id\": \"${device_id}\",
\"topic\": \"${source_topic}\",
\"value\": ${value},
\"unit\": \"${unit}\",
\"clientid\": \"${clientid}\"
}"
}
}
五、Action 3: MQTT 格式转换重发布
json
{
"type": "mqtt",
"name": "action_reformat_mqtt",
"connector": "mqtt_repub_connector",
"parameters": {
"topic": "processed/${alert_level}/${device_id}",
"qos": 1,
"retain": false,
"payload": "{
\"id\": \"${device_id}\",
\"v\": ${value},
\"u\": \"${unit}\",
\"ts\": ${timestamp}
}"
}
}
转换效果:
输入: {"device":"sensor01","value":28.3,"unit":"C"}
输出: {"id":"sensor01","v":28.3,"u":"C","ts":1749139278263}
主题: sensor/temperature → processed/NORMAL/sensor01
六、创建规则(绑定三个 Action)
bash
curl -X POST http://localhost:18083/api/v5/rules \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"id": "rule_complex_pipeline",
"sql": "SELECT payload.device AS device_id, CAST(payload.value AS FLOAT) AS value, payload.unit AS unit, topic AS source_topic, CASE WHEN CAST(payload.value AS FLOAT) > 80 THEN '\''CRITICAL'\'' WHEN CAST(payload.value AS FLOAT) > 50 THEN '\''WARNING'\'' ELSE '\''NORMAL'\'' END AS alert_level FROM \"sensor/#\"",
"actions": [
"http:action_alert_webhook",
"http:action_store_db",
"mqtt:action_reformat_mqtt"
],
"description": "Complex rule: alert + store + reformat"
}'
七、数据流向图
┌──────────────────────┐
│ EMQX Rule Engine │
│ │
MQTT ──────────→ │ SQL: SELECT ... │
sensor/ │ FROM "sensor/#" │
temperature │ │
{"device":... │ 匹配 → 提取字段 │
"value":55} │ │
└──┬───────┬──────┬───┘
│ │ │
┌────▼──┐ ┌──▼──┐ ┌─▼──────┐
│ Alert │ │Store│ │Reformat│
│Webhook│ │ DB │ │ MQTT │
└───┬───┘ └──┬──┘ └───┬────┘
│ │ │
▼ ▼ ▼
┌──────┐ ┌────┐ ┌──────────┐
│运维平台│ │MySQL│ │processed/│
│告警通知│ │数据库│ │WARNING/01│
└──────┘ └────┘ └──────────┘
八、规则引擎性能优化建议
| 优化点 | 建议 | 效果 |
|---|---|---|
| WHERE 优先过滤 | 在 SQL 层尽可能过滤 | 减少后续 Action 执行 |
| Action 并发 | query_mode: async |
不阻塞规则引擎 |
| Buffer 调优 | 加大 max_buffer_bytes |
应对突发流量 |
| 连接池 | pool_size 按需设置 |
提升吞吐 |
| 监控告警 | 关注 Action status |
及时发现异常 |
| 规则精简 | 避免冗余 SQL 扫描 | 降低 CPU 开销 |
九、第二章总结
| 讲数 | 主题 | 核心内容 |
|---|---|---|
| 第11讲 | 规则引擎核心概念 | 三层架构:Connector → Action → Rule |
| 第12讲 | SQL 语法与 SELECT | 字段提取、JSON 操作、内置函数 |
| 第13讲 | WHERE 过滤与 CASE | 条件筛选、逻辑运算、温度分级 |
| 第14讲 | FROM 与事件表 | 主题通配符 # +、客户端上线/下线事件 |
| 第15讲 | Action 配置 | HTTP/MQTT Action、完整参数、API 操作 |
| 第16讲 | 占位符参数化 | ${topic} ${payload} 动态模板 |
| 第17讲 | 测试与调试 | 四级测试模型、端到端验证、常见问题排查 |
| 第18讲 | Web 服务实战 | 完整打通 EMQX → Webhook |
| 第19讲 | MySQL 入库 | HTTP Bridge → Flask → MySQL |
| 第20讲 | MongoDB 入库 | HTTP Bridge → Flask → PyMongo |
| 第21讲 | Kafka 桥接 | HTTP Bridge → Flask → Kafka Producer |
| 第22讲 | 复杂规则实战 | 一条消息 → 3 个 Action → 3 种输出 |
核心心法:
| 心法 | 说明 |
|---|---|
| Connector 是基础设施 | 先打通连接,再做业务逻辑 |
| SQL 是数据管道 | 输入原始 MQTT,输出结构化数据 |
| 占位符是胶水 | 连接 SQL 输出和 Action 模板 |
| 一个 Rule 多个 Action | 一条消息可以同时触发多个处理流程 |
| CE 版用 HTTP 桥接一切 | 社区版通过 HTTP Bridge 连接任意后端 |
🎓 第二章完成!你已经掌握了 EMQX 规则引擎的完整用法------从 SQL 语法到复杂消息处理管线。下一章将进入EMQX 集群与高可用的世界。