6.9 KiB
6.9 KiB
车辆数据最小落库合约
目标
车辆接入系统只持久化能回答业务问题、能回放纠错、能支撑高频查询的数据。协议细节默认保存在 raw,不向上层业务表扩散。
第一性原则
- raw 是证据层,必须能证明收到过什么、解析成什么。
- Kafka 是回放层,消费者失败后优先靠 Kafka lag 追平。
- Redis 是当前态缓存,不是历史库。
- TDengine 只承担高写入时间序列:raw、位置历史。
- MySQL 只承担低基数业务状态:身份映射、实时快照、日指标。
- 同一个事实只落一个主表,其他表只存查询必要的投影。
- 新字段先进入
parsed_json,只有稳定查询需求出现后才提升为列。
目标表边界
| 存储 | 表/Key | 保留内容 | 不保留内容 |
|---|---|---|---|
| TDengine | raw_frames |
raw hex/text、完整 parsed_json、解析状态、协议标签、车辆标识标签 |
fields_json 这类可从 parsed_json/业务表再得到的重复字段 |
| TDengine | raw_frame_payload_chunks |
超长 raw/parsed payload 分片 | 业务查询字段 |
| TDengine | vehicle_locations |
时间、VIN/协议标签、经纬度、速度、方向、SOC、总里程等位置核心字段 | 完整协议 JSON、注册鉴权信息、phone/device_id/vehicle_key 兜底标识 |
| MySQL | vehicle_realtime_snapshot |
每个协议+VIN 的最新事件时间、接收时间、车牌、事件 ID | phone、device、source endpoint、完整 JSON |
| MySQL | vehicle_realtime_location |
每个协议+VIN 的最新位置核心字段 | raw、parsed JSON、消息头内部字段 |
| MySQL | vehicle_daily_mileage |
日期、VIN、协议、日里程、首末总里程、样本数 | 临时 vehicle key、泛化 metric key/value、自增 id、created_at、每帧细节、位置点列表 |
| MySQL | vehicle_identity_binding |
人工维护或导入的 VIN、车牌、phone、device_id 映射 | 注册历史、协议状态、自增 id、created_at |
| MySQL | jt808_registration |
JT808 phone 主键下的注册、鉴权、VIN 匹配状态、来源端点 | GB32960/MQTT 注册信息、位置历史、created_at |
| Redis | vehicle:realtime-raw:{protocol}:{vin} |
每协议最新完整 parsed 状态 | 历史数据、统计结果 |
当前应收敛的重复点
-
vehicle_mileage_points与vehicle_locations.total_mileage_km重复。- 状态:Go 写入链路已停止创建和写入
vehicle_mileage_points。 - 查询:不再暴露单独
/api/history/mileage-points,里程点直接从/api/history/locations的total_mileage_km获取。 - 生产:ECS TDengine 历史库已在上线前重建,
vehicle_mileage_points不再存在。
- 状态:Go 写入链路已停止创建和写入
-
raw_frames.fields_json与raw_frames.parsed_json、vehicle_locations重复。- 状态:新建 raw schema 和 raw 写入已停止使用
fields_json。 - 兼容:raw 查询只选择公共列,不依赖
fields_json;旧表保留该列也不影响查询。 - 生产:ECS TDengine 历史库已在上线前重建,
raw_frames不再包含fields_json。
- 状态:新建 raw schema 和 raw 写入已停止使用
-
vehicle_daily_mileage不再接收临时vehicle_key。- 原因:日统计是正式业务指标,应只统计已经定位到 VIN 的车辆。
- 无 VIN 的 JT808 等数据保留在 raw/Redis/session 中用于排查和待绑定,不进入正式车辆指标。
- 状态:schema 使用
PRIMARY KEY (vin, stat_date, protocol),不保留自增id和created_at;首末总里程和样本数保留为日里程计算状态。 - 索引:主键已覆盖按 VIN 查询,不再额外维护
idx_vin;只保留按日期、协议日期查询需要的索引。 - 查询:
/api/stats/daily-metrics默认不执行COUNT(*),total表示本页返回条数;只有报表总页数等场景传includeTotal=true。 - 生产:RDS 已在上线前删除泛化
vehicle_daily_metric,改为专用vehicle_daily_mileage。
-
vehicle_locations不再接收临时身份兜底字段。- 原因:位置历史是正式车辆时间序列,只按
protocol + vin分表和查询。 - 无 VIN 的位置帧仍保留在
raw_frames.parsed_json,待 identity/binding 修复后可从 raw 重放补写。 - 查询:
/api/history/locations默认不执行大表COUNT(*),total表示本页返回条数;只有需要精确总数时传includeTotal=true。 - 生产:ECS TDengine
vehicle_locations已在上线前重建为protocol、vin两个 tag。
- 原因:位置历史是正式车辆时间序列,只按
-
历史 raw 查询默认不做精确总数。
- 原因:
raw_frames是最高写入量证据层,分页/排查通常只需要最新一页数据。 - 查询:
/api/history/raw-frames默认total为本页返回条数;只有导出前预估、后台管理需要总页数时才传includeTotal=true。
- 原因:
-
MySQL realtime 当前态表不保留代理主键和创建时间。
- 原因:
vehicle_realtime_snapshot、vehicle_realtime_location都是每协议每 VIN 一行的当前态投影,业务主键就是(protocol, vin)。 - 状态:schema 使用
PRIMARY KEY (protocol, vin),不保留自增id和created_at,对外只暴露最新updated_at。 - 写入:车牌从
vehicle_identity_binding反查时使用 VIN 级短 TTL 内存缓存,避免实时帧每条都打 MySQL;缓存不作为事实来源。 - 生产:项目上线前可直接重建这两张 MySQL 表,实时数据会从 Kafka 新消息继续投影。
- 原因:
-
identity 层只保留两张表。
vehicle_identity_binding:VIN 与 plate/phone/device_id 的映射,供 808 等协议反查 VIN。jt808_registration:808 注册、鉴权、最新活跃和 VIN 匹配状态。- 状态:
vehicle_identity_binding使用 VIN 主键;jt808_registration使用 phone 主键;两张表都不保留代理自增主键和created_at。 - 生产:RDS 已在上线前删除旧
vehicle_identity_binding_registration、vehicle_identity_bindings,gateway 启动会自动创建最小 schema。
字段提升规则
协议字段进入系统后分三层:
parsed_json:默认入口,保存完整结构化解析。- 核心列:只有跨协议稳定查询需要时才提升,例如经纬度、速度、SOC、总里程。
- 指标表:只有聚合口径稳定、产品需要分页/排序/报表时才持久化。
反例:
- 不因为某个协议字段存在就增加 MySQL 列。
- 不为了方便调试在 snapshot/location 放完整 JSON。
- 不为 telemetry field 配置服务提前复制一份字段表;字段配置服务应从 raw/Kafka 回放生成自己的结果。
删除或迁移顺序
- 先写测试证明旧 API 能从新主表读到同等结果。
- 改代码停止写重复表或重复列。
- 部署后观察 Kafka lag、writer 成功计数、API 结果。
- 查询生产库确认旧表不再新增。
- 做一次备份或导出。
- 删除旧表或旧列。
任何一步无法证明安全,就停在兼容状态,不直接删生产数据。