4. 批流一体存储选型

实验数据批流一体处理中,存储层往往是最先被低估、也最容易在后期成为瓶颈的部分。实验场景的数据形态非常特殊:既有传感器以毫秒级持续上报的时序流,也有实验结束后一次性归档的高分辨率图像、频谱文件或原始二进制包;既有需要低延迟读取的实时告警特征,也有需要长期保留、反复回溯的原始记录。因此,存储选型不是“选一个最快的”,而是要让不同形态的数据落到最合适的存储介质与访问接口上。

五类存储形态的定位差异

对象存储(如 S3、MinIO、OSS)以扁平命名空间和 HTTP 接口为核心,适合大文件、一次写入多次读取、归档与共享。它的优势是容量近乎无限、成本低、天然支持并发读取;代价是延迟较高(通常几十毫秒起),且不支持原地修改,只能整体覆盖写。分布式文件系统(如 HDFS、CephFS)提供 POSIX 语义和目录树,适合需要随机读写、多计算节点共享同一份文件的场景,但元数据服务在大量小文件下容易成为瓶颈。消息队列(如 Kafka、Pulsar、RabbitMQ)本质是流式传输通道,负责把数据从生产者搬到消费者,它擅长顺序追加、分区并行、按位点回放,但不适合长期存储和复杂查询。数据湖(如 Hive on HDFS、Iceberg、Hudi)在对象存储或文件系统之上增加表格式与元数据层,让原始文件具备表语义,支持批量扫描与增量读取。湖仓一体(如 Iceberg + Spark/Flink、Delta Lake、Hudi)进一步把事务、Schema 演进、时间旅行和流式写入统一到同一份数据上,是当前批流一体最主流的落地形态。

实验数据批流一体存储分层适配 数据源 传感器时序流 实验图像/频谱 原始二进制包 人工标注记录 消息队列 Kafka / Pulsar 分区顺序追加 按位点回放 低延迟缓冲 流批计算 Flink / Spark 窗口聚合 特征提取 异常检测 湖仓一体 Iceberg / Hudi 事务与快照 Schema 演进 时间旅行 底层存储介质 对象存储 S3 / MinIO / OSS 大文件·低成本·高并发 分布式文件系统 HDFS / CephFS POSIX·随机读写·共享 数据湖表格式 Hive / Iceberg 批量扫描·增量读取 湖仓一体存储 Iceberg + 对象存储 流批统一·ACID 实验数据适配建议 ● 高频时序流:Kafka 缓冲 → Flink 窗口聚合 → Iceberg 落湖,保留原始流用于回放 ● 大文件归档:对象存储直接写入,通过 Iceberg 元数据挂载为表,避免小文件问题 ● 需随机读写中间结果:分布式文件系统或本地 SSD 缓存,不作为长期主存储

从实验数据特征反推选型

假设一个典型实验场景:8 个振动传感器以 2 kHz 采样率持续采集,每个通道每秒产生约 4 KB 原始数据,8 通道合计约 32 KB/s,一天约 2.7 GB;同时每 10 分钟触发一次高分辨率频谱分析,单次输出约 50 MB 的二进制文件。实时侧要求 500 ms 内完成异常检测并告警,离线侧要求保留 180 天原始数据并支持按实验批次回溯。

这个场景中,实时告警链路适合用 Kafka 承接:按传感器 ID 分区,保证同一通道数据有序,Flink 消费后做滑动窗口统计,异常结果写入告警主题。原始数据则不应长期留在 Kafka 中,因为 Kafka 的存储成本随保留时间线性增长,且不支持按实验批次做复杂查询。合理的做法是 Flink 在消费的同时把原始数据以 Parquet 格式批量写入对象存储,并用 Iceberg 表管理这些文件。频谱文件体积大、写入频率低,直接以对象存储路径归档,Iceberg 表中只记录文件路径和元数据,查询时按需拉取。

如果实验需要频繁对中间结果做随机读写,例如迭代式参数拟合,那么把中间数据放在 HDFS 或 CephFS 上会比对象存储更合适,因为对象存储的“整文件覆盖写”语义会导致每次迭代都产生大量小文件。但这类中间数据生命周期短,实验结束后应转存到对象存储归档,避免占用分布式文件系统的高成本存储。

关键参数与判断依据

选型时至少需要关注四组参数。第一是延迟:实时链路端到端延迟要求低于 1 s 时,消息队列和内存计算是必选项;对象存储的 GET 延迟通常在 20~100 ms,适合秒级以上的批处理。第二是文件大小分布:如果单文件普遍小于 1 MB,对象存储和 HDFS 都会面临小文件问题,需要先做合并或使用 Iceberg 的 compaction。第三是写入模式:追加写适合 Kafka 和 Iceberg,覆盖写适合对象存储,随机写适合分布式文件系统。第四是查询模式:按时间范围扫描适合 Iceberg 分区裁剪,按主键点查适合 HBase 或 Redis,全文检索则需要额外引入搜索引擎。

一个实用的判断顺序是:先看数据是否需要“被回放”,需要则选消息队列;再看是否需要“被 SQL 查询”,需要则选数据湖或湖仓一体;最后看是否需要“被随机修改”,需要则选分布式文件系统。三者可以组合,但不要让同一份数据同时承担三种角色。

湖仓一体在实验场景中的落地要点

湖仓一体的核心价值是把流式写入和批量读取统一到同一份数据上。以 Iceberg 为例,Flink 写入时通过 FlinkIcebergSink 提交小文件,Iceberg 的元数据层记录 snapshot,Spark 或 Trino 读取时看到的是某个 snapshot 的一致性视图。实验场景中,这意味着实时写入的原始数据可以在几分钟后就被离线 SQL 查询到,而不需要等待 ETL 批处理窗口。

落地时需要注意三点。一是分区策略:实验数据天然按“实验批次 + 日期”组织,Iceberg 分区应使用 days(ts) 加 bucket(experiment_id, 16),避免单分区过大或过小。二是小文件治理:流式写入默认每 1 分钟提交一次,180 天会产生约 26 万个文件,必须配置定时 compaction,例如每 6 小时对最近分区做一次 rewrite。三是 Schema 演进:实验过程中传感器通道可能增减,Iceberg 支持 add column 和 rename,但删除列需要谨慎,因为历史 snapshot 仍会引用旧字段。

-- Iceberg 建表示例(实验原始数据)
CREATE TABLE lab.raw_sensor (
  ts            TIMESTAMP(3),
  experiment_id STRING,
  channel_id    INT,
  value         DOUBLE,
  quality       INT
) USING iceberg
PARTITIONED BY (days(ts), bucket(experiment_id, 16))
TBLPROPERTIES (
  'write.format.default' = 'parquet',
  'write.target-file-size-bytes' = '134217728',
  'commit.retry.num-retries' = '4'
);

这段配置中,目标文件大小设为 128 MB,是为了平衡小文件数量与单文件读取效率;重试次数设为 4,是为了应对对象存储偶发的 503 错误。实际执行后,可以通过 SELECT count(*) FROM lab.raw_sensor.snapshots 观察 snapshot 增长速率,如果每小时超过 60 个,说明提交间隔过短,需要调大 checkpoint 间隔或启用 compaction。

选型对比与常见误区

存储形态 典型延迟 写入模式 最适合的实验数据 主要限制
消息队列 毫秒级 顺序追加 实时传感器流、告警事件 不擅长长期存储与复杂查询
对象存储 20~100 ms 整文件覆盖 图像、频谱、归档包 不支持原地修改,小文件效率低
分布式文件系统 毫秒级 随机读写 迭代计算中间结果 元数据瓶颈,成本较高
数据湖表格式 秒级 批量/增量 历史回溯、批量分析 需要额外维护元数据与 compaction
湖仓一体 秒级 流批统一 实时入湖 + 离线查询 架构复杂,调优门槛高

最常见的误区是把 Kafka 当数据库用,设置 30 天保留期后直接在上面做 SQL 查询。Kafka 的查询能力仅限于按位点消费,无法做过滤、聚合和 join,长期保留还会导致磁盘成本超过对象存储。另一个误区是认为对象存储“慢”,实际上对象存储的吞吐可以通过并发读取线性扩展,真正的问题在于延迟和元数据操作,而不是带宽。理解这两点,才能在实验数据批流一体架构中做出合理的存储分层。