3. 数据工程基础:数据源类型与仓库分层设计

ETA 预测模型的精度高度依赖底层数据的质量与结构。本章聚焦数据工程的核心环节:识别三类关键数据源、理解数据仓库的四层分层模型,并梳理 ETL 流程的全貌。扎实的数据基础是后续参数配置与成本分析的先决条件。

3.1 数据源类型

ETA 模型需要融合实时轨迹、业务订单与环境数据。下表对比了三种核心数据源的特征与用途:

数据源 典型系统 数据格式 更新频率 ETA 模型用途
GPS 轨迹 车载终端、手机 SDK JSON / Protobuf 1–5 秒/次 实时速度、路径偏移、拥堵识别
订单系统 OMS / TMS 结构化表 (MySQL) 分钟级 起终点、货物类型、时效要求
天气 API 气象局 / 第三方平台 XML / JSON 1 小时/次 雨雪、能见度、风速对车速的影响
工程提示:GPS 轨迹数据量最大(单日可达 TB 级),建议在采集端做轻量过滤(如剔除漂移点),避免 ODS 层存储压力过大。天气 API 建议缓存本地,防止因接口限流导致模型特征缺失。

3.2 数据仓库分层设计

标准数据仓库采用四层架构,从原始数据到业务指标逐层加工。下图展示了各层之间的数据流转关系:

数据仓库分层架构(ODS → DWD → DWS → ADS) ODS 原始数据层 GPS / 订单 / 天气 保留原始快照 DWD 明细层 清洗 / 去重 / 标准化 统一时间戳与坐标 DWS 汇总层 路段平均速度 小时级聚合指标 ADS 应用层 ETA 特征宽表 运费成本维度表 数据流向说明 ① ODS → DWD:清洗去重,统一格式 ② DWD → DWS:按路段/时段聚合 ③ DWS → ADS:宽表拼接,供模型使用 分层核心价值 • 数据血缘可追溯 • 计算任务可复用 • 模型特征与业务解耦

3.3 ETL 流程概览

ETL(Extract, Transform, Load)是将原始数据加工为模型可用特征的核心流水线。以下流程图展示了从数据源到 ADS 层的完整 ETL 步骤:

ETL 流程概览:从数据源到特征宽表 数据源 GPS / 订单 / 天气 Extract 增量 / 全量抽取 Transform 清洗 / 聚合 / 关联 Load 写入 ADS 宽表 各阶段关键操作 Extract 阶段: • GPS:Kafka 实时消费,按 5 秒窗口落盘 • 订单:JDBC 增量抽取,基于 update_time 分区 • 天气:定时调用 API,JSON 解析后存入 ODS Transform 阶段: • GPS 漂移点过滤(速度 > 120 km/h 剔除) • 订单地址解析为经纬度(Geocoding) • 天气与轨迹按时间+空间关联 Load 阶段: • 写入 ADS 特征宽表,按日期+城市分区 调度策略: • GPS 实时流每 5 分钟触发一次微批次 • 订单与天气每小时全量刷新

3.4 分层设计的关键考量

  • ODS 层:保持与源系统一致的数据结构,不做任何业务转换。GPS 轨迹以 JSON 原样存储,便于回溯排查数据质量问题。
  • DWD 层:执行数据清洗(空值填充、异常值剔除)、格式统一(时间戳转为 UTC+8、坐标转为 GCJ-02)、实体对齐(同一订单在不同系统中的 ID 映射)。
  • DWS 层:按业务维度(路段、时段、天气等级)预聚合统计指标,如“路段 A 在雨天早高峰的平均车速”。该层数据量较 DWD 减少 80% 以上,大幅提升下游查询效率。
  • ADS 层:面向模型训练与成本分析,生成特征宽表。每条记录对应一次运输任务,包含 40–60 个特征字段(如出发时间、预计里程、历史准点率、实时拥堵指数、单位里程油耗成本等)。
工程最佳实践:建议在 DWD 层建立统一的“轨迹事件表”,将 GPS 点序列转换为行程段(Trip Segment),每段记录起止时间、平均速度、最大速度、停留时长。该表是后续所有 ETA 特征计算的基石。

3.5 ETL 代码示例:GPS 轨迹清洗(PySpark)

from pyspark.sql import SparkSession
from pyspark.sql.functions import col, when, lag, abs as spark_abs
from pyspark.sql.window import Window

spark = SparkSession.builder.appName("gps_clean").getOrCreate()

# 读取 ODS 层原始 GPS 数据
df_raw = spark.read.json("hdfs://data/ods/gps/2025-03-01/")

# 过滤漂移点:速度 > 120 km/h 或 经纬度超出中国范围
df_clean = df_raw.filter(
    (col("speed") <= 120) &
    (col("speed") >= 0) &
    (col("lat") >= 18) & (col("lat") <= 54) &
    (col("lng") >= 73) & (col("lng") <= 135)
)

# 按车辆 ID 和时间排序,计算相邻点时间差
window_spec = Window.partitionBy("vehicle_id").orderBy("timestamp")
df_clean = df_clean.withColumn(
    "time_diff_sec",
    (col("timestamp").cast("long") - lag("timestamp").over(window_spec).cast("long"))
)

# 剔除时间差 < 1 秒的重复点
df_dedup = df_clean.filter(col("time_diff_sec") >= 1)

# 写入 DWD 层
df_dedup.write.mode("overwrite").parquet("hdfs://data/dwd/gps_clean/2025-03-01/")
注意:GPS 数据清洗时,不要直接删除所有“速度=0”的点。车辆在红绿灯或装卸货时的静止点对 ETA 模型中的“等待时间”特征至关重要。建议保留静止点并标记状态,而非简单过滤。