真实做水务数据平台的都知道,排水管网监测这块,看着不起眼,实质上比很多“高大上”的工业互联网项目都要麻烦。泵站、液位计、雨量站、流量计,点位数百上千,数据每秒都在产生,而且这个行业有个特点——问题一旦爆发就是城市内涝、污水溢流这种大事,数据晚几秒都可能会误事。我前后参与过几个智慧排水方向的项目,实话说,真正把数据从“采集”做到“能用”的,离不开一个合适的时间序列数据库。这套系统里,我选的是 TDengine,今天就把它的应用过程、踩过的坑、整理出的建模思路一次性讲清楚。
这个内容适合正在做智慧城市、水务信息化项目的技术人员,也适合想了解时序数据库在真实工业场景中怎么落地的朋友。我会从数据特点、库表设计、接入实践一直讲到数据分析和运维心得,不会有太多“PPT式”的高大上名词,基本都是可以直接拿去参考的实操经验。
1. 智慧排水场景的数据特点与架构选择
1.1 一张排水管网图背后到底有多少数据要接
智慧排水系统的基础,是先搞清楚“测什么”。一个典型的中等城市排水信息化项目,监测对象一般包括四类:管网液位、泵站运行状态、雨量、污水厂进/出水水质流量。分布到全区可能有几百到上千个监测点位。
每个点的上报频率不一样。泵站电机这种设备数据,可能每秒都在报;管网液位通常 1~5 分钟一条;雨量筒在降雨时段会加密到每 30 秒一条;水质仪表因为采样周期长,可能 15 分钟一条。按一个 800 个点位的项目估算,即使平均每分钟一条,一天新增数据量也就是百万条级别,一年下来几亿条。这个量级在关系型数据库里不是不能存,但存进去容易,取出来难。
到了汛期,大屏上要按五分钟粒度展示整个城区的液位态势,后台还要算每小时的均值、峰值、累计雨量,这些查询都是典型的“时间范围 + 聚合”操作。用传统数据库做,索引建少了查询慢,索引建多了写入慢,还要天天想办法清理历史数据。这个场景天生就是为时序数据库准备的。
1.2 为什么不用关系型数据库
不少项目一开始用的是 MySQL 或者 PostgreSQL,我甚至见过用 SQL Server 存监测数据的。最初的几十万个点跑起来没感觉,但数据到千万级、亿级以后,问题就开始冒头。
最突出的问题是聚合查询。按小时、按天做 AVG、MAX、MIN 操作,关系型数据库会把所有原始行扫一遍,即使建了时间索引,数据量上来之后扫描成本依旧很高。排水业务里很多分析要跨一整个汛期查询,一条 SQL 跑几十秒,值班的人根本没法用。
其次是历史数据的清理。排水数据有一个特点:越老的数据价值越低。原始液位数据超过一年基本没人看了,但直接 DELETE 在 MySQL 里是灾难性的,锁表时间、碎片、性能回退样样都麻烦。时序数据库一般自带保留策略,数据超过设定时间自动滚动删除,省掉一个专职 DBA 的活儿。
还有一个容易被忽略的点:关系型数据库对“同一个物理测点、同一时间戳可能产生多条记录”这种数据天然不友好。排水现场的传感器经常闹脾气,重复上报、乱序上报是家常便饭。时序库在这块的处理机制反而更贴合实际情况。
1.3 为什么选择 TDengine 而不是其他时序库
时序数据库不止 TDengine 一种。比较常见的还有 InfluxDB、TimescaleDB 等。我在选型时其实做了对比测试,最后定 TDengine 而不是其他方案,有几个现实原因。
第一,超级表和标签模型很适合排水业务。排水管网里,每个测点都有大量静态属性:属于哪条路、哪个泵站、量程多高、装的什么仪表、维护人是谁。如果每个测点单独建一张表,几千张表管理起来非常痛苦;如果全塞一张大表,每次查询都把属性丢出来分析,又浪费空间。TDengine 的超级表思路是“一张大表管结构,标签管属性”,建模方式非常贴近这种场景。
第二,内置的降采样和窗口聚合太实用了。汛期值班要看五分钟均值,做月度分析要看天均值,TDengine 一条 SQL 就能搞定,还能配合时间范围自动填充缺失的窗口,后台不用写复杂的定时任务。
第三,部署和运维足够轻。单节点能跑,不需要像很多大数据方案那样上来就要搞一整套集群。我们前期部署就是一台 32 核 64G 的服务器,托底了全区 800 多个点位的全量数据。后期如果数据量翻倍,再加节点就行,水平扩展也是原生支持的。
当然,它也不是没缺点。TDengine 有自己的 SQL 方言,和标准 SQL 有点出入,团队里写惯了 MySQL 的人要适应几天;如果业务里大量涉及“非时间序列数据”,比如图片、单据、配置表之类的,建议还是留给专门的关系库处理,不要硬塞进来。选型这事,合适比“先进”更重要。
2. TDengine 的核心机制如何在排水场景落地
2.1 用超级表管理测点,用标签管理设备属性
TDengine 对排水项目最友好的一点,就是它的“超级表 + 子表 + 标签”三层模型。我一开始对这三个概念有点绕,后来用排水业务一比喻就通了。
超级表(STABLE)就像管网台账里的一张“测点类型定义表”:规定每个测点要记录什么字段、字段什么类型。子表(TABLE)则对应每个具体的物理监测点:管网液位计 001、管网液位计 002、泵站流量计 005……每个子表只有自己的流水数据,物理上独立存储,查询时却能通过超级表统一切换。
标签(TAGS)用来存每个测点的静态描述。这些信息在每条数据里都是不变的,不如单独存放、单独建索引。
实际建表前,我会先画一张字段规划表,把每个测点的共有字段定下来:
| 字段名 | 类型 | 说明 |
|---|---|---|
| ts | TIMESTAMP | 采集时间 |
| level | FLOAT | 液位值,单位米 |
| ratio | INT | 量程百分比 |
| battery | FLOAT | 电池电压,单位 V |
| signal | INT | 信号强度,单位 dBm |
标签则包括测点编号、所属泵站/路段、行政区、测点类型、量程上限、维护人电话等,这些字段不参与数据计算,只用来分组和过滤。
2.2 建表、写入、查询的完整 SQL 实践
建库和建超级表的 SQL 长这样:
CREATE DATABASE IF NOT EXISTS smart_drainage KEEP 365 DAYS 10 BUFFER 16 WAL_LEVEL 1; USE smart_drainage; CREATE STABLE IF NOT EXISTS level_points ( ts TIMESTAMP, level FLOAT, ratio INT, battery FLOAT, signal INT ) TAGS ( point_id BINARY(20), location BINARY(64), station BINARY(32), district BINARY(16), range_max FLOAT );这里有几个参数值得说明。KEEP 365表示保留一年的原始数据,超过自动删除;DAYS 10表示按 10 天一个数据文件分区;BUFFER 16是写入内存池大小;WAL_LEVEL 1动态调整写入策略,保证性能和可靠性的平衡。
每条数据对应一个测点,所以每个测点都建一张子表,用标签绑定到超级表上:
CREATE TABLE IF NOT EXISTS level_point_001 USING level_points ( point_id, location, station, district, range_max ) TAGS ( 'LV-001', '北干道雨水管网', '城北泵站', '北区', 10.0 );这只是演示写法。实际项目里几百个子表肯定不能手敲,我会写一个初始化脚本,从点位台账 Excel 里读信息,自动拼接建表语句批量执行。这一步看似不起眼,但后面所有查询、告警、报表都要靠这套标准标签体系支撑。
写入用 INSERT:
INSERT INTO level_point_001 ( ts, level, ratio, battery, signal ) VALUES ( NOW, 3.82, 38, 3.65, -72 );在接入层数据量大时,推荐批量写入。一条 SQL 带多组 VALUES,或者用参数绑定的方式,比一条一条 INSERT 快非常多。这个差别在每秒几千条写入时特别明显。
到了查询环节,TDengine 的窗口聚合特别趁手。汛期值班看五分钟均值:
SELECT _wstart, AVG(level), MAX(level) FROM level_points WHERE point_id = 'LV-001' AND ts >= NOW - 1d INTERVAL(5m);按行政区统计每小时最大液位:
SELECT _wstart, district, MAX(level) FROM level_points WHERE ts >= NOW - 1d INTERVAL(1h);注意窗口聚合的起始时间列用_wstart,不是原来的ts,这是 TDengine 和标准 SQL 差异使用里容易踩坑的地方。
2.3 保留策略与数据生命周期管理
排水数据不像交易流水要永久留存。原始液位数据一年后基本失去实时参考价值,但历史趋势分析又想保留一个粗粒度版本。这个需求 TDengine 处理起来很顺手,我用的方案是“双表策略”。
原始数据表保留一年,KEEP 365;另外建一张按天聚合的统计表,用一个定时任务或者干脆在查询端定时跑 INSERT SELECT,把每天的日最高、日最低、日均值落到统计表里,这张表保留五年。这样既控制存储成本,又不丢长期趋势数据。
我在一个中期项目里具体算过账:800 个点位,五千多万条记录/月,单台普通服务器加上历史聚合表,磁盘占用大约在 1.5T 左右。而如果全部按原始数据保留五年,这个量会直接翻几倍,对机房存储压力很大。合理设置保留策略,是排水数据平台上线的第一课。
3. 落地过程中的工程化实操
3.1 设备接入层的设计要点
数据库只是存储底座,数据能不能真实、及时地进来,取决于接入层做得好不好。我在项目里见过太多因为设备上报时间和服务端接收时间混淆,导致数据整体漂移的情况。
排水现场的传感器通常通过 4G/5G 或者 LoRa 网关把数据推到消息队列,再经过一道规则引擎做单位换算、异常过滤,最后落到 TDengine。设备上报的 JSON 里一般会有采集时间字段,入库时必须用这个字段作为时间戳,而不是消息到达服务器的时间。否则设备网络一抖动,数据时间线就乱了。
另外,传感器时间的校准是个长期工作。很多液位计内部的时钟会慢慢漂移。我的做法是在接入层收到数据时,对比设备时间和服务器时间,偏差超过 2 分钟就在日志里标黄,连续偏差超过 10 分钟直接告警,让运维人员去现场处理。这套机制在项目运行三个月后,整体数据时间准确率能长期保持在 99% 以上。
3.2 从接入层到存储层的流量控制
汛期是排水数据平台的“高并发时刻”。暴雨期间,所有点位几乎同时加密上报,加上网关重连、数据补传,写入量可能是平时的 10 倍。
我在项目里验证过 TDengine 的批量写入能力:一台 8 核 16G 的机器,通过 REST 接口批量写入,每秒可以处理上万条数据。但对云端部署方案,网络传输很快会成为瓶颈。为了应对断网补传和数据延迟,我会在消息队列和 TDengine 之间加一层带有本地缓存能力的转发服务,把数据按设备 ID 分组,攒够 200 条或者 2 秒便批量提交一次。
这套设计的核心目的是“削峰填谷”。即使 TDengine 短暂不可用,消息队列里的数据也不会丢,恢复后自动补写,不会造成数据缺口。实际运行中,最怕的不是写入慢,而是数据乱序。TDengine 在乱序数据处理上虽然比很多时序库好,但大批量乱序写入还是会增加合并开销。所以我在写入客户端里做了简单的时间戳排序,按设备 ID 分桶,保证同一条设备的数据基本按时间戳递增进入数据库。这个细节对长期性能影响很大。
3.3 在 Grafana 上做一张排水监测大屏的要点
TDengine 和 Grafana 有官方数据源插件,装好之后配置一下地址、库名、账号密码就能连上。排水场景里最常用的是“按测点选曲线”的交互模式。我通过 Grafana 的模板变量功能,把测点列表做成一个下拉框,变量查询语句类似:
SELECT DISTINCT point_id FROM level_points;大屏上每个面板都引用$point_id这个变量,领导想看哪个点就切换哪个点。再按标签维度再加一个行政区变量,就能做到“选区→选点→看趋势”的完整交互。
画液位曲线时,我会把阈值线也画上去。用 Grafana 的阈值选项把超过警戒线的区域标红,值班人员扫一眼就知道哪个点位超限。再叠加雨量计数据,两条曲线放同一时间轴,对应关系很直观。
地图点位展示这块,我建议先在 Grafana 里用 Geomap 面板把点位和实时液位按颜色映射出来。这种方式开发成本最低,效果也足够用于日常监控。如果甲方明确要求 3D 管网展示或者视频融合,再单独开发专门的 Web 可视化端,不必一上来就把可视化做成大工程。
4. 数据带来的业务价值与下一步方向
4.1 管网液位告警与泵站联动
排水系统里最核心的业务就是液位监控和泵站调度。我们的平台运行后,最大的价值就是让调度中心从“被动接电话”变成“主动看数据”。
告警规则可以完全在查询层实现。比如某个点位液位超过量程上限的 75% 就发黄色预警,超过 90% 发红色预警,持续 10 分钟以上自动通知维护人员。这类规则用 TDengine 的连续查询(CQ)功能实现最方便:
CREATE CONTINUOUS QUERY cq_level_alert ON smart_drainage BEGIN SELECT point_id, MAX(level) AS max_level, COUNT(*) AS cnt INTO alert_results FROM level_points WHERE level > 7.5 INTERVAL(1m) END;连续查询每分钟跑一次,把超限点位写入告警结果表,再由后端的告警程序读取结果推送消息。这套链路的好处是告警逻辑完全统一,不会出现前端和后端各搞一套规则的情况。
实际运营中,我们曾经靠这套模型提前四十分钟发现一个泵站进水管液位异常上涨,调度人员随即远程调整了前池闸门。事后查明是上游施工导致管段过水能力下降,如果按以前人工巡检的模式发现,大概率已经出现路面积水了。数据平台的价值在这个案例里体现得很直接。
4.2 内涝预警:从实时监测到等值线可视化
实时液位曲线只能告诉人“哪里涨了”,但调度中心更关心“积水范围可能扩展到哪里”。这个需求靠单点曲线解决不了,需要把液位数据放到空间维度看。
我们做过一版内涝风险图,核心思路是:把某个时刻所有液位计的值当作离散空间点,用插值算法生成一张覆盖整个城区的液位等值线图。液位高于某个阈值的区域自动标记为高风险区,高值区连片的话,就说明这些路段存在积水风险。
这个方案在技术实现上不太复杂:从 TDengine 查最近一分钟所有点位数据,取出经纬度和液位值,在 Web 端画等值线图层。真正复杂的是数据质量——如果两三个点位出现异常高值,插值出来的等值线会把一片区域都标红,产生误报。我们后来在生成等值线之前加了一道“离群点剔除”逻辑,对明显高于周边点位的读数做复核,确认属实才参与插值。
等值线图看的是当前状态,再往前走一步就是预测。我尝试过用时间序列预测算法,比如 ARIMA,对重点点位未来 30 分钟到 2 小时的液位做短时预测。做法是先取过去 7 天的液位数据做模型训练,再用滚动预测的方式输出未来曲线,当预测曲线超过警戒线就提前预警。实验下来,平稳时期的效果比较理想,误差能控制在量程的 5% 以内;但遇到极端强降雨,单纯靠历史数据预测的偏差会明显变大,需要叠加雷达降雨预报做修正,这部分我认为有进一步探索的价值。
4.3 用聚类算法辅助管网诊断
数据积累够了以后,我开始尝试做一些离线分析,辅助管网养护决策。一个比较有效的尝试是对全区液位监测点做聚类分析。
具体做法是取连续三个月的每日液位特征:日均值、夜间最小值、峰现时间、变化幅度,用聚类算法把点位分成几类。分出来的结果很有意思,有一部分点位全天液位都偏高,而且夜间不回落,基本可以判断是管网存在沉积或者过水断面不足;另一部分点位白天低晚上高,往往和周边小区集中排水有关;还有一类点位波动剧烈,可能挨着施工工地或者存在雨污混接。
这套分析不能替代现场排查,但能大幅缩小检修范围。以前养护人员靠经验判断管道问题,现在数据可以给出一个排序:哪些点位最值得优先去查。我在和甲方对接时,把聚类结果和管网 GIS 图层叠加,生成了一张“管网健康度热力图”,汇报时非常直观。算法并不复杂,核心是先做干净的特征工程,再把结果的业务含义解释清楚。
5. 常见问题与排查技巧实录
5.1 写入变慢、数据丢失怎么办
项目运行期间最常见的故障就是写入变慢。排查思路一般按这个顺序来:先看网络和消息队列有没有积压,再看 TDengine 的写入日志有没有报错,最后看磁盘空间和 CPU 负载。
有一个特别容易忽略的点是磁盘空间不足。TDengine 对磁盘满的处理策略比较保守,磁盘满了会拒绝写入而不是覆盖旧数据。所以我们部署时专门写了一个监控脚本,每日检查磁盘使用率,超过 80% 就自动清理旧的备份文件,超过 90% 直接报警。这个脚本看着简单,实际救过我们好几次。
如果数据写入报错里有 “out of disk space” 字样,基本可以确定是存储问题;如果是 “unable to resolve table name”,说明建表流程出了问题。还有一个经验:批量写入时一次性写入的条数不是越大越好,实测 1000~2000 条一批稳定性最好,超过 10000 条反而可能因为内存和超时问题失败。
5.2 查询聚合慢但不想加机器,可以怎么优化
排水平台用久了以后,查询变慢通常会指向几个原因:单表数据量太大、聚合粒度太细、查询条件没有利用到标签过滤。
第一个方向是调整分区粒度。TDengine 默认按 days 参数分区,如果单个分区数据量仍然很大,可以把 10 天一个文件改成 1 天一个文件,查询时能更快定位数据文件。
第二个方向是善用标签过滤。很多查询一上来就是WHERE ts > ...,但如果先加WHERE district = '北区',就能直接跳过大量无关数据,查询速度能快一个数量级。这个优化在数据量几千万以后感受特别明显。
第三个方向是预聚合。如果某个报表每天都要按小时看全城液位均值,不建议每次实时跑全量聚合,而是每天凌晨跑一次定时任务,把前一天的数据聚合成一张轻量级的统计表,报表只查统计表。成本是一天一次全量扫描,收益是报表端秒开。预聚合这步我们在项目里做得比较晚,如果能重来一次,我会在上线初期就把所有核心报表的预聚合任务设计好。
5.3 版本升级和数据迁移的注意事项
TDengine 迭代速度比较快,大版本升级时需要特别注意兼容性。我们有一次从旧版本升级到新版本时,没有先看升级说明,结果启动后部分旧表查询报错。建议所有生产环境升级前先做两件事:一是用 taosdump 全量备份数据,二是找一台测试机完整走一遍升级流程。
taosdump 是官方备份工具,备份命令很简单:
taosdump -c taos.cfg -o /backup_dir恢复的时候注意目标机器的时间要与源一致,否则恢复的时间序列也会错乱。
我们在一次机房迁移时踩过这个坑:备份文件里数据没问题,但恢复后发现某些窗口聚合结果和原来不一致。排查后发现是源库和目标库的时区设置不同,导致时间戳偏移了 8 小时。这个教训让我养成了一个习惯——每次部署新环境第一件事就是统一时间源,统一时区,统一网络时间协议。
5.4 运维脚本化的几点心得
排水数据平台上线之后,考验的不只是功能,而是长期稳定性。我逐步把日常运维整理成一批脚本,覆盖几个场景:检查服务健康状态、磁盘空间清理、写入量监控、数据一致性校验。
比如检查服务健康的脚本,定时推送数据库的SHOW指令,如果返回异常就自动重启服务进程并发送通知。写入量监控则每天统计前一天的总写入行数,如果某个点位连续三天的写入量为零,大概率是现场仪表故障或者通信断开,系统自动生成工单提醒人员巡检。
这批脚本一开始很简陋,后来我按照“脚本只负责执行,配置和模板独立出来”的思路重构了一遍,新增点位时不必改脚本,只改配置文件就行。运维工作从“每天手动盯”变成了“平台自巡检 + 人工处置异常”,大大减轻了值守压力。这个“运维脚本系列”的积累,才是项目后期省心省力的关键。
5.5 汇报层面的数据可视化技巧
平台做完技术部分之后,还有一个同样重要的工作:向决策层汇报效果。我每次汇报都会准备两类材料:一类是技术架构图和数据链路图,适合技术评审;另一类是一线业务案例,比如内涝预警的闭环截图、泵站联动调度的前后对比,专门给管理层看。
在汇报 PPT 里,技术细节尽量控制在一页以内。管理者真正关心的其实是几个问题:系统上线后预警响应时间缩短了多少、管网巡检效率提升了多少、有没有成功的实战案例。我会先把这些指标讲清楚,再展开技术实现。用真实数据说话,比堆砌架构名词更有说服力。
我个人在实际操作中的体会是,在排水这种传统业务场景里,数据库选型往往不是最难的,最难的是把设备、网络、时间、点位这些基础数据管好。TDengine 解决的是存储和查询的底层问题,但如果现场数据源头不准,再好的数据库也白搭。同样重要的,是留足时间做好点位台账的标准化,一开始嫌麻烦,后期会省掉无数麻烦。
如果让我重新做一个排水项目,我会把数据质量监控提到第一天就开始做,把核心报表的预聚合任务在上线前设计好,把排查脚本从第一个点位接入时就同步积累。踩过几次坑之后,我对这套系统的建议很简单:基础打牢、机制自动、报表预聚,然后把精力留给真正需要判断和决策的业务分析。