EMQX 实战进阶博客:从入门到物联网架构

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 --- 确认是 EMQX
  • version: 5.5.0 --- 当前版本号(2026年6月最新版)
  • uptime: 2 minutes --- 启动后的运行时长

🏭 典型应用场景

场景 连接规模 消息特点 EMQX 优势
智能家居 万级 低频控制指令 低延迟、多协议支持
车联网 百万级 高频 GPS + 状态上报 海量连接、规则引擎实时处理
工业物联网 十万级 传感器定时上报 集群高可用、数据持久化
移动推送 亿级 APP 消息推送 超高并发的连接管理
边缘计算 千级/节点 云边协同 NanoMQ 边缘轻量版

🛡️ 避坑指南

  1. "EMQX 只支持 MQTT?"------错!

    EMQX 内置多协议网关,同时支持 CoAP、LwM2M、HTTP/WebHook。通过 Gateway 插件可以接入更多协议。

  2. 开源版 vs 企业版

    功能 开源版 企业版
    核心 MQTT Broker
    集群
    规则引擎
    数据桥接到 40+ 数据库
    LDAP / OAuth2 认证
    审计日志与合规
  3. EMQX ≠ EMQ X

    EMQX 是 EMQ 公司的旗舰产品。旧版本(4.x)叫 EMQ X,5.0 起改名 EMQX。见到 emqxEMQ 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 代发了遗嘱消息!

🛡️ 避坑指南

  1. Topic 不要以 / 开头

    bash 复制代码
    ❌ /sensor/temperature   # 开头多了一个空层级
    ✅ sensor/temperature
  2. # 只能放在最后

    bash 复制代码
    ✅ sensor/#            # 匹配所有子主题
    ❌ sensor/#/alert      # # 后面不能再有层级
    ❌ #                    # 匹配所有主题(慎用!负载杀手)
  3. QoS 降级原则

    订阅者的 QoS ≤ 发布者的 QoS。如果发布者用 QoS 2,订阅者用 QoS 0,实际投递是 QoS 0!

  4. 保留消息不是"数据库"

    一个 Topic 只有一条保留消息(最新的那条)。不适合做历史数据存储。

  5. 遗嘱消息的触发条件

    只有非正常断开 时才触发。正常调用 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.rundocker.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 已就绪。

🛡️ 避坑指南

  1. Docker 镜像拉取失败 / 超时

    原因 解决方案
    华为云 ECS 访问 docker.io 被阻断 使用国内镜像加速器(如上配置)
    DNS 解析失败 尝试不同镜像源:docker.1ms.run / docker.xuanyuan.me
    镜像源全部不可用 手动导出→传输→导入镜像
  2. 端口被占用

    bash 复制代码
    # 检查端口占用
    $ ss -tlnp | grep -E '1883|8083|18083'
    
    # 如果被占用,更换宿主机端口映射
    $ docker run -d --name emqx -p 1884:1883 -p 18084:18083 emqx/emqx:5.5.0
  3. Dashboard 密码忘记

    bash 复制代码
    # 重置 admin 密码
    $ docker exec emqx emqx ctl admins passwd admin new_password
  4. --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) 管理扩展插件

🛡️ 避坑指南

  1. Dashboard 只能看当前节点的统计

    集群模式下,Dashboard 默认显示所在节点的数据。查看全集群需要切换到「集群概览」视图。

  2. Dashboard 监控数据有延迟

    指标刷新间隔约 5-10 秒,不要用 Dashboard 做精确的实时监控(用 Prometheus + Grafana)。

  3. 不要用默认密码跑生产

    bash 复制代码
    # 立即修改密码!
    $ docker exec emqx emqx ctl admins passwd admin YourStrongP@ssw0rd!
  4. 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.confcluster.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.confcluster.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

🛡️ 避坑指南

  1. 改了配置不生效?三个检查

    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
  2. node.cookie 必须一致

    集群中所有节点的 node.cookie 必须完全相同,否则无法加入集群。日志中看到 incompatible cookie 就是这个原因。

  3. 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 ⭐⭐ 中 高性能缓存 + 临时凭证

🛡️ 避坑指南

  1. 创建认证器后匿名仍可连接

    原因 :Dashboard 创建认证器后需重新连接客户端,已建立的连接不受影响。

    解决:Dashboard → 客户端 → 踢出所有匿名客户端。

  2. 密码管理:不要明文存密码

    bash 复制代码
    # EMQX 5.x 内置数据库密码经过 PBKDF2 加密
    # 但 MySQL 外部认证表的密码字段建议也用加密存储
  3. 认证失败排查

    bash 复制代码
    # 查看 EMQX 日志中认证失败的记录
    $ docker logs emqx | grep -i 'auth\|login\|authenticate' | tail -20
  4. 不要只配一个认证器

    建议至少配两个:数据库认证器 + 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。

🛡️ 避坑指南

  1. Topic 设计原则

    原则 好例子 坏例子
    层级合理 factory/line1/sensor/temp factoryline1sensortemp
    有命名规范 {source}/{line}/{type}/{field} 随意起名
    不包含敏感信息 device/data device/192.168.1.100/admin/root123
    从一般到具体 china/gd/sz/factory1/line1 line1/factory1/sz/gd/china
  2. # 订阅的陷阱

    bash 复制代码
    # ❌ 订阅所有主题------CPU 和带宽杀手!
    $ mosquitto_sub -t '#' -h broker
    
    # ✅ 只订阅需要的内容
    $ mosquitto_sub -t 'factory/#' -h broker
  3. 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 管理界面 绝对不要暴露到公网!

🛡️ 避坑指南

  1. 生产环境必须用 TLS

    不要用 1883 明文端口!MQTT 消息(包括密码)在网络上明文传输,任何人都能截获。

  2. Dashboard 端口安全

    bash 复制代码
    # ✅ 正确:只绑定内网 IP
    dashboard.listeners.http.bind = "192.168.0.1:18083"
    
    # ❌ 错误:绑定所有网卡
    dashboard.listeners.http.bind = "0.0.0.0:18083"
  3. max_connections 要合理设置

    bash 复制代码
    # 预设资源下限。值太大可能导致 OOM
    listeners.tcp.default.max_connections = 100000
  4. 监听器重启会断开现有连接

    使用 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 配置实现

🛡️ 避坑指南

  1. ACL 规则顺序很重要!

    erlang 复制代码
    %% ❌ 错误顺序------默认拒绝放在开头,后面规则都不生效
    {deny, all}.
    {allow, {username, "admin"}, pubsub, ["#"]}.
    
    %% ✅ 正确顺序------具体规则在前,默认规则在后
    {allow, {username, "admin"}, pubsub, ["#"]}.
    {deny, all}.
  2. 永远拒绝 $SYS/# 主题

    $SYS/# 暴露了 EMQX 的内部运行状态,绝不能让普通设备读取。

  3. 别忘了测试

    每次修改 ACL 后,立即用 Dashboard 的「授权测试」功能验证规则是否正确。不要等到上线被攻击才发现。

  4. 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

🛡️ 避坑指南

  1. 生产环境不要长期开 debug 日志

    debug 级别日志量极大,严重影响性能且快速填满磁盘。排查完问题后立即改回 warning

  2. 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
  3. 保留消息不是无限的

    EMQX 默认保留消息数量上限很大(取决于内存),但建议设置上限:

    bash 复制代码
    # enqx.conf
    retainer.max_payload_size = 1MB        # 单条最大 1MB
    retainer.max_retained_messages = 10000  # 最多保留 1 万条
  4. 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 类型只有 httpmqtt。如果要连接 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 消息的本质是一段字节流。要从杂乱的消息中提取有价值的数据,我们需要:

  1. 字段选择 --- 只提取需要的数据(SELECT)
  2. 条件过滤 --- 只处理感兴趣的消息(WHERE)
  3. 类型转换 --- 把字符串转成数字(CAST)
  4. 内置函数 --- 时间格式化、字符串处理等

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 语句是否正确:

操作步骤

  1. 打开 Dashboard → 数据集成 → 规则
  2. 点击「创建规则」
  3. 输入 SQL 语句
  4. 在右侧的「SQL 测试」区域输入测试数据:
json 复制代码
{
  "topic": "sensor/temperature",
  "qos": 1,
  "payload": "{\"device\":\"sensor01\",\"value\":28.3,\"unit\":\"C\"}",
  "clientid": "mqttx_test",
  "timestamp": 1749139278263
}
  1. 点击「测试」,查看 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"
  }'

踩坑提醒 🔥:

  1. Action 引用格式必须是 "类型:名称"(如 "http:send_to_webhook"),不能只写名称
  2. Connector URL 和 Action path 会拼接为最终请求 URL
  3. 如果 Connector URL 是 http://host:8080/webhook 且 Action path 是 /api,最终 URL 为 http://host:8080/webhook/api
  4. 建议 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 仅支持 httpmqtt 两种类型
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 = WARNINGdevice = 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 测试功能:

  1. 输入 SQL 语句
  2. 点击「测试」
  3. 查看输出字段列表 --- 哪些字段有值,哪些是 null
  4. 在 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 测试

最直观的方式:

  1. 进入 EMQX Dashboard → 数据集成 → 规则
  2. 输入 SQL 语句
  3. 右侧「SQL 测试」区域填写模拟数据
  4. 点击「SQL 测试」按钮
  5. 查看执行结果

模拟数据示例

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 类型仅支持 httpmqtt,不包含 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_bytesinflight_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 集群与高可用的世界。

相关推荐
砍材农夫1 小时前
物联网实战:Spring Boot MQTT | 客户端框架比对
spring boot·后端·物联网
未若君雅裁1 小时前
JVM 是什么:组成、运行流程与整体架构
jvm·架构
hoiii1871 小时前
基于 STM32 的标准遥控器架构与源码
stm32·嵌入式硬件·架构
MetrixAeroCore1 小时前
马来西亚物联网卡适用场景与本地网络适配全解析|MetrixAeroCore
物联网
Gopher_HBo1 小时前
存储层LSM Tree
后端·架构
步步为营DotNet1 小时前
.NET Aspire 在云原生微服务架构中的深度实践与剖析
云原生·架构·.net
江华森1 小时前
《网络架构实战:从单机到云原生的全栈思考》博客系列
网络·云原生·架构
真实的菜1 小时前
Redis 从入门到精通(六):集群模式(Cluster)—— 分布式架构、哈希槽与 Gossip 协议全解
redis·分布式·架构
喵了几个咪1 小时前
技术复盘:基于 GoWind Admin 实现 Kratos 框架单体轻量化落地
前端·架构