3、数据存储与索引:分布式存储(HDFS、MinIO)、时序数据库(InfluxDB)、空间索引(R-Tree)与检索策略
数据存好了,怎么存得稳、查得快,是路采系统能不能用的关键。
说实话,我见过不少团队,采集车跑了一整天,回来发现数据写坏了,或者回放时卡成幻灯片。嗯,问题往往就出在存储和索引上。
3.1 分布式存储:HDFS 与 MinIO
路采数据有多大?一台车,64线激光雷达+6路摄像头,一小时轻松上百GB。单机硬盘根本扛不住。所以,分布式存储是必须的。
3.1.1 HDFS:适合大文件批处理
HDFS 是 Hadoop 生态的老将。它的设计理念很简单:文件切块(默认128MB),多副本(默认3副本),分散存储。
我个人的习惯:HDFS 适合存原始数据包(.pcap、.bag)。这些文件大、写一次读多次,正好对路。
dfs.replication:副本数。路采数据建议设2,省钱又安全。dfs.blocksize:块大小。激光雷达数据建议256MB,减少元数据压力。dfs.namenode.name.dir:NameNode 元数据路径。一定要配多个磁盘,否则 NameNode 挂了整个集群就废了。
balancer 定时任务,再没出过问题。
3.1.2 MinIO:兼容 S3 的轻量方案
如果你觉得 HDFS 太重,MinIO 是个好选择。它兼容 AWS S3 接口,部署简单,性能也不错。
为什么我推荐 MinIO?
- 单机就能跑,适合小团队或边缘节点。
- 支持纠删码(Erasure Coding),比副本更省空间。
- 有 Web 管理界面,方便查看数据。
我在项目中常用 MinIO 存中间处理结果,比如去噪后的点云、压缩后的图像。这些数据访问频繁,MinIO 的 S3 接口让集成变得很简单。
项目名_传感器类型_日期 的格式,比如 projectA_lidar_20241001。这样后续检索时一目了然。
3.2 时序数据库:InfluxDB
路采数据里,除了点云和图像,还有大量时序信号:车速、GPS 坐标、IMU 姿态、CAN 总线数据。这些数据的特点是:高频写入、按时间查询、很少更新。
传统关系数据库(MySQL、PostgreSQL)处理这种场景效率很低。你想想看,每秒几千条写入,还要做时间范围查询,MySQL 的 B+ 树索引很快就扛不住了。
这时候,时序数据库就派上用场了。我个人最常用的是 InfluxDB。
3.2.1 InfluxDB 的核心概念
| 概念 | 说明 | 路采场景示例 |
|---|---|---|
| Measurement | 类似关系数据库的表 | vehicle_speed、gps_position |
| Tag | 可索引的元数据 | vehicle_id、sensor_type |
| Field | 实际数值 | speed_kmh、latitude |
| Timestamp | 时间戳(纳秒精度) | 2024-10-01T10:30:00.123456789Z |
写入示例:
# 写入车速数据
INSERT vehicle_speed,vehicle_id=car01,sensor_type=can speed_kmh=65.3 1727764200000000000
# 写入 GPS 数据
INSERT gps_position,vehicle_id=car01 latitude=39.9042,longitude=116.4074 1727764200000000000
3.2.2 查询优化策略
InfluxDB 的查询性能,很大程度上取决于 Tag 的设计。我见过有人把所有字段都设成 Tag,结果索引爆炸,写入变慢。
我的建议:
- Tag 只放基数低的字段,比如车辆ID、传感器类型。
- Field 放数值型数据,比如速度、加速度。
- 时间戳用纳秒精度,避免时间重叠导致数据覆盖。
3.3 空间索引:R-Tree 与检索策略
路采数据里,空间信息无处不在。比如:这帧点云是在哪个路口采集的? 这段视频覆盖了哪些路段?
传统的一维索引(B+ 树)处理不了这种二维空间查询。你需要的是空间索引。
3.3.1 R-Tree 原理
R-Tree 的核心思想是:用最小外接矩形(MBR)来近似表示空间对象。每个节点对应一个矩形区域,子节点的矩形完全包含在父节点内。
查询时,从根节点开始,判断查询区域与节点矩形是否相交。如果相交,就继续往下查;如果不相交,直接跳过。
说白了,这就是一种空间上的二分法。只不过二分的是二维空间,而不是一维数值。
3.3.2 在路采数据中的应用
我常用的做法是:
- 把每段路采数据的地理范围(经纬度边界)存成 MBR。
- 用 R-Tree 索引这些 MBR。
- 查询时,输入一个地理区域(比如某个路口方圆 500 米),R-Tree 快速返回所有相关的数据段。
代码示例(Python + RTree 库):
import rtree
# 创建 R-Tree 索引
idx = rtree.index.Index()
# 插入数据段:id=0,MBR=(经度最小, 纬度最小, 经度最大, 纬度最大)
idx.insert(0, (116.40, 39.90, 116.41, 39.91))
# 查询:某个路口方圆 500 米
query_area = (116.405, 39.905, 116.415, 39.915)
results = list(idx.intersection(query_area))
print(f"命中数据段 ID: {results}")
3.3.3 检索策略:时间+空间联合查询
实际场景中,你往往需要同时按时间和空间检索。比如:找出 2024年10月1日 上午10点到11点,经过北京国贸路段的激光雷达数据。
我的策略是:
- 先时间后空间:先用 InfluxDB 的时间索引缩小范围,再用 R-Tree 做空间过滤。
- 或者反过来:如果空间范围很小,先空间后时间。
- 混合索引:有些系统支持时空联合索引(如 Geomesa),但配置复杂,我一般不用。
WHERE time BETWEEN ... AND ... 不加索引,结果查一次要 10 分钟。嗯,后来加了时序索引和空间索引,查询时间降到了 1 秒以内。
3.4 总结
数据存储和索引,说白了就是用空间换时间,用结构换效率。
- 大文件用 HDFS 或 MinIO 存。
- 时序信号用 InfluxDB 管。
- 空间位置用 R-Tree 索引。
- 查询时,时间+空间联合检索,效率最高。
我个人觉得,这部分是路采系统里最容易被忽视的。很多人只关注算法,结果数据存得乱七八糟,回放时卡成狗。嗯,希望你能避开这些坑。