4. 数据摄取基础

在 Databricks 批流一体架构中,数据摄取是整条数据链路的入口。无论下游是做实时看板、离线报表还是特征工程,第一步都要把外部系统的数据可靠地引入 Delta Lake。Databricks 主要面对三类数据源:云对象存储(S3、ADLS Gen2、GCS)中的文件、关系型数据库(MySQL、PostgreSQL、SQL Server 等)中的表,以及消息队列(Kafka、Event Hubs、Kinesis)中的事件流。这三类源的读取语义差异很大:文件是"批量到达、可重复扫描"的,数据库是"有主键、可增量拉取"的,消息队列是"持续追加、按偏移量消费"的。理解这些差异,才能选对摄取工具。

三类数据源的读取方式与工具选择

云存储文件是最常见的摄取对象。Databricks 提供两条路径:COPY INTO 和 Auto Loader。COPY INTO 是一条 SQL 命令,适合按计划(如每小时)批量加载新文件,它通过记录已处理文件列表实现幂等,重复执行不会重复加载。Auto Loader 则基于 Spark Structured Streaming,使用 cloudFiles 数据源,能够以流式方式持续发现新文件,并借助 RocksDB 状态存储跟踪已见文件,支持目录列表和文件通知两种发现模式。

关系型数据库通常通过 JDBC 读取。批量场景可用 spark.read.jdbc() 配合分区列并行拉取;增量场景则依赖时间戳列或自增主键做水位线(watermark)过滤,例如每次只拉取 updated_at > 上次最大值 的行。消息队列使用 Kafka 连接器,以流式方式消费,通过 startingOffsets 和 checkpointLocation 保证断点续读。

下面的图展示了三类源进入 Delta Lake 的摄取路径与关键机制,注意 Auto Loader 与 COPY INTO 在"增量识别"上的不同实现。

三类数据源摄取路径与增量机制 云对象存储 S3 / ADLS Gen2 / GCS JSON / CSV / Parquet 新文件持续落盘 关系型数据库 MySQL / PostgreSQL JDBC 读取 时间戳 / 自增主键 消息队列 Kafka / Event Hubs 持续事件流 offset 消费 Auto Loader cloudFiles 流式发现 RocksDB 状态跟踪 COPY INTO SQL 批量加载 已处理文件列表幂等 JDBC / Kafka 连接器 水位线过滤 checkpoint 断点续读 Delta Lake 目标表 MERGE / append · schema evolution · 幂等写入

Auto Loader 增量加载实操

Auto Loader 的核心是 cloudFiles 数据源。下面示例从 ADLS Gen2 的 raw/events 目录持续读取 JSON 文件,写入 Bronze 层 Delta 表,并开启 schema 自动演进。

checkpoint = "abfss://meta@acct.dfs.core.windows.net/ckpt/events"

df = (spark.readStream
      .format("cloudFiles")
      .option("cloudFiles.format", "json")
      .option("cloudFiles.schemaLocation",
              "abfss://meta@acct.dfs.core.windows.net/schema/events")
      .option("cloudFiles.inferColumnTypes", "true")
      .load("abfss://raw@acct.dfs.core.windows.net/events/"))

(df.writeStream
   .option("checkpointLocation", checkpoint)
   .option("mergeSchema", "true")
   .trigger(availableNow=True)
   .toTable("bronze.events"))

关键参数说明:cloudFiles.schemaLocation 保存推断出的 schema 及演进历史;checkpointLocation 记录已处理文件与流进度;trigger(availableNow=True) 表示"处理完当前所有新文件后停止",适合按调度运行的准实时场景,而 trigger(processingTime="1 minute") 则用于常驻流。若目录文件量极大,可启用文件通知模式:.option("cloudFiles.useNotifications", "true"),由云存储事件驱动发现,避免全目录列举的开销。

COPY INTO 增量加载实操

COPY INTO 更适合"定时批量、文件量中等"的场景。它自动跳过已加载文件,语法简洁:

COPY INTO bronze.events
FROM 'abfss://raw@acct.dfs.core.windows.net/events/'
FILEFORMAT = JSON
FORMAT_OPTIONS ('mergeSchema' = 'true')
COPY_OPTIONS ('mergeSchema' = 'true');

执行后可通过 DESCRIBE HISTORY bronze.events 查看每次加载的 operationMetrics,其中 numFiles、numOutputRows 反映本次实际新增的文件数与行数。若某次执行 numFiles=0,说明没有新文件,属于正常幂等行为。需要重新加载时,可用 COPY INTO ... FORCE = true 强制重读,但要注意这会造成重复数据,通常只在修复错误后配合去重使用。

数据库与消息队列的增量读取

数据库增量拉取依赖水位线。假设源表有 updated_at 列,首次全量后记录最大时间戳,后续每次执行:

last_ts = spark.sql("SELECT max(updated_at) FROM bronze.orders").collect()[0][0]

jdbc_df = (spark.read.format("jdbc")
  .option("url", "jdbc:postgresql://db:5432/shop")
  .option("dbtable", f"(SELECT * FROM orders WHERE updated_at > '{last_ts}') t")
  .option("user", "etl").option("password", "***")
  .option("partitionColumn", "order_id")
  .option("lowerBound", 1).option("upperBound", 1000000)
  .option("numPartitions", 8)
  .load())

消息队列则用 Kafka 连接器,startingOffsets 设为 earliest 或 latest,checkpoint 保证重启后从上次 offset 继续。Kafka 消息的 value 通常是二进制,需 CAST(value AS STRING) 后再解析 JSON。

选择建议:文件源优先 Auto Loader(流式、schema 演进强);已有调度体系且文件量不大时 COPY INTO 更简单;数据库用 JDBC + 水位线;消息队列用 Kafka 连接器。三者最终都写入 Delta 表,统一由下游批流一体消费。
常见坑:Auto Loader 的 schemaLocation 与 checkpointLocation 必须分开且持久化,否则重启会重新推断 schema 或重复加载;COPY INTO 的源路径若包含子目录,需确认是否递归;JDBC 水位线若源表时间戳被回填修改,会漏数据,建议同时用主键做二次校验。