Edge AI 与 IoT 全栈:云端时序数据库 + Grafana 看板,把数据变成洞察

一、为什么气象数据必须用时序数据库,而不是 MySQL

这是新手最爱踩的认知坑。把气象数据塞进 CREATE TABLE weather (id, ts, t, h, ...) 的 MySQL,前 100 万行很爽,后面就崩了:

  1. 写入模式完全不同:气象数据是"持续高频追加、几乎不更新、按时间范围查",MySQL 的 B+ 树为"随机读写+事务"设计,海量 append 会写放大严重、索引膨胀。
  2. 时间序列查询:"查过去 24 小时每分钟平均温度"在 MySQL 要扫大量行+GROUP BY;时序库用时间分区 + 降采样(downsampling)秒回。
  3. 存储压缩:时序库针对"时间戳+数值"做列式 + _delta 压缩,10 亿点可能只占几 GB;MySQL 同样数据几十 GB。
  4. 数据生命周期:气象数据久了要自动降精度归档(热数据秒级、冷数据分钟级),时序库原生 TTL + 连续查询;MySQL 要手写定时任务。

一句话:数据是"时间为主"就用时序库,是"记录为主"才用宽表/关系库(宽表选型我们昨天那篇 HBase vs Cassandra 讲过)。气象站是典型的时序场景。

二、时序库选型:InfluxDB vs TDengine

维度 InfluxDB(2.x) TDengine(国产,3.x)
架构 单机/TSM 引擎,集群版收费 开源集群,分布式原生
写入性能 高 极高(常快数倍)
查询语言 InfluxQL / Flux SQL 兼容(学习成本低)
资源占用 中 低(单节点扛很高吞吐)
生态 Grafana 原生支持 Grafana 有插件,国内资料多
适用 国际项目、Flux 灵活分析 国内、要 SQL、要省钱省资源

本篇用 TDengine 演示(SQL 友好、单机就能跑、 Grafana 有官方插件),你换成 InfluxDB 逻辑完全一致。

三、一键部署:EMQX + TDengine + Grafana

用 Docker Compose 把三件套起起来:

yaml

复制代码
# docker-compose.yml
version: '3'
services:
  tdengine:
    image: tdengine/tdengine:3.2.0.0
    ports: ["6030:6030", "6041:6041"]
    volumes: ["tdengine-data:/var/lib/taos"]
  emqx:
    image: emqx:5.6.0
    ports: ["1883:1883", "18083:18083"]   # MQTT + 控制台
  grafana:
    image: grafana/grafana:10.2.0
    ports: ["3000:3000"]
    environment:
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes: ["grafana-data:/var/lib/grafana"]
volumes:
  tdengine-data:
  grafana-data:

bash

bash 复制代码
docker compose up -d

TDengine 里建库建表(超级表 + 子表,每站一张子表是 TDengine 的最佳实践):

sql

sql 复制代码
-- 建库: 保留 365 天, 按天分片, 10 年归档
CREATE DATABASE weather KEEP 365 DURATION 1d;
USE weather;

-- 超级表 (模板)
CREATE STABLE s_weather (
  ts TIMESTAMP,
  t FLOAT, h FLOAT, p FLOAT,
  lux FLOAT, rain INT, pm25 FLOAT,
  batt FLOAT, rssi INT
) TAGS (station_id BINARY(16));

-- 每站一张子表 (首次收到数据时自动建, 见消费端代码)

四、Python 消费端:订阅 MQTT → 落库

这是数据落地的核心。用 paho-mqtt 订阅 + 通配所有站点,executemany 批量写 TDengine(REST 接口,免驱动):

python

python 复制代码
import paho.mqtt.client as mqtt
import requests, json, time

TDENGINE_URL = "http://localhost:6041/rest/sql/weather"
USER, PASS = "root", "taosdata"

def write_to_tdengine(rows):
    # rows: [(station, ts, t, h, p, lux, rain, pm25, batt, rssi), ...]
    sql = "INSERT INTO "
    # 按站点分表写入 (TDengine 子表)
    tables = {}
    for r in rows:
        st = r[0]
        tables.setdefault(st, []).append(r[1:])
    stmt = ""
    for st, vals in tables.items():
        stmt += f"station_{st} USING s_weather TAGS('{st}') VALUES "
        for v in vals:
            stmt += f"('{v[0]}',{v[1]},{v[2]},{v[3]},{v[4]},{v[5]},{v[6]},{v[7]},{v[8]}) "
    requests.post(TDENGINE_URL, auth=(USER, PASS), data=stmt.encode())

buffer = []
def on_message(client, userdata, msg):
    payload = json.loads(msg.payload)
    station = payload["device"]
    row = (station, payload["ts"], payload["t"], payload["h"],
           payload["p"], payload["lux"], payload["rain"],
           payload["pm25"], payload["batt"], payload["rssi"])
    buffer.append(row)
    if len(buffer) >= 50:        # 攒批写, 减少 IO
        write_to_tdengine(buffer); buffer.clear()

client = mqtt.Client()
client.on_message = on_message
client.connect("localhost", 1883, 60)
client.subscribe("weather/+")     # 订阅所有站点
client.loop_forever()

关键优化:**攒批写(50 条一批)**而非每条直写,TDengine 的写入吞吐能差一个数量级。这也是时序库 vs MySQL 的一大优势------批量追加写极快。

五、Grafana 看板:把曲线画出来

Grafana 加 TDengine 数据源(装 tdengine-datasource 插件或用 SimpleJSON),然后建 Dashboard。一个站的温度/湿度/气压面板 SQL:

sql

sql 复制代码
-- 温度曲线 (过去 24h, 1 分钟降采样)
SELECT _wstart, AVG(t) FROM s_weather
WHERE ts > NOW - 24h
INTERVAL(1m) FILL(PREV);

-- 多站对比 (气温)
SELECT station_id, AVG(t) FROM s_weather
WHERE ts > NOW - 1h
GROUP BY station_id;

Grafana 里拖几个 Panel:温度时序图、湿度时序图、气压变化(天气预报看气压趋势!)、PM2.5 柱状、以及一张"设备健康度"表格(batt/rssi 低于阈值标红)。一张看板,N 台站一目了然。

六、告警:别等数据异常了你才知道

用 Grafana Alerting 或 TDengine 的流式计算,配置三条关键告警:

  1. 高温告警 :SELECT AVG(t) FROM s_weather WHERE ts>NOW-5m INTERVAL(1m) > 38 → 邮件/钉钉通知(大棚/机房场景重要)。
  2. 降雨告警 :rain = 1 持续 → 通知(联动灌溉/排水)。
  3. 低电量/离线告警 :batt < 3.4 或 NOW - last_ts > 15m(断网/没电静默死亡,呼应①篇的 batt/rssi 设计)。

sql

sql 复制代码
-- 离线检测: 某站最近 15 分钟没数据 (断网/没电)
SELECT station_id FROM s_weather
WHERE ts < NOW - 15m
GROUP BY station_id;   -- 配合定时任务或流式规则触发告警

七、现场踩坑 5 连

  1. 消费端挂了数据全丢:MQTT 用 QoS 0 则 broker 不留存。对策:broker 开"保留消息 + 会话",或消费端做持久队列(如 Kafka 中转,呼应我们第六周 Kafka/Pulsar)。
  2. 时钟漂移导致时间错乱:ESP32 用 NTP 同步(①篇坑 2),否则断网补传的时间戳乱序,时序库曲线断裂。
  3. TDengine 子表爆炸:每站一张子表是对的,但别用"每 10 分钟一张表",会炸。坚持"每设备一张子表"。
  4. Grafana 时区坑 :默认 UTC,看板时间比本地差 8 小时。对策:Grafana 配 timezone: Asia/Shanghai。
  5. 批量写阻塞主循环:消费端写库慢会拖慢 MQTT 接收。对策:写库放独立线程/协程,接收和落库解耦。

八、总结与下篇预告

② 篇我们把① 篇上送的数据真正"接住并变现":用 TDengine(或 InfluxDB)时序库 替代 MySQL,享受高频追加+降采样+自动 TTL 的红利;用 Docker Compose 一键拉起 EMQX + TDengine + Grafana ;写了攒批落库的 Python 消费端 ;在 Grafana 里画出温度/湿度/气压曲线并配置高温/降雨/低电量/离线四类告警。现在你的气象站不再是"会发数据的盒子",而是一个可观测、可告警、能回看的微型气象系统。

下一篇( 云端 AI 预测),我们兑现"AI 天气站"的 AI 部分:用历史时序训练一个轻量预测模型(Prophet / LSTM / 甚至下沉 TinyML 到 ESP32),预测未来几小时的温度与降雨概率,并把预测结果画进同一张 Grafana 看板。到那时,它就真的从"气象记录仪"升级成"天气预言机"了。


下一篇预告

《AI 天气站 ③:云端 AI 预测》------兑现"AI 天气站"的 AI 部分:用历史时序训练轻量预测模型(Prophet / LSTM / 或下沉 TinyML 到 ESP32),预测未来几小时温湿度与降雨概率,画进同一张 Grafana 看板。下一篇敬请期待。

系列导航:

相关推荐
河北乐讯通物联网服务商14 小时前
航油管理终端:机场里每架飞机加了什么油、加了多少,是怎么实时算清楚的?
物联网·物联网卡·航油管理终端
数字新视界16 小时前
信创机房动环监控技术在提升数据中心管理效率中的应用与前景分析
物联网·数据中心·数据中心基础设施管理·dcim管理系统·数据中心dcim
wuyk5551 天前
Keil工具链-第05讲-启动文件与__main到底做了什么
c语言·stm32·单片机·嵌入式硬件·物联网
Eyfcom2 天前
〖物联网终端实战 02〗RFID 批量群读防碰撞算法:整托盘入库如何做到“秒级”识别
物联网·物流系统·快递系统·跨境物流快递系统·物联网终端实战
天空'之城2 天前
009基于物联网的智能厨房安全监测系统
毕业设计·esp32·电磁阀·厨房安全·燃气检测
Eyfcom3 天前
〖物联网终端实战 01〗UWB 室内高精度定位:仓储 AGV 与人员的厘米级轨迹追踪
物联网·算法·物流系统·快递系统·跨境物流快递系统·物联网终端实战
Dev_XH3 天前
【成品设计】基于物联网的车间安全生产监控系统设计
stm32·单片机·嵌入式硬件·物联网
Dev_XH4 天前
【成品设计】基于单片机的家用智能浇花系统的设计与实现
stm32·单片机·嵌入式硬件·物联网
CServer_014 天前
源码交付!有色金属工厂的AI数据底座
人工智能·物联网·制造