【Hudi】 并发控制 — 多个作业同时写怎么办

22 阅读 1928 字 · 约 7 分钟

两个写入作业同时往一张表写数据,会不会把对方的数据覆盖掉?

数据模型

先讲 Hudi 的数据模型,因为并发控制的前提是知道每条记录长什么样。

不是所有 Hudi 表都要有主键和分区。 COW 表可以不指定 recordKey,只能做批量追加,每次写入都是新文件。MOR 表必须指定 recordKey,因为增量更新要靠它定位。分区也是可选的,不分区就是一张平表。但需要行级更新和并发控制的场景,recordKey 和 partitionPath 就是前提,下面讲的都基于这个假设。

Hudi 里每条有三条核心元字段:

(recordKey, partitionPath, commitTime)
  • recordKey: 业务主键。比如订单号 order_12345
  • partitionPath: 分区路径。比如 2024-01-01,决定这条记录落在哪个分区目录
  • commitTime: 这条记录属于哪个 commit。每次写入都有一个 commitTime,所有记录都带着它

这不是 Hudi 自己发明的。Hive/HDFS 里分区就是目录层面的事,Hudi 只是把 recordKey 和 commitTime 也提升到元数据层面,用它们来做索引和并发控制。

除了这三个,还有两个隐式的元字段:

  • _hoodie_record_key: 记录的主键
  • _hoodie_commit_time: 这条记录的 commit 时间
  • _hoodie_partition_path: 这条记录所在的分区路径

它们不是用户定义的,是 Hudi 自动写进 Parquet/Avro 的,查询时可以直接用——比如增量查询靠 _hoodie_commit_time 过滤。

乐观并发控制

Hudi 的并发控制是乐观锁。乐观锁的核心思想是:不预先加锁,先写,提交时检查有没有冲突。有冲突就失败,让上层重试。

怎么判断冲突

两个写作业同时提交,Hudi 怎么知道它们冲突了?靠 Timeline。

  1. 作业 A 开始写,记下当前最新的 commitTime(比如 2024-08-14-01:00:00
  2. 作业 A 写数据文件,写完了准备提交
  3. 提交时,作业 A 扫一眼 Timeline,看有没有新的 commit 出现
  4. 如果 Timeline 上没有新 commit,说明没其他人改过,A 的提交直接成功
  5. 如果 Timeline 上出现了作业 B 的 commit(2024-08-14-01:05:00),说明 B 在 A 写的过程中已经提交了。A 检查 B 改的文件和 A 改的文件有没有交集:
    • 没交集(改了不同的 file group)→ 没关系,A 照常提交
    • 有交集(改了同一个 file group)→ 冲突,A 提交失败,回退回数据,上层重试

关键在第 5 步:是否冲突取决于文件级别的交集,不是 commitTime 先后。两个作业改不同 file group 可以并行提交,只有改同一个文件时才需要排队。

为什么不用悲观锁

悲观锁是写之前先加排他锁,写完了再放。这能保证不冲突,但代价是:

  • 锁住期间其他写端全等着,吞吐量低
  • 在分布式系统里还要担心锁的过期、死锁、锁服务挂了这些事

Hudi 选乐观锁是因为它的场景决定了冲突概率不高:同一个 file group 同时被两个作业改是小概率事件,大多数时候互不干扰。乐观锁在冲突少的时候吞吐量远高于悲观锁。

LockProvider: 外部锁的配合

Timeline 本身能做乐观并发检查,但在某些场景下还不够。如果两个写端在同一时刻检查 Timeline 都发现没有新 commit,两个都会尝试提交——这时需要外部的互斥。

Hudi 引入 LockProvider 专门处理这个问题,支持几种实现:

  • ZookeeperBasedLockProvider: 用 ZooKeeper 临时节点做分布式锁。最常用,Spark/Flink 集群通常已经部署了 ZooKeeper
  • HiveMetastoreBasedLockProvider: 用 Hive Metastore 的锁机制。适合已经有 Hive 的场景
  • InProcessLockProvider: 单进程内的锁。只适合单机测试,生产不用

LockProvider 不是替代 Timeline 的,是互补的。Timeline 做冲突检测,LockProvider 保证"检查+提交"这两个动作之间没有竞态。

实际部署时选哪个取决于你环境里已有的基础设施。有 ZooKeeper 就用 ZooKeeper,有 Hive 就用 Hive。

小结

Hudi 的并发控制可以归纳为两句话:

  1. 多数时候不冲突:两个作业改不同的 file group,Timeline 上并行提交,互不干扰
  2. 冲突时有保障:改同一个 file group 时乐观锁检测到冲突,回退重试;LockProvider 兜底防止提交阶段的竞态

这不是数据库级的强事务,但就是这套"乐观锁 + 外部锁"的组合,让 Hudi 在不可变存储上做到了多作业并发安全。