2. Azure存储与数据湖基础

在 Azure 批流数据管道中,存储层不是“把文件放进去”的终点,而是整条数据链路的起点与中间枢纽。批处理需要稳定、可重放的数据快照,流处理需要低延迟、可追加的落地目标,而两者又常常需要共享同一份数据。理解 Blob Storage 与 ADLS Gen2 的差异、层级命名空间的工作方式、分区策略如何影响吞吐,以及它们与批流引擎的集成方式,是设计可靠管道的前提。

Blob Storage 与 ADLS Gen2:同一底座,两种语义

Azure Blob Storage 是对象存储,数据以“容器/Blob”两级结构组织。容器是扁平命名空间,Blob 名称中的斜杠只是名称的一部分,并不代表真实目录。例如 raw/2024/06/01/events.parquet 在 Blob Storage 中仍是一个完整对象名,列出前缀时依赖服务端按名称前缀扫描。它适合存放非结构化或半结构化数据、备份、日志归档,也适合作为流式追加的落地目标。

ADLS Gen2 并不是另一种存储介质,而是在 Blob Storage 之上启用了层级命名空间(Hierarchical Namespace, HNS)。启用 HNS 后,目录成为真实对象,重命名或删除目录是元数据操作,而不是对每个对象逐个复制或删除。这对大数据分析非常关键:当批处理作业需要原子性地重命名临时目录、或对分区目录做整体清理时,ADLS Gen2 的目录语义能显著降低操作成本与失败风险。因此,数据湖分析场景通常优先选择 ADLS Gen2;纯对象存储、静态资源或简单归档则可用 Blob Storage。

Blob Storage 与 ADLS Gen2:命名空间语义与批流集成 Blob Storage:扁平命名空间 容器 container-raw raw/2024/06/01/events.parquet raw/2024/06/02/events.parquet raw/2024/06/03/events.parquet 斜杠仅为名称字符;目录重命名 = 逐对象复制/删除 ADLS Gen2:层级命名空间(HNS) 文件系统 fs-lake /raw/2024/06/01/events.parquet /raw/2024/06/02/events.parquet /raw/2024/06/03/events.parquet 目录为真实对象;重命名/删除为元数据操作 数据分区策略:路径即谓词 时间分区:/raw/year=2024/month=06/day=01/hour=08/ → 批处理按分区裁剪,流处理按小时滚动落盘 业务分区:/curated/region=cn-east/tenant=acme/ → 多租户隔离与按业务维度并行读取 与批流管道的集成方式 流处理 Event Hubs → 追加落盘 批处理 Databricks / Synapse 读取 统一存储层 ADLS Gen2 作为单一事实源 下游消费 Power BI / 数据服务

层级命名空间带来的实际差异

在 Blob Storage 中,若要把 raw/2024/06/01/ 下的 10 万个文件移动到 archive/2024/06/01/,客户端需要逐个复制再删除,耗时且可能中途失败。启用 HNS 后,对目录执行重命名是一次原子元数据操作,几乎瞬时完成。这一差异直接影响批处理作业的“提交”阶段:Spark 或 Databricks 写出结果时通常先写临时目录,再重命名为正式分区目录。在 ADLS Gen2 上,这个重命名是廉价的;在 Blob Storage 上,则可能成为瓶颈。因此,数据湖分析工作负载应优先使用 ADLS Gen2。

另一个差异是访问控制。ADLS Gen2 支持 POSIX 风格的 ACL,可在目录级别授予读写执行权限,并与 Azure RBAC 叠加。对于多团队共享的数据湖,这意味着可以按目录隔离权限,而不必为每个团队复制一份数据。Blob Storage 的访问控制主要依赖容器级 RBAC 和 SAS,粒度较粗。

数据分区策略:路径即谓词

分区策略的核心思想是让查询引擎能够通过路径前缀裁剪数据,而不是扫描全量。常见做法是按时间分区,例如 /raw/year=2024/month=06/day=01/hour=08/。批处理读取时,引擎只需列出匹配前缀的目录,跳过无关分区;流处理则按小时滚动写入新目录,避免单目录文件数无限增长。

分区键的选择需要权衡。分区过粗(如只按年),单分区数据量过大,并行度不足;分区过细(如按秒),会产生大量小文件,增加元数据压力和读取开销。经验上,单个分区文件大小控制在 128 MB 到 1 GB 之间较为合适。对于流式写入,可采用“小时分区 + 文件滚动”策略:每小时一个目录,目录内文件达到一定大小或时间间隔后关闭并新建文件。

除时间外,业务维度也可作为分区键,例如 /curated/region=cn-east/tenant=acme/。这适合多租户场景,但要注意分区基数。若租户数量达到数万,目录数量会爆炸,此时应改用分区内过滤或 Delta Lake 的 Z-Order 等布局优化,而不是继续增加分区层级。

与批流管道的集成方式

流处理侧,Event Hubs 或 Kafka 的数据通常由 Stream Analytics、Spark Structured Streaming 或 Flink 消费,并以追加方式写入 ADLS Gen2。写入时建议使用 Parquet 或 Delta 格式,并按时间分区。为保证一致性,可采用“至少一次”写入加幂等去重,或使用 Delta Lake 的事务日志实现精确一次语义。

批处理侧,Databricks、Synapse Spark 或 Azure Data Factory 直接读取 ADLS Gen2 上的分区目录。由于 HNS 支持目录级操作,批处理可以高效地列出分区、读取 Parquet 文件,并将结果写回新的分区目录。若批流需要共享同一份数据,建议将 ADLS Gen2 作为单一事实源:流处理写入 /raw,批处理读取 /raw 并写入 /curated,下游再从 /curated 消费。

实践建议:在 ADLS Gen2 上为流式写入启用“追加 Blob”或使用 Delta Lake 的 MERGE 与 OPTIMIZE,定期合并小文件;批处理读取时显式指定分区谓词,避免全目录扫描。

一个可参考的分区与集成配置示例

假设某物联网平台每秒产生约 5000 条设备事件,需要同时支持实时告警与离线分析。可采用如下结构:

存储账户:stiotlake(启用 HNS)
文件系统:fs-lake
路径规划:
  /raw/events/year=2024/month=06/day=01/hour=08/part-000.parquet
  /curated/device_summary/year=2024/month=06/day=01/
  /curated/alerts/year=2024/month=06/day=01/

流处理:Event Hubs → Spark Structured Streaming
  触发间隔:1 分钟
  输出格式:Delta
  分区列:year, month, day, hour
  检查点:/checkpoints/stream_events

批处理:Databricks 每日 02:00 运行
  读取:/raw/events/year=2024/month=06/day=01/
  写入:/curated/device_summary/year=2024/month=06/day=01/
  优化:OPTIMIZE + Z-ORDER BY device_id

该示例中,流处理按小时分区写入,批处理按天读取并聚合。由于 ADLS Gen2 支持目录级重命名,批处理作业的临时目录提交不会成为瓶颈。若使用 Blob Storage,则需评估重命名开销,或改用 Delta Lake 的事务日志来避免目录重命名。

选择与判断依据

当工作负载以分析为主、需要目录级权限、频繁重命名或删除目录时,选择 ADLS Gen2。当工作负载是简单的对象读写、静态资源托管或成本敏感的归档时,Blob Storage 已足够。分区策略上,优先按时间分区,控制单分区文件大小,避免分区基数过大。集成方式上,让流处理负责低延迟追加,批处理负责批量转换与聚合,两者通过 ADLS Gen2 上的分区目录解耦,从而获得可重放、可扩展的批流管道。