做时序预测这些年,我越来越觉得:模型再厉害,也扛不住数据来路乱七八糟。之前接手一个设备健康预测项目,业务方要的是"未来两小时温度趋势",可数据散落在三套系统里,一套走消息队列,一套只能导CSV,还有一套要临时写脚本去爬内部接口。每次训练集导出基本靠手搓,跑一次要半天,特征对齐还老出问题。后来痛下决心,把所有时序数据统一收进 TimechoAI 作为数据底座,用 SDK 和 REST 双通道对外提供服务,整个链路才算真正稳定下来。这篇文章就把这套实践的来龙去脉、通道设计、具体落地步骤和踩过的坑完整写出来,给正在做时序预测数据层建设的朋友一个可参考的样本。
1. 为什么时序预测要从"接口"迁移到"数据底座"
先说结论:接口不是不能做,而是做到后面成本会指数级上升。我们当初把所有数据源都抽象成"接口",听起来很灵活,实际上埋了不少雷。
1.1 接口时代的三个隐形代价
第一个代价是数据口径不一致。不同接口返回的时间戳粒度不一样,有的到秒,有的到毫秒;温度字段有的给瞬时值,有的给五分钟平均值。下游建模时如果不仔细处理,模型看到的就是一堆"同名字但不同含义"的特征列。
第二个代价是链路太长。当时训练一个预测模型,需要从原始接口拉数、落本地文件、写清洗脚本、再跑聚合统计,四步都得人工介入。每次跑批都要重新写一遍参数,别说业务部门催进度,连我们自己都觉得羞耻。
第三个代价是没有任何元数据管理。哪个测点是哪个设备的?单位是摄氏度还是华氏度?数据质量是正常还是缺失补齐过?这些信息全在工程师脑子里,换个人接手基本等于从零开始。
1.2 数据底座到底"底"在哪
后来我们换了个思路:不要面向接口做应用,而是面向数据底座做应用。所谓数据底座,核心就三件事:
- 统一的时序数据模型,让所有测点都有唯一的序列标识;
- 统一的接入方式,历史补数、实时写入、批量导出都有标准化通道;
- 统一的查询语义,无论数据原来在哪,最终都以同一种 SQL 或 API 形式对外暴露。
TimechoAI 在这套架构里承担的就是底座角色。它底层是列式存储加时序优化的计算引擎,底层内核兼容 Apache IoTDB 的生态,这意味着团队里已有的 SQL 经验可以平移过来,不用为了上数据底座而重新学习一套方言。我们当时评估过几种方案,选它还有一个很现实的原因:SDK 和 REST 两种通道都是原生能力,不需要自己再单独搭一层代理服务。这个特性在后面做双通道实践时帮了大忙。
1.3 从接口到数据底座,我们做了什么动作
落地过程其实就三步:
- 把散落的接口数据统一抽取到 TimechoAI,按设备、测点、标签建模;
- 写入通道按数据源类型划分:业务库用 SDK 增量同步,第三方伙伴数据走 REST 接口推送;
- 预测任务、特征查询、结果展示全部基于底座读取,不再直连原始接口。
做完这三步之后,最直观的变化是:训练集准备时间从小时级降到了分钟级,而且口径终于统一了。后面我们所有预测模型的迭代,都建立在这套底座之上。
2. SDK 与 REST 双通道的分工逻辑
双通道不是炫耀技术,而是因为不同使用方的诉求完全不同。我们把通道设计成"SDK 管生产、REST 管消费",各自承担明确的职责,避免出现一个通道被硬塞进不合适的场景。
2.1 两种通道的本质差异
SDK 通道是嵌入应用进程内的客户端。它的优势是长连接、批量、低延迟,适合高频写入、大规模导出、流式读取这类"程序对程序"的场景。REST 通道走标准 HTTP 协议,优势是跨语言、跨网络、易于调试,浏览器、脚本、第三方系统都能直接用,但每次请求有 HTTP 开销,不适合小步快跑式的频繁读写。
两者的差别我通常用一个类比:SDK 像是仓库里的内部传送带,适合大批量搬运;REST 像是对外营业窗口,适合零散查询和外部提交。没有哪一个绝对好,关键在于用对场景。
2.2 我们实际定下的通道使用规则
项目落定时,我们内部定了几条铁律:
| 场景 | 推荐通道 | 原因 |
|---|---|---|
| 历史数据批量补录 | SDK | 批量提交性能高,支持事务性写入 |
| 实时采集写入 | SDK | 长连接省去重复握手,吞吐更稳 |
| 外部系统推送数据 | REST | 对方不需要引入我们的SDK,降低对接成本 |
| 临时查询、排障 | REST | curl 直接调,方便快速验证 |
| 前端展示预测曲线 | REST | Web端不能直接走 SDK,HTTP JSON 最自然 |
| 训练集全量导出 | SDK | 需要流式遍历,REST会受单次响应体限制 |
这套规则定完之后,我们基本不再纠结"某个需求走哪个通道",因为边界已经画清楚了。边界清晰的最大好处是:性能问题、权限问题、报错问题都能按通道快速定位,不用翻代码找是谁调的。
2.3 双通道共同服务的闭环
我们最终跑通的闭环是这样的:实时采集服务通过 SDK 写入原始遥测数据;预测任务每天早上通过 SDK 批量读取过去 N 天的历史序列,完成特征工程后调用模型推理;推理出的预测值通过 SDK 以"预测序列"的形态写回底座;前端应用通过 REST 查询最新实际值和预测值,绘制趋势对比图。整个链路里,数据始终在底座之内流转,外部接口不再是必经之路。这就是我标题里说的"从接口到数据底座"的真正含义。
3. SDK 通道实战:测点建模、批量写入与训练集导出
SDK 通道是整套架构的主动脉,承载了绝大多数数据进出。这一节我直接展示我们在项目中沉淀下来的实操路径,代码基于 Timecho 官方 SDK 的常见接口形式编写,真实项目中请以你手里版本的文档为准。
3.1 先建模:把原始接口字段映射成时序模型
很多做预测的同学拿到数据第一件事就是跑模型,这是最容易踩的坑。时序数据如果没有清晰的测点模型,特征工程和模型上线后维护都会很痛苦。
我们当时的原始接口返回长这样:设备ID、时间戳、电压、电流、温度、转速。映射到 TimechoAI 时,我们设计的测点路径结构是:
设备ID.测点类型.物理量比如Turbine_01.sensor.temperature,Turbine_01.sensor.rpm。这样设计的好处有三个:一是按设备隔离,不同设备的数据不会互相干扰;二是在时间序列语义上表达清楚物理量;三是后续做按设备分组聚合时,SQL 写起来非常自然。
建模型操作如下(以 SQL DSL 形式):
CREATE DATABASE equipment KEEP 365D; CREATE TIMESERIES equipment.Turbine_01.sensor.temperature WITH DATATYPE=FLOAT, ENCODING=GORILLA; CREATE TIMESERIES equipment.Turbine_01.sensor.rpm WITH DATATYPE=FLOAT, ENCODING=GORILLA; CREATE TIMESERIES equipment.Turbine_01.sensor.voltage WITH DATATYPE=FLOAT, ENCODING=GORILLA;建序列的同时,我们把序列的别名、单位、采样频率写进了底座自带的标签体系里,后续查元数据时直接读标签,再也不用翻接口文档了。
3.2 用 SDK 批量补历史与增量写入
历史数据补录是最考验写入通道的场景。我们第一次补三个月的数据,量级在几亿条测点记录左右。当时走了不少弯路,比如逐条插入,结果慢得无法接受;后来改成批量写入,性能直接提升了两三个数量级。
一个可用的写入代码片段大致长这样:
from timecho.client import Session session = Session( host="10.20.1.10", port=6667, username="write_user", password="***", ) session.open() # 按批次组织数据,每批包含多条时间-值对 batch_data = [ ("equipment.Turbine_01.sensor.temperature", 1716027000000, 72.5), ("equipment.Turbine_01.sensor.temperature", 1716027060000, 72.8), ("equipment.Turbine_01.sensor.rpm", 1716027000000, 3010), ("equipment.Turbine_01.sensor.rpm", 1716027060000, 2998), ] session.write_records(batch_data) session.close()注意这里的时间戳我用的是毫秒整数。时序数据库最怕时间戳语义不统一,所以我们在接入规范里强制约定:全链路时间戳统一为毫秒,时区统一为 UTC 存储、展示层转本地时间。这条规范在后来的排障中省了大量口水。
增量写入的思路类似,区别在于数据源变成了消息队列里的实时消费。此时不要一条一条地 close 再 open 连接,而是复用一个长连接,消费一批写一批;如果写入量非常大,可以考虑把写操作放到独立线程里异步提交,避免阻塞消费主流程。
3.3 导出训练集:降采样、对齐与补点
建好底座的真正价值在导出训练集时体现出来。以前从接口拉数据还要自己做时间对齐,现在用一句聚合查询就能完成:
SELECT device_id, time, AVG(temperature) AS temp_avg, MAX(temperature) AS temp_max, AVG(rpm) AS rpm_avg FROM equipment.*.sensor.temperature WHERE device_id IN ('Turbine_01', 'Turbine_02') AND time >= 1714521600000 AND time <= 1717200000000 GROUP BY device_id, time(5m)这个查询直接按 5 分钟窗口聚合出特征,方便训练集与预测目标对齐。还有一点容易被忽略:特征对齐后经常存在空窗,比如设备停机导致某段时间没有数据。我的做法是先在聚合层做"按业务时间窗口重采样":窗口内没有数据的,标记为空,并让特征工程模块感知这个标记,而不是直接用上个值填充。这样模型能区分"正常波动"和"数据缺失",避免学出虚假规律。
3.4 SDK 通道的性能调优要点
SDK 通道我们压过几次性能,真正起作用的调优点有四个:
- 批量大小:单次写入记录数从几十提升到几千后,吞吐明显爬升,但超过某个阈值后收益递减;
- 连接复用:进程内全局复用一个 session,不要频繁开关;
- 写入缓冲:在 SDK 外层加了一层队列缓冲,消息到达先入队,攒批刷写;
- 异常重试:网络抖动导致的写入失败,用指数退避重试三次,避免瞬时错误拖垮链路。
这四点做完之后,我们的实时写入链路基本没再因为性能问题返工过。
4. REST 通道实战:轻量查询、服务化预测结果回写
如果说 SDK 是主动脉,那 REST 就是面向外部的窗口。我们的前端、运营看板、运维告警服务,都走 REST 通道。相比 SDK,REST 的接入门槛低,但也要注意请求设计。
4.1 REST 适合的典型场景
我们实际用到 REST 的场景有三类:
- 前端页面查询某设备的最近趋势和预测曲线;
- 临时跑一个查询验证数据是否正确,比如业务同学联调时直接看 JSON 返回;
- 外部系统推送特定的业务指标数据,比如第三方气象预报数据,对方只支持 HTTP 回调。
REST 在这几个场景里最大的价值是:不用强制对方引入一套 SDK,减少两层信任成本。尤其是外部对接,让第三方直接 Post 一个 JSON 就能把数据写进来,推进速度会快很多。
4.2 一个查询请求的完整生命周期
REST 查询本质上就是把 SQL 通过 HTTP 提交给服务端执行并返回结果。我们最常用的调用方式是:
curl -X POST http://10.20.1.10:18080/api/v1/query \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tio_live_xxx" \ -d '{ "sql": "SELECT temperature, rpm FROM equipment.Turbine_01.sensor WHERE time >= now() - 1h", "format": "json" }'响应体大概是这样的结构:
{ "code": 0, "data": { "timestamps": ["2024-05-19T08:00:00Z", "2024-05-19T08:05:00Z"], "values": { "temperature": [72.5, 72.8], "rpm": [3010, 2998] } } }这个流程里我特别想提醒一点:REST 通道一定要做 SQL 白名单或参数化约束,不能让调用方随便提交任意 SQL。我们早期开放了全 SQL 提交,结果一个同事不小心提交了不带时间范围的查询,直接把底层引擎压到告警。后来我们在网关层做了限制:业务查询只允许带时间范围,聚合查询只允许按固定窗口聚合,导出任务走单独的批量接口。安全边界是自己的,不要指望所有调用方都有分寸。
4.3 预测结果的服务化回写
REST 不仅能查,也能写。我们在预测任务跑完之后,会把每个设备的预测序列通过 SDK 写回底座,路径设计成:
equipment.{device_id}.prediction.temperature然后前端用 REST 查询时,可以直接把实时值和预测值一起取回来:
curl -X POST http://10.20.1.10:18080/api/v1/query \ -H "Content-Type: application/json" \ -H "Authorization: Bearer tio_live_xxx" \ -d '{ "sql": "SELECT temperature, prediction.temperature FROM equipment.Turbine_01.sensor WHERE time >= now() - 2h", "format": "json" }'这样前端画趋势图时,不需要后端再单独聚合一次,直接消费底座的查询结果即可。我们管这个叫"预测结果服务化":模型推理只是一个内部过程,对外暴露的是统一的查询语义,调用方根本感知不到哪个值是真实采集、哪个值是模型输出的。
4.4 REST 通道的限流与缓存
面向大量前端请求时,REST 通道不能裸奔。我们加了两层防护:
- 网关层限流:按调取方身份限制每秒请求数,避免一个页面轮询把其他业务的查询饿死;
- 结果缓存:对近 5 分钟的常用趋势查询做了缓存,命中后直接返回,减少底层引擎压力。
刚开始我们没做缓存,结果前端每个图表组件各自发请求,同一个设备的曲线数据在 10 秒内被查了几十遍。加上一层简单缓存后,网关压力立刻小了一大半。别迷信"查询性能够快就不用缓存",高频重复查询永远要优先挡住。
5. 双通道落地后最容易踩的五个坑
双通道的架构看起来清爽,真正跑起来还是有不少暗坑。我们用了大概两个月才把所有关键问题磨平,这里按踩坑顺序和影响程度逐个说。
5.1 时区与时间戳精度不一致
这是最隐蔽但破坏力最大的坑。REST 通道的调用方来自不同系统,有的传毫秒时间戳,有的传 ISO 字符串;有的按本地时间计算"最近一小时",有的按 UTC 计算。第一次对不上时,我盯着两条一模一样的 SQL 看了半天,一个返回数据一个不返回,最后发现是调用方传的时区差 8 个小时。
解决方案就一条:在网关层统一约定时间戳格式,拒绝接收非标准格式;存储统一 UTC,展示转换由应用层负责。这个约定从双通道设计的第一天就要写进接口文档,不要指望每个调用方自觉。
5.2 数据乱序与重复写入
设备上报数据有时会因为网络重传而乱序。SDK 写入时如果严格按时间顺序写还好,一旦并发场景多,可能出现"后到的旧数据"覆盖了"先到的新数据"的诡异情况。我们在底座层面开启了写入去重和乱序数据的处理策略,同时在应用层为每条数据带上设备侧的递增序号,方便判断是否重复。
更重要的是预测训练时不要被乱序数据误导。我们的经验是:训练集导出前会加一个质量校验步骤,检查每个窗口的时间戳单调性和最小间隔,异常数据单独标记出来,而不是直接喂给模型。
5.3 SDK 连接与 REST 连接被误用
SDK 和 REST 在连接管理上思路完全不同。SDK 讲究长连接复用,一个进程全局一个 session 就够;REST 则不要维护长连接,每次请求独立握手反而更简单。许多同学容易在 REST 网关层引入厚重的连接池,结果增加复杂度还拖慢速度。
还有一个常见误用:批量导出数据时用 REST 一条条查。REST 单次响应有大小限制,拉大训练集会非常痛苦。正确的做法是:导出走 SDK 的流式遍历,REST 只负责小结果集查询。我们内部把这条写进代码评审规范里,凡是导出类需求出现 REST 调用,一律打回。
5.4 写读并发导致的资源争抢
双通道同时高负载时,写入和查询会争抢底层的 IO 和 CPU 资源。我们有段时间批量补历史数据,同时前端看板在做小时级聚合查询,结果两边都慢。
后来做了三级隔离:
- 时间上错峰:大批量补数放到夜间执行;
- 资源上分区:把高频查询的序列和低频大批量的历史序列放在不同的存储组中,避免存储层面的相互影响;
- 任务上限流:写任务开始时主动降低查询侧并发上限,保证核心页面可用。
这套隔离做完之后,即使补数任务进行中,前端核心查询的响应时间也能维持在可接受范围内。
5.5 权限边界的双通道一致性
SDK 和 REST 虽然通道不同,但必须沿用同一套权限模型。我们前期的教训是:在 SDK 里配置了高权限账号,同一套凭据也被复制到 REST 调用里,结果前端页面能直接执行任意查询,风险相当大。
现在的做法是:SDK 使用独立的服务账号,权限最小化到读写指定设备范围;REST 使用面向应用的 token,只能查询脱敏字段和聚合结果。两个通道的权限在底座管理端统一配置,避免漂移。安全这个事,宁可一开始麻烦一点,也不要上线后弥补。
6. 复盘:这一套迁移后我看到的真实变化
从接口直连到双通道数据底座,整体切换周期约三周。印象最深的是,架构复杂度和业务体验之间的关系:表面上多了一层底座,多了一套 REST 网关,好像"多了一点东西";实际上对上层业务来说,接入路径反而变得更简单了,因为大家不再需要各自对接不同的数据源格式。
6.1 几个真实的量化对比
以我们设备温度预测项目为例,迁移前后最明显的对比:
- 训练集准备:从人工导出加清洗约 4 小时,缩短为自动跑批约 15 分钟;
- 查询响应:前端趋势查询平均响应从 800 毫秒左右降到 120 毫秒左右;
- 新设备接入:原来要写一套接口适配代码,现在只需要建测点、配写入任务,时间从天级降到小时级;
- 排障效率:数据链路从"黑盒"变成"可查询",任何人通过 REST 验证一条数据是否入库,过程不超过一分钟。
模型效果本身没有因为换底座而突变,但迭代速度明显加快。过去一周只能试两个特征组合,现在是同一天可以跑六七个实验。这对我而言才是数据底座最大的价值:它不直接产生模型能力,但它让模型能力可以被快速打磨。
6.2 如果从零开始,我会怎么设计
再让我做一次,我会把顺序优化成:
- 先想清楚数据模型(设备、测点、标签、单位、采样频率),再想通道;
- 通道规则在第一天就定死,SDK 管生产、REST 管消费,不做灵活性让步;
- 权限体系最先建,而不是最后补;
- 任何数据入库都统一走底座,不允许绕过底座直连底层存储。
这四条看起来简单,但每个都是从实际摩擦里磨出来的教训。
6.3 后续可以扩展的方向
这套双通道底座目前已经稳定运行,后面我计划做两件事:一是把特征工程的部分计算下沉到底座,让服务端直接输出聚合特征,减少数据搬运;二是把模型的预测结果也纳入统一的元数据体系,加上置信度标签,让前端不只是看到一条预测曲线,还能看到模型对这段预测的把握程度。
技术选型从来不是越新越好,而是越贴合场景越好。TimechoAI 的 SDK 和 REST 双通道恰好覆盖了我们生产与消费两端的需求,省掉了自建接入层的维护成本。如果你也正在为时序预测的数据链路头疼,不妨先别急着调模型,把数据底座这一层夯扎实,后面的路会顺很多。