数据损坏这种事,在数据湖里并不少见。尤其用过 Hudi 的朋友应该都知道,它是一个基于 HDFS 或者 S3 这类存储上的数据湖框架,能支持流式写入、增量读取。但你有没有想过,万一哪一天表里的文件丢了一部分,或者某个 commit 的元数据对不上了,整张表会不会直接就废了?别慌,这篇文章就是来帮你把“体检”和“急救”的思路理顺的。我会用最接地气的方式,带着你从文件系统到 Hudi 的元数据,一层一层排查,再教你怎么做备份、怎么恢复,保证你读完能上手。
一、先说说数据损坏这件事
1.1 什么情况下会出问题
Hudi 表说白了就是一堆 Parquet 文件加上一些记录操作的时间线文件。任何一层出了问题都可能导致数据读不出来:
- 磁盘坏道、网络抖动,导致写入文件不完整。
- 不小心删掉了某个分区目录。
- 覆盖写的时候,旧文件被清理掉,但新文件还没写成功。
- HDFS 的 NameNode 损坏,或者 S3 上的桶被误清空。
- 多个任务同时写一张表,又没有配置好并发控制,把时间线搞乱了。
这些情况听着吓人,但只要我们有预案,大多数都能救回来。
1.2 你能从这篇文章里得到什么
我会先带你检查 Hudi 表的“健康状态”,包括文件系统层面和 Hudi 元数据层面。然后给你一套完整的备份和恢复方案,全部用 Shell 命令演示,不需要额外安装复杂的工具,只要你有 Hudi CLI 或者能跑 Spark 的环境就行。文中的示例都是同一个技术栈:Shell + Hudi CLI,方便你直接复制去试。
二、动手之前先摸清家底
2.1 Hudi 表到底长什么样
一张 Hudi 表在存储上通常有三个部分:
- 数据文件:一般是 Parquet 或者 HFile,放在表目录下的各个分区路径里。
- .hoodie 目录:这是 Hudi 的元数据目录,里面存放了 commit、clean、rollback 等操作的时间线文件。
- 可选元数据表:如果开启了
hoodie.metadata.enable=true,会有一个.hoodie/metadata子目录,用于加速查询和文件索引。
可以把 .hoodie 想象成表的“体检报告目录”,每次提交都会往里面写一个新文件,比如 20240801100000.commit 表示一次提交完成。如果这个目录里的信息丢了,表就找不到哪些文件是当前有效的了。
2.2 几种常见的“病危”信号
- 读表时报
FileNotFoundException,说明某个数据文件不存在。 - 跑批任务报
CommitNotFoundException,说明时间线文件缺失。 - 用 Spark 查询时返回的结果行数忽多忽少,说明提交信息对不上。
- 文件大小异常,比如很多 0 字节文件,大概率是写入中断。
三、文件系统层面怎么体检
3.1 检查文件是否存在
最直接的办法就是列出表目录下的所有文件。你可以在 HDFS 上执行 hdfs dfs -ls -R,或者在 S3 上执行 aws s3 ls --recursive。我们这里统一用命令行操作。
# 技术栈:Shell(文件系统命令)
# 先看看表目录整体结构,假设表放在 /data/hudi/ogs
hdfs dfs -ls -R /data/hudi/ogs
# 如果输出一堆目录和文件,说明路径还在
# 我们可以数一下文件数量,跟平时对比一下
hdfs dfs -count /data/hudi/ogs
# 如果发现某个分区目录不见了,比如 d=2024-08-01
hdfs dfs -ls /data/hudi/ogs/d=2024-08-01
如果分区目录没了,先别急着重新生成,看看有没有备份。没备份的话,只能从底层存储的快照去恢复。
3.2 用 fsck 检查文件一致性
HDFS 有自己的文件系统检测命令,叫 fsck。它能告诉你哪些 block 丢了,哪些副本不健康。对于 S3 来说,没有这个命令,但你可以用 aws s3api head-object 检查每个 key 是否存在且能正常读取。
# 技术栈:Shell(HDFS 文件系统检查)
# 对 Hudi 表所在的目录做 fsck,只检查不修复
hdfs fsck /data/hudi/ogs -files -blocks
# 如果看到类似 MISSING BLOCK 的信息,说明有文件损坏
# 这时候可以尝试用 hdfs fsck -delete 把损坏文件标记删除,
# 但注意删除前必须确认备份已存在,否则数据就真没了。
# 修复前建议先用 -list-corruptfileblocks 看下有哪些坏文件
hdfs fsck /data/hudi/ogs -list-corruptfileblocks | grep -i corrupt
fsck 的结果很多,你主要看 MISSING BLOCK 或 CORRUPT 字样。另外你也可以加上 -locations 参数查看 block 分布,判断是否因为节点故障导致副本数不够。
3.3 校验文件内容是否完整
文件存在不代表内容没坏。常见的做法是用 Parquet 工具读取文件页脚,或者用 Hudi CLI 的 validate 命令。这里我们先用 Shell 检查文件大小,再结合 Hudi CLI 做内容级校验。
# 技术栈:Shell(文件大小对比)
# 假设之前每次 commit 生成的 parquet 文件大概在 128MB 左右
# 现在发现有个文件只有 10KB,极可能是损坏的
hdfs dfs -ls /data/hudi/ogs/d=2024-08-01/*.parquet
# 还可以按目录统计总大小,跟备份记录对比
hdfs dfs -du -s -h /data/hudi/ogs/d=2024-08-01
如果文件大小明显异常,那就尝试用 parquet-tools 读取一下:
# 技术栈:Shell(parquet-tools 需要额外安装,这里演示命令)
# 读取 parquet 文件的 schema,能读出来说明头部没坏
parquet-tools schema /data/hudi/ogs/d=2024-08-01/part-00000-xxx.parquet
# 读取前 10 行数据,如果不报错说明基本完整
parquet-tools head -n 10 /data/hudi/ogs/d=2024-08-01/part-00000-xxx.parquet
四、Hudi 特有的表完整性检查
文件系统只是敲门砖,真正的重点在 Hudi 自己的元数据。我们要确保时间线是完整的,每个 commit 都有对应的数据文件。
4.1 看时间线
Hudi 的每个 commit / delta commit 都会在 .hoodie 目录下生成一个文件。你可以直接列出来看:
# 技术栈:Shell(hadoop fs 命令)
# 列出 .hoodie 目录下所有提交记录
hdfs dfs -ls /data/hudi/ogs/.hoodie
# 按时间排序,可以看到最近一次 commit 是什么时候
hdfs dfs -ls /data/hudi/ogs/.hoodie/*.commit | sort -k6,7
# 查看 commit 内容,这个文件是 JSON 格式的,有提交的写操作详情
hdfs dfs -cat /data/hudi/ogs/.hoodie/20240801100000.commit | head -20
如果发现时间线有空洞,比如某个时间点之前的 commit 都存在,但是中间有一个 .commit 文件丢失,那这之后的读取就会出问题。
4.2 用 Hudi CLI 做深度校验
Hudi CLI 是一个专门用来管理 Hudi 表的工具,可以校验表的一致性,列出损坏的 commit。安装好的话,直接执行 hudi-cli 进入交互模式,再执行命令。为了脚本化,我们也可以把命令写成一行。
# 技术栈:Shell(Hudi CLI)
# 进入 hudi-cli 后,连接目标表
hudi-cli -- table --table /data/hudi/ogs
# 在交互模式里执行如下命令(也可以写成脚本文件批量执行)
# 1. 查看整个表的 commit 时间线
commits show
# 2. 校验表元数据是否一致
metadata validate
# 3. 检查 file system view,它会比对元数据里的文件列表和实际存储的文件
file-system-view --table /data/hudi/ogs --partition-path d=2024-08-01
上面命令中,metadata validate 是比较重要的一步。它会读取 .hoodie/metadata 目录中的文件映射,和实际的文件系统做对比。如果发现某个文件在元数据中登记了,但实际不存在,就会报出来。
如果你不想进入交互模式,也可以用如下非交互方式:
# 技术栈:Shell(Hudi CLI 非交互模式)
# 将多条命令写入一个脚本文件,再通过管道传进去
cat > /tmp/hudi_check.txt <<'EOF'
connect --path /data/hudi/ogs
metadata validate
commits show
quit
EOF
hudi-cli < /tmp/hudi_check.txt
五、备份方案怎么做才靠谱
备份这件事,其实比恢复简单。关键是你要定期执行,并且备份完要验证。下面我给出一个适合大多数场景的 Shell 备份脚本,用 HDFS 的 distcp 把整张表复制到备份目录。
5.1 冷备份(表不变动时直接复制)
冷备份的意思就是先停掉所有写入任务,再把表目录完整复制到另一个目录。这样能拿到一个一致性快照。
# 技术栈:Shell(HDFS distcp)
#!/bin/bash
# 表名和备份根目录
TABLE_NAME="ogs"
BACKUP_BASE="/backup/hudi"
TAB_DIR="/data/hudi/${TABLE_NAME}"
BACKUP_DIR="${BACKUP_BASE}/${TABLE_NAME}_$(date +%Y%m%d%H%M%S)"
# 1. 确保备份根目录存在
hdfs dfs -mkdir -p "${BACKUP_BASE}"
# 2. 使用 distcp 做分布式复制,保留块信息和权限
hdfs distcp -update -delete "${TAB_DIR}" "${BACKUP_DIR}"
# 3. 输出备份完成的标记
echo "Backup done at ${BACKUP_DIR}" >> /var/log/hudi_backup.log
脚本里的 -update 表示增量复制新文件,-delete 表示删除源目录中已经不存在的文件。注意这两个选项不要随意动,否则备份可能越积越大或者留下旧文件。
5.2 增量备份(针对时间线文件)
如果表一直在写入,又不能停,那就做增量备份。你有两个思路:
- 定期只备份
.hoodie目录和最近一段时间的新数据文件。 - 用 Hudi CLI 把某个 commit 之后产生的文件列表导出,再复制这些文件。
# 技术栈:Shell(按 commit 时间备份)
#!/bin/bash
# 获取最近的 commit 时间
LAST_COMMIT=$(hdfs dfs -ls /data/hudi/ogs/.hoodie/*.commit | awk '{print $NF}' | sed 's/.*\///' | sort | tail -1)
echo "最近 commit: ${LAST_COMMIT}"
# 把该 commit 相关的数据文件列表保存下来
# 这里用 hudi-cli 的 commit verify... 实际我们可以简化,
# 直接基于文件时间戳找出最近一天生成的数据文件
hdfs dfs -ls -R /data/hudi/ogs \
| grep "2024-08-01" \
| awk '{print $NF}' \
> /tmp/recent_files.txt
# 用 distcp 复制这些文件到增量备份目录
hdfs dfs -mkdir -p /backup/hudi/ogs_incremental/20240801
while read -r file_path; do
hdfs dfs -cp "$file_path" "/backup/hudi/ogs_incremental/20240801/"
done < /tmp/recent_files.txt
注意这个增量脚本比较简单,实际生产环境建议用 hudi-cli 的 commit show 获取准确的 commit 文件列表,不要用文件时间戳,因为可能包含历史文件。
5.3 备份后验证
备份完一定要验证,否则真出事时发现备份是坏的,哭都来不及。验证步骤分两块:
- 目录结构完整:备份目录下
.hoodie存在,数据文件数量与源表一致。 - 能正常读出数据:用 Hudi CLI 连接备份目录,执行
commits show看能不能列出 commit。
# 技术栈:Shell(备份验证)
# 校验文件数量是否一致
source_count=$(hdfs dfs -count /data/hudi/ogs | awk '{print $2}')
backup_count=$(hdfs dfs -count /backup/hudi/ogs_20240801120000 | awk '{print $2}')
echo "源文件数: ${source_count}, 备份文件数: ${backup_count}"
if [ "$source_count" -ne "$backup_count" ]; then
echo "文件数量不一致,备份失败!"
exit 1
else
echo "文件数量一致,备份成功。"
fi
# 用 hudi-cli 连接备份表看时间线
hudi-cli -- table --table /backup/hudi/ogs_20240801120000 --action commits show
六、恢复操作实战
恢复是重头戏,我分几种情况讲。
6.1 从备份恢复
这是最幸福的场景,直接复制回去就行。
# 技术栈:Shell(HDFS distcp 恢复)
#!/bin/bash
# 源表已经坏了,先从最近的备份恢复
BACKUP_DIR="/backup/hudi/ogs_20240801120000"
TAB_DIR="/data/hudi/ogs"
# 备份目录必须存在,且里面有 .hoodie
if [ ! -d "$BACKUP_DIR/.hoodie" ]; then
echo "备份不完整,.hoodie 目录缺失!"
exit 1
fi
# 先把损坏的表目录移到另一个位置作为证据保存(别直接删除)
hdfs dfs -mv /data/hudi/ogs /data/hudi/hudi_corrupt_$(date +%Y%m%d%H%M%S)
# 然后从备份把表复制回原路径
hdfs dfs -cp -p "$BACKUP_DIR" "$TAB_DIR"
# 恢复完成后立刻验证
hudi-cli -- table --table "$TAB_DIR" --action metadata validate
-cp -p 会保留文件权限和时间戳,比较稳妥。
6.2 没有完整备份怎么办
如果只备份了数据文件,没有备份 .hoodie 目录,或者 .hoodie 目录损坏了,需要重建元数据。Hudi 提供了 repair 功能,可以把历史上所有 commit 重新整理。但前提是数据文件上自带 Hudi 的 _hoodie_commit_time 等字段,这样工具才能识别属于哪次提交。
# 技术栈:Shell(Hudi CLI repair)
# 连接表目录
hudi-cli -- table --table /data/hudi/ogs
# 在交互模式执行:
# repair --mode modify // 这个命令会重建文件系统视图
# 不过更常用的是 spark-sql 中的 refresh table,但这里我们保持 Shell 技术栈
实际上 Hudi CLI 里有 repair 子命令,可以修一些不一致。如果连 .hoodie 里的 commit 文件都没了,那恢复起来非常麻烦。一个可行的思路是:用 Spark 读取所有 parquet 文件,提取 _hoodie_commit_time 字段,重新生成一个最小的时间线。这属于“重建”而非“恢复”,这里我们只提一下,不展开。
6.3 恢复后的自检
恢复完不是能查询就算完,还要做几项自检。
# 技术栈:Shell(自检)
# 1. 检查目录文件数
hdfs dfs -count /data/hudi/ogs
# 2. 检查时间线最后一个 commit 是否存在
hdfs dfs -ls /data/hudi/ogs/.hoodie/*.commit | tail -3
# 3. 用 Hudi CLI 验证表能读取
hudi-cli -- table --table /data/hudi/ogs --action metadata validate | grep "No conflicts"
# 4. 找一个分区执行文件系统视图校验
hudi-cli -- table --table /data/hudi/ogs --action file-system-view --partition-path d=2024-08-01
如果这些命令都没有报错,说明表已经基本恢复。但保险起见,你可以再跑一次全量读,比如用 Spark SQL 的 select count(*) 对比恢复前后的业务记录数。
七、应用场景和优缺点
7.1 什么场景下需要这套方法
- 数据湖里有重要业务表,公司要求数据必须可恢复。
- 每天有大量流式写入,突然某天任务失败,导致部分分区文件被清理。
- 团队经常做表结构演进或数据重写,操作失误概率高。
- 底层存储(HDFS/S3)出现过故障,怀疑有数据块丢失。
7.2 数据损坏与恢复方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 冷备份 | 恢复简单,一致性高 | 需要停写,备份耗时长,占用空间大 |
| 增量备份 | 备份快,节省空间 | 恢复时依赖多个备份段,操作复杂 |
| Hudi 元数据修复 | 可以在没有完整备份时抢救数据 | 只能修复元数据,不能找回已删除的数据文件 |
| 底层存储快照 | 实现最安全,可以秒级恢复 | 需要存储系统支持,比如 HDFS snapshot 或 S3 版本控制 |
实际生产环境通常混合使用:平时做增量备份,每天一次冷备份,同时开启底层存储的快照功能。万一真出事,先用快照恢复,再用增量备份找到最新 commit。
7.3 注意事项
- 备份文件不要放在同一个节点或同一个故障域。HDFS 备份到另一个集群,S3 备份到另一个桶,或者不同 region。
- 恢复操作必须在确认备份完整之后再执行。想象一下,你第一次恢复失败,第二次又去恢复,结果把本来就完好的数据给覆盖了,那就悲剧了。
- 定期做“恢复演练”。很多团队备份了半年,从来没试过恢复,真出事时才发现备份目录里少了
.hoodie关键文件。 - Hudi 表写入时最好开启
hoodie.cleaner.policy的保留策略,别让 cleaner 在你还想保留历史版本的时候把文件清理掉。 - 如果是多写并发,一定要开启乐观锁或使用单写入者模式,否则时间线会上乱七八糟,那时候就不是备份能解决的问题了。
八、文章总结
数据损坏并不可怕,可怕的是没有检查手段,也没有恢复预案。通过这篇文章你应该掌握了几个核心要点:
- 文件系统层面用
fsck、du、ls这些命令做体检。 - Hudi 层面要重点检查
.hoodie时间线和元数据表,用hudi-cli的metadata validate做一致性比对。 - 备份方案要覆盖数据文件和元数据,缺一不可。
- 恢复操作要严谨,先备份坏表,再恢复,最后验证。
- 没有完美无损方案,定期演练才是保住数据的最佳防线。
以后遇到 Hudi 表报错,先别慌。按照这套“体检 → 备份 → 恢复 → 自检”的流程走,至少能帮你保住 95% 以上的数据。记住,数据湖的稳定性不是靠运气,而是靠一整套防患于未然的习惯。现在就可以拿一张测试表试试这些命令,等真派上用场的时候,你会感谢自己读过这篇文章。
Comments