资讯详情

EMQX + InfluxDB:物联网设备数据采集与时序存储全链路实战

📅 2026/10/16 11:11:53 | 华诺云谱 👁 阅读
EMQX + InfluxDB:物联网设备数据采集与时序存储全链路实战
1. 项目整体设计从传感器到时序库的一条数据高速公路手里的环境监测设备越来越多分布在楼内各个角落每台设备持续上报温度、湿度、电压和电流。早期我用普通消息队列加传统关系型数据库跑这套东西在数据量小的时候还算凑合等设备上了三十多台、每秒几十条消息进来以后问题就来了查询某段时间的温度曲线越来越慢磁盘占用也失控历史数据清理得靠人工写脚本。后来把链路整体换成了 MQTT 协议 EMQX 消息服务 InfluxDB 时序数据库整个数据采集存储体系清爽了很多。如果你也在折腾物联网设备数据采集、环境监控、工业设备状态上报这类项目这套组合值得参考。先说清楚这套方案到底解决什么问题。EMQX 负责把设备的消息接进来它天生就是干这个的——支持海量设备同时连接、支持 MQTT 协议的完整特性设备端只需要一条简单的发布订阅链路就能把数据送上来InfluxDB 负责把这些带时间戳的测量值存下来它针对按时间顺序写入、按时间范围查询的场景做了深度优化写入吞吐和压缩率都比普通关系型数据库高一个量级。两者中间通过规则引擎或自定义消费者连接就构成了一条设备上报 - 消息中间件 - 时序存储 - 查询分析的完整管道。这个方案适合谁适合手里有几十到几千台设备的开发者、做 IoT 平台架构选型的技术负责人、以及刚开始接触时序数据的后端工程师。你不需要一开始就上大数据全家桶一个 EMQX 加一个 InfluxDB单机能扛住非常可观的吞吐。这套链路我在实际项目里跑了一年多整体稳定下面把设计思路、选型理由、配置过程和踩过的坑一次讲清楚。1.1 为什么是 EMQX而不是自己写 MQTT Broker 或直接用消息队列很多人会问既然设备数据要经过消息中间件我直接用现成的消息队列不就行了还真不行。MQTT 协议和通用消息队列的定位差别很大。设备端往往处于弱网环境、IP 会变、连接不稳定MQTT 设计之初就考虑了这些它基于发布/订阅模型有 QoS 等级控制消息可靠性有遗嘱消息Last Will让服务端感知设备异常下线有会话延续机制让设备重连后不丢消息。这些特性在通用消息队列里要么缺失要么需要你用大量业务代码去补。EMQX 是 MQTT Broker 里我用下来最顺手的它的核心优势是连接层做得非常扎实基于 Erlang/OTP 的高并发模型单节点撑几万连接毫无压力集群后更是能到百万级。另一个很关键的点是它的规则引擎内置了数据桥接能力可以直接把 MQTT 消息经过简单 SQL 转换后写入 InfluxDB省掉一条自研消费链路。这点后面实操部分详细讲。1.2 整体架构与数据流向设计我的这套链路核心流向如下设备侧传感器通过 MQTT 客户端按固定主题上报数据主题设计为 emqx/env/node-1/data 这种带层级的形式方便后续规则匹配。EMQX 收到消息后经过规则引擎匹配主题并提取字段把数据桥接到 InfluxDB 数据库。存储层设计为原始数据表加聚合统计表原始数据保留短周期长期统计靠连续查询自动降采样。展示层后续接 Grafana 可以完全可视化。两条写入路线值得对比一是 EMQX 规则引擎直写 InfluxDB适合标准场景设备消息字段格式固定、无需复杂清洗时最省事吞吐高延迟低运维只需要管好 EMQX 配置。二是写一个独立的消费者程序订阅 MQTT 主题再批量写入 InfluxDB当需要做复杂解析、多数据源关联、调用外部 API 补全设备信息时比较灵活。我实际项目里两条都用了常规环境数据走规则引擎直写特殊设备协议的解析走消费者程序。下面逐个环节拆开讲。2. EMQX 部署与关键配置解析2.1 部署方式选择Docker 一键起集群后续再扩EMQX 的部署没什么玄学。单机验证用 Docker 最省事一条命令就能把服务拉起来docker run -d --name emqx -p 1883:1883 -p 8083:8083 -p 18083:18083 -e EMQX_DASHBOARD__DEFAULT_PASSWORDyour-password emqx/emqx:5.8.0这里端口需要解释一下1883 是 MQTT 默认 TCP 端口设备客户端连接就靠它8083 是 MQTT over WebSocket 端口给浏览器客户端或某些小程序用的18083 是 Dashboard 管理界面端口规则引擎、认证配置都在这个界面上完成。如果你本地已经占了端口自行调整映射对应关系即可。生产环境建议直接上 Docker Compose 编排 EMQX 加 InfluxDB后面整套链路一条命令拉起。我做项目时习惯先用 Docker 快速验证确认配置无误后再用 Compose 固定整套环境供团队使用。在前面的标题里我列了 docker-compose.yml 的片段这里完整展开services: emqx: image: emqx/emqx:5.8.0 container_name: emqx restart: always ports: - 1883:1883 - 8083:8083 - 18083:18083 environment: - EMQX_DASHBOARD__DEFAULT_PASSWORDyour-password volumes: - emqx-data:/opt/emqx/data - emqx-etc:/opt/emqx/etcinfluxdb: image: influxdb:1.8 container_name: influxdb restart: always ports: - 8086:8086 environment: - INFLUXDB_DBiot_data - INFLUXDB_HTTP_AUTH_ENABLEDtrue - INFLUXDB_ADMIN_USERadmin - INFLUXDB_ADMIN_PASSWORDyour-influx-password volumes: - influxdb-data:/var/lib/influxdb启动后用 docker compose up -d 即可。这里我把 InfluxDB 的 HTTP 认证也顺手开了后面会细讲为什么不要把库裸奔在局域网里。2.2 认证授权不要把设备接入裸奔在网络上很多刚接触 MQTT 的开发者喜欢把 EMQX 默认配置直接拿来用设备端连接 Broker 时不带任何用户名密码。这在内网测试没问题一旦放到稍微正式一点的环境我就强烈建议开启认证。EMQX 5.x 的 Dashboard 里访问控制 - 认证 - 创建认证器选择用户名密码类型然后录入设备账号密码。设备端连接时带上账号信息即可。这里有个细节每个设备尽量用独立的 client_id 和独立的账号方便后续做设备维度的消息追踪和审计。我在项目里给十几台传感器各建了一个账号虽然管理起来多几步操作但出了问题能直接定位到具体设备排查效率高很多。另外要注意 EMQX 的 Dashboard 默认密码新版本安装后会要求你修改。这个密码一定不要用默认值我见过不少把 Dashboard 暴露在公网上结果被改配置的案例。生产环境还建议把 Dashboard 端口通过防火墙限制到内网访问或至少用反向代理加一层认证。2.3 规则引擎与数据桥接EMQX 最核心的竞争力EMQX 的规则引擎允许你用类 SQL 的语法处理消息流匹配主题、提取字段、执行动作。它工作在消息发布的路径上设备消息到达 Broker 后先经过规则引擎过滤和转换再把结果交给配置好的动作处理。这比传统的外部消费者模式少一跳网络开销。在 Dashboard 中创建规则时核心是写一条 FROM 语句。比如我想匹配所有环境数据主题并提取 payload 字段规则 SQL 大致为SELECT payload.temperature AS temperature, payload.humidity AS humidity, payload.voltage AS voltage, payload.current AS current, payload.device_id AS device_id FROM env//data这里的 是 MQTT 主题的通配符代表任意一层。规则写到这还不算完还要指定动作。EMQX 5.x 的动作类型里有 InfluxDB 数据桥接配置时填 InfluxDB 地址、端口、数据库名、账号密码然后在数据写入模板里声明 measurement 名称、哪些字段作为 tags、哪些作为 fields、时间戳取哪个字段。配置完成后规则引擎会自动把消息转换成 InfluxDB 的 Line Protocol 写入。规则引擎的调试功能一定要用起来。EMQX Dashboard 里规则列表页旁边有调试入口可以模拟一条消息跑一遍规则看提取出来的字段对不对、SQL 有没有解析报错。我在最初配置时就是靠这个功能把字段名映射错误一个个暴露出来的。写完规则后先调试再上线节省大量时间。3. InfluxDB 时序数据建模与存储设计3.1 measurement、tag、field 怎么选影响查询性能和磁盘占用InfluxDB 的数据模型只有四个核心概念measurement、tag、field、time。measurement 类似关系库的表名time 是时间戳tag 是带索引的标签field 是具体的测量值。查询时 tag 可以用来快速过滤field 是真正存储数值的地方。选择 tag 和 field 有一条核心准则需要经常按这个维度分组或过滤的离散值放 tag连续变化的数值放在 field。比如设备 ID、设备类型、所在房间号这些适合放 tag温度、湿度、电压、电流这些必须放 field。另外 tag 的值不要设计得太离散比如把一条消息的 request_id 这种唯一标识塞进 tag索引会急剧膨胀存储和查询性能都会受到明显影响。我最终的 measurement 设计是 env_datatags 里放 device_id 和 node_typefields 里放 temperature、humidity、voltage、current 四个数值。这样按设备查询某时间段的曲线、按节点类型统计数据都非常快。3.2 保留策略和连续查询自动清理与自动降采样保存原始数据当然有用但不可能永久保留。关系型数据库没有内建的过期清理机制InfluxDB 的保留策略Retention Policy简称 RP天然解决这个问题。我的做法是双 RP 策略原始数据 env_data 的原始保留策略设置为 30 天超出自动删除同时创建一个长期保留策略 rp_1y用于存放降采样后的聚合数据保留 365 天。降采样由连续查询Continuous Query简称 CQ自动完成InfluxDB 每隔一段时间自动跑一次聚合统计把上一小时内的数据按设备维度聚合成平均值写入长期表。在 InfluxDB 1.x 里创建库和策略的命令如下CREATE DATABASE iot_data CREATE RETENTION POLICY rp_30d ON iot_data DURATION 30d REPLICATION 1 DEFAULT CREATE RETENTION POLICY rp_1y ON iot_data DURATION 365d REPLICATION 1 CREATE CONTINUOUS QUERY cq_1h_env ON iot_data BEGIN SELECT mean(temperature) AS avg_temp, mean(humidity) AS avg_humidity INTO rp_1y.env_1h FROM env_data GROUP BY time(1h), device_id, node_type END这个设计的思路是做实时查看和报警用原始 30 天数据做趋势分析和月度报告用长期聚合数据。双策略既控制了磁盘成本又保留了足够长的历史窗口。注意 CQ 的 GROUP BY time 窗口大小要和 INTO 的表保持一致否则数据写到了错误的位置。3.3 存储容量估算先算清楚能撑多久时序数据库的容量规划比关系型数据库更直观因为写入模型是 append-only数据量增长基本可预测。以我的项目为例每台设备 5 秒上报一条消息JSON 序列化后约 180 字节转成 InfluxDB Line Protocol 加上时间戳和文本开销按 300 字节估算比较稳妥。单台设备一天的数据量大约是 86400 / 5 * 300 5184000 字节约 5MB。一个月的原始数据约 150MB这还是单台设备。三十台设备就是 4.5GB 每月。加上 InfluxDB 的索引开销和内部碎片冗余实际磁盘占用再乘 1.5 到 2 倍比较保险。配合 30 天保留策略整体磁盘占用可以稳定在一个可控范围。实际部署时InfluxDB 的存储目录默认在 /var/lib/influxdbDocker 部署时需要挂载宿主机目录否则容器删除数据全没。这块建议一开始就规划好磁盘容量并且配置日志轮转和监控告警。4. 实操全链路从设备采数到 InfluxDB 落库4.1 模拟设备端用 Python 发布 MQTT 消息没有真实传感器的话可以用脚本模拟设备端消息。我建议用 paho-mqtt 库它是最主流的 Python MQTT 客户端安装一条命令搞定。模拟脚本的逻辑很简单每隔 5 秒生成一组随机温湿度数据发布到 EMQX 的 env/node-1/data 主题。import json import time import random from paho.mqtt import client as mqtt_client broker 192.168.1.100 # 改成你的 EMQX 地址 port 1883 username device-1 password device-pass-1 topic env/node-1/data client mqtt_client.Client(client_iddevice-1) client.username_pw_set(username, password) client.connect(broker, port, 60) client.loop_start() for _ in range(720): payload { device_id: node-1, node_type: env-sensor, temperature: round(random.uniform(20.0, 30.0), 2), humidity: round(random.uniform(40.0, 70.0), 2), voltage: round(random.uniform(4.8, 5.2), 3), current: round(random.uniform(0.1, 0.5), 3), ts: int(time.time() * 1000) } result client.publish(topic, json.dumps(payload), qos1) status result.rc if status ! mqtt_client.MQTT_ERR_SUCCESS: print(fpublish failed: {status}) time.sleep(5) client.loop_stop() client.disconnect()几个关键点说一下。QoS 我选的是 1表示消息至少送达一次这对环境监测类数据足够可靠又不像 QoS 2 那样有四次握手开销。payload 里的 ts 字段我用毫秒时间戳这个在写入 InfluxDB 时要对应声明精度不然时间戳会被误解为纳秒导致时间显示成 1970 年。客户端连接后的 loop_start 必须在 publish 之前调用它负责维持网络心跳和消息收发的后台循环遗漏这一步会看到连上了但消息发不出去。另外 paho-mqtt 的 publish 是异步接口返回的是发布结果对象而非发送结果真正确认发布完成靠回调或等待。如果追求严格确认可以注册 on_publish 回调看 mid 是否匹配但环境监测场景下 QoS 1 配合定期检查磁盘数据就可以了。4.2 方案 AEMQX 规则引擎直写 InfluxDB设备消息到了 EMQX接下来就是把数据灌进 InfluxDB。方案 A 的核心配置在 EMQX Dashboard 的规则页面我的步骤记录如下第一步创建规则。在规则引擎页面新建规则规则 SQL 填写上文提到的 SELECT 语句FROM 指定 env//data。这个模糊匹配可以覆盖所有设备主题后续新增设备不需要改动规则。第二步配置动作。在规则 SQL 下方编辑动作选择数据桥接 - InfluxDB 写入。新建 InfluxDB 桥接资源填写地址、端口、数据库、账号密码。然后配置写入模板measurement 填 env_datatag 映射选择 device_id 和 node_typefield 映射选择 temperature、humidity、voltage、current时间戳字段选择 payload 里的 ts精度声明为毫秒。第三步启用规则并验证。保存规则后回到规则列表点击测试按钮手动输入一条模拟消息跑一遍检查输出和动作执行是否成功。确认没问题后在 InfluxDB 端查询确认数据落库。这个方案的优点是端到端延迟极低消息进入 EMQX 后直接转换写入中间不经过任何自建服务。缺点是字段映射在 EMQX 界面里配置后要调整格式就得去界面改规则。对于字段格式稳定、设备类型固定的场景我是强烈推荐这个方式。4.3 方案 B自定义消费者订阅 MQTT 再写入 InfluxDB当消息需要更灵活的处理时比如要从消息内容里去查设备档案、要拼接多个数据源的信息、要按业务规则拆分路由我建议用方案 B。写一个常驻 Python 服务订阅通配主题收到消息后解析逻辑完全由自己掌控。示例消费者如下import json from paho.mqtt import client as mqtt_client from influxdb import InfluxDBClient broker 192.168.1.100 port 1883 topic env/# influx InfluxDBClient(host127.0.0.1, port8086, usernameadmin, passwordyour-influx-password, databaseiot_data) def on_message(client, userdata, msg): try: data json.loads(msg.payload) json_body [{ measurement: env_data, tags: { device_id: data.get(device_id), node_type: data.get(node_type) }, time: data.get(ts), fields: { temperature: data[temperature], humidity: data[humidity], voltage: data[voltage], current: data[current] } }] influx.write_points(json_body, batch_size200) except Exception as e: print(fparse or write error: {e}, raw: {msg.payload}) client mqtt_client.Client() client.username_pw_set(consumer, consumer-pass) client.on_message on_message client.connect(broker, port, 60) client.subscribe(topic, qos1) client.loop_forever()注意 influx.write_points 的 batch_size 参数实际控制的是 InfluxDB 客户端内部攒批写入的条数每次达到 200 条才真正发一次 HTTP 请求能有效降低写入开销。方案 B 的瓶颈通常不在 InfluxDB 而在 Python 单线程处理能力设备量上到几百台时可以考虑用 asyncio 版客户端或加进程池。4.4 查询校验怎么确认数据真的落库了无论用方案 A 还是方案 B配置完成后都必须做查询验证。InfluxDB 1.x 提供了 influx CLI 和 HTTP API 两种方式我自己习惯先用 CLI 快速验证influx -host 127.0.0.1 -port 8086 -database iot_data -username admin -password your-influx-password进入交互式终端后执行SHOW MEASUREMENTS SELECT * FROM env_data ORDER BY time DESC LIMIT 10如果能看到刚发的消息记录链路基本就通了。然后可以进一步验证规则引擎的字段映射是否正确检查各字段是否都有数值没有的字段说明规则 SQL 的字段名写错了。HTTP API 方式适合快速脚本验证curl -G http://127.0.0.1:8086/query?dbiot_datauadminpyour-influx-password --data-urlencode qSELECT * FROM env_data LIMIT 5还有一种情况要注意数据确实写进去了但 Grafana 上查出来的时间是 1970 年。这说明写入时时间戳精度没有对齐InfluxDB 默认按纳秒处理如果你在 EMQX 桥接里声明的是毫秒而实际传的是秒或者反过来时间偏移就会非常大。统一在源头把 ts 字段格式固定成毫秒各个地方声明精度也用毫秒就不会有这种问题。5. 常见问题与排查技巧实录5.1 设备连接不上 EMQX这个问题的排查路径比较固定。先确认网络层通不通在设备端 telnet 一下 1883 端口看 TCP 能不能连通通了再看认证配置EMQX 开启了认证客户端没带正确的用户名密码会被拒绝连接再看 client_id 是否冲突两个设备用了相同的 client_id后者会把前者踢下线表现为连接频繁断开。我遇到过一种隐蔽场景设备端设置的 keepalive 是 60 秒但局域网偶尔抖动超过这个时间EMQX 判定设备失联主动断开。解决方法是把 keepalive 调到 120 秒同时在 EMQX 侧适当放宽最大连接时长。5.2 消息发了但 InfluxDB 没数据遇到最多的是规则引擎 SQL 没有匹配到目标主题。EMQX 规则 SQL 的 FROM 子句用的是 MQTT 主题过滤表达式如果你在消息里发布到 env/node-1/data但规则里写的是 env/node-1/data/extra 那当然永远匹配不上。容易忽略的是通配符的写法MQTT 的单层通配符是 多层是 #拿 SQL 思维去写容易出错。字段映射不匹配也是高频问题。设备端发布 JSON 里字段名是 temp规则 SQL 和 InfluxDB 桥接模板里却写了 temperature那 InfluxDB 里对应字段就是空的。此时去 EMQX 规则的调试页面模拟一次消息输出结果一目了然。5.3 字段类型冲突导致写入失败InfluxDB 对同一个 field 的数据类型有严格要求第一次写的是 float后续写了字符串或整数写入会直接报错。我踩过的一个例子某天设备端程序异常把 voltage 字段发成了字符串结果这条消息以及后续所有包含 voltage 的写入全部失败表里出现了一段数据空洞。解决方法是把所有数值字段在设备端统一强转成 float 再发送并且在消费者解析时再做一次类型转换兜底。已经脏掉的数据只能删掉重建字段名或删除对应序列后重新写入。5.4 时间戳精度问题导致的诡异时间时间戳精度问题在前面提过这里再补一个场景使用 EMQX 规则引擎的 InfluxDB 桥接时如果你不显式指定 payload 里的时间戳字段EMQX 默认会用服务器收到消息的时间写入。这本身没问题但如果你在设备端也带了 ts 字段而没有在模板中启用它就会出现双时间字段InfluxDB 只认 time 字段ts 只会被当作普通 field 存储。所以配置时要想清楚统一用服务器时间还是统一用设备时间。我建议优先用服务器时间因为设备端时钟漂移是普遍现象尤其电池供电的设备RTC 不准会导致数据写入顺序混乱。5.5 磁盘无声无息占满时序数据库最大的隐藏坑就是保留策略没配置或配置失效。InfluxDB 1.x 的数据库默认保留策略是 autogenduration 是 0也就是永不过期。很多人建库后直接往里写忘了改默认 RP半年后磁盘满了才来排查。我建议建库后第一件事就是显式创建自己想要的 RP 并设置为默认同时监控 InfluxDB 存储目录的磁盘使用率提前设好阈值告警。另外 CQ 也有自己的计算周期InfluxDB 默认每 10 秒检查一次但实际的 GROUP BY 窗口是 1 小时聚合数据的产出会有一定延迟。如果下游报表依赖 CQ 结果要接受这个延迟不要拿实时逻辑去要求它。5.6 查询越来越慢的优化路径查询慢最常见的原因是时间范围过大且没有 tag 过滤条件。比如 SELECT * FROM env_data 丢到 Grafana 里拉全量数据数据量上来以后必然卡。我的优化路径是先确保查询 SQL 里带 WHERE time 范围再尽量带上 device_id 这个 tag 过滤条件再确认查询涉及的时间区间是否跨越了太多 shardInfluxDB 按时间分片存储范围太大会扫描多个分片最后对于需要长期统计的查询不要查原始表去查 CQ 已经聚合好的 1 小时表扫描的数据量少几个数量级。6. 扩展方向与实际项目体会链路跑通之后往上扩展就很自然了。Grafana 加 InfluxDB 数据源把温湿度曲线、电压波动、设备在线率全部可视化这一步的配置比想象中简单因为 InfluxDB 和 Grafana 的集成已经非常成熟数据源填地址和库名就能拉数据。报警方面可以用 Grafana Alerting针对异常温度、连续多次上报失败等规则发告警无需再引入额外的监控组件。EMQX 侧如果设备量继续增长单节点不够可以横向扩展成集群多个节点的 MQTT 消息通过集群内部路由互通前端加负载均衡即可。InfluxDB 1.x 的集群能力比较弱写入瓶颈真的到了一般先用更高配置的机器和批量写入扛或考虑按业务拆库拆分。到了这一步架构复杂度才开始上升但对于绝大多数场景单机 InfluxDB 配合合理保留策略能撑很久。我在实际使用中还有个体会就是调试链路时一定要有端到端的模拟工具。设备端脚本、MQTT 客户端、EMQX 规则调试、InfluxDB 查询每个环节都可能出错只有把每个环节单独验证通了再串联起来才不会在链路不通时一头雾水。这套组合最大的价值是让数据管道本身变得透明——设备有没有上报、消息有没有到 Broker、数据有没有落库、查询是否正确每一步都有清晰的检查手段。如果你正在评估物联网数据方案我建议不要一上来就追求复杂的流处理框架。先用 EMQX 加 InfluxDB 把链路跑通数据能看到、能查到、能报警这套地基就是扎实的。后续真有更高需求再往流处理、实时计算方向演进也完全来得及。最后分享一个小技巧所有配置改动都通过 Docker Compose 和配置文件管理不要只做一次性的手动配置否则环境销毁重建后所有规则和桥接都要重新点一遍界面那种痛苦谁经历过谁知道。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑