4、数据湖的存储:对象存储与文件格式的选择
聊到数据湖,很多人第一反应就是“便宜、能存海量数据”。但真正落地的时候,你会发现存储选型和文件格式的搭配,才是决定数据湖能不能跑起来的关键。我见过不少团队,S3 买了、Parquet 用了,结果查询慢得让人崩溃——问题往往出在细节上。
4.1 对象存储:数据湖的基石
说白了,数据湖的底层存储几乎都是对象存储。AWS S3、Azure ADLS、GCP GCS,这三家占了绝大多数市场。为什么是对象存储?因为它天生适合海量非结构化数据,扩展性几乎无限,而且成本比 HDFS 低得多。
核心优势:
- 弹性扩展:存储容量随需增长,不需要提前规划磁盘
- 高持久性:S3 号称 99.999999999% 的持久性,我实际用下来确实没丢过数据
- 按需付费:只付实际使用的存储和请求费用,不像 HDFS 要预留大量磁盘
- 与计算分离:Spark、Presto、Flink 都可以独立扩缩容,存储不动
但对象存储也有坑。我记得第一次把 Hadoop 作业直接迁移到 S3 时,发现性能惨不忍睹。原因很简单——对象存储的 最终一致性 和 高延迟的 LIST 操作,跟 HDFS 的强一致性完全不一样。
避坑指南:
我曾经在项目中遇到 S3 的 LIST 操作成为瓶颈。每天几百万个小文件,每次分区发现都要 LIST 一遍,耗时从几秒变成几分钟。后来改用 Hive 分区元数据缓存 + 分区剪枝,才把查询时间降下来。
4.2 文件格式:选对才能跑得快
对象存储只是容器,真正决定数据怎么存、怎么读的,是文件格式。目前主流的有三种:Parquet、ORC、Avro。我个人的经验是:选格式之前,先想清楚你的场景是 OLAP 还是 OLTP。
| 特性 | Parquet | ORC | Avro |
|---|---|---|---|
| 存储类型 | 列式 | 列式 | 行式 |
| 压缩率 | 高(Snappy/Zstd) | 更高(ZLIB/Zstd) | 中等 |
| 读取性能 | 列投影快,适合分析 | 列投影快,适合分析 | 全行读取快,适合 ETL |
| Schema 演进 | 支持(有限) | 支持(有限) | 原生支持 |
| 适用引擎 | Spark、Presto、Hive | Hive、Spark(稍弱) | Kafka、Flink、Spark Streaming |
你想想看,如果你的数据湖主要跑 BI 查询,选 Avro 就是给自己挖坑。列式存储的优势在于只读需要的列,I/O 量能减少 80% 以上。我做过一个测试:同样 100GB 的数据,Parquet 做 COUNT 和 SUM 比 Avro 快 5-10 倍。
4.3 Parquet:我为什么推荐它作为默认选择
如果只能选一种格式,我会选 Parquet。原因有三:
- 生态最广:Spark、Presto、Hive、Impala、Drill 都原生支持,几乎没遇到过不兼容的情况
- 压缩效率高:配合 Snappy 或 Zstd,存储成本能降到 CSV 的 1/5 到 1/10
- 谓词下推:Parquet 的 min/max 统计信息可以跳过大量无关数据块
我的习惯:
在项目中,我通常把 Parquet 作为“黄金数据”的存储格式。原始数据用 Avro 或 JSON 入湖,清洗后转成 Parquet 供分析使用。这样既保留了原始记录的灵活性,又保证了查询性能。
但 Parquet 也不是万能的。它的 Schema 演进能力有限——比如删除列或重命名列,Parquet 不支持。如果你需要频繁修改数据结构,建议在写入层做一层抽象。
4.4 ORC:Hive 生态的利器
ORC 是 Hive 的亲儿子,在 Hive 场景下性能甚至比 Parquet 还好。我记得在某个 Hive 数仓项目中,把存储从 TextFile 换成 ORC 后,查询时间从 20 分钟降到了 3 分钟。
ORC 的优势在于:
- 更细粒度的索引:支持 stripe-level 和 row-group-level 的统计信息
- 内置压缩:ZLIB 压缩率比 Snappy 高 20-30%
- ACID 支持:Hive 的 ACID 事务只能用在 ORC 上
但如果你主要用 Spark,ORC 的支持就没那么好了。Spark 读 ORC 时,有些优化(比如向量化读取)不如 Parquet 成熟。所以我的建议是:Hive 为主选 ORC,Spark 为主选 Parquet。
4.5 Avro:流式数据的首选
Avro 是行式存储,适合写密集和全行读取的场景。比如 Kafka 消息、日志采集、ETL 中间结果。为什么?因为 Avro 的 Schema 是 JSON 格式,可以随数据一起存储,天然支持 Schema 演进。
我做过一个实时数仓项目,数据从 Kafka 通过 Flink 写入 HDFS,中间格式就是 Avro。原因很简单:
- Flink 对 Avro 的序列化/反序列化支持最好
- Avro 的 Schema 可以随时加字段,不影响下游
- 行式存储适合逐条写入,不需要缓存成批
注意:
Avro 不适合做分析查询。如果你把 Avro 直接暴露给 Presto 或 Spark SQL,查询性能会非常差。我一般会在 Avro 之上建一层 Parquet 的物化视图,或者用 Spark 定期把 Avro 转成 Parquet。
4.6 文件格式与对象存储的搭配策略
选好了格式,还得考虑怎么跟对象存储配合。这里有几个我踩过的坑:
- 文件大小:对象存储对大量小文件很不友好。S3 的 PUT/GET 请求是按次数收费的,100 万个 1KB 的文件比 1000 个 1GB 的文件贵得多。我建议每个文件至少 64MB 到 256MB。
- 分区策略:按时间分区是最常见的。但注意分区数不要太多,否则 LIST 操作会变慢。我一般控制每个分区下的文件数在 1000 以内。
- 压缩选择:Snappy 和 Zstd 是主流。Snappy 快但压缩率一般,Zstd 压缩率高但 CPU 开销大。我个人的习惯是:分析场景用 Zstd,流式场景用 Snappy。
4.7 实战建议:我的选型清单
最后,给你一个我实际项目中用的选型清单:
| 场景 | 推荐格式 | 压缩 | 文件大小 | 分区字段 |
|---|---|---|---|---|
| BI 报表查询 | Parquet | Zstd | 128-256MB | 日期 + 业务线 |
| Hive ETL | ORC | ZLIB | 64-128MB | 日期 + 来源 |
| 实时流写入 | Avro | Snappy | 32-64MB | 事件时间 |
| 日志归档 | Parquet | Zstd | 256-512MB | 日期 + 日志级别 |
| 机器学习特征 | Parquet | Snappy | 64-128MB | 日期 + 特征分组 |
最后说一句:
没有银弹。Parquet 再好,在流式场景下也是累赘。Avro 再灵活,做分析就是灾难。搞清楚你的数据怎么来、怎么用,再选格式。这个顺序不能反。