凌晨两点半,监控电话把我从睡梦里拽起来。Flink 作业报错:xxx.parquet is not a Parquet file。第一眼看见这个报错我是有点懵的,因为作业白天还跑得好好的,日志没有任何改动,怎么突然就在读文件阶段崩了?这个错误在 Flink + Hudi 的实时数仓链路里不算罕见,但“不是 Parquet 文件”这几个字的背后,往往不是表面理解的文件格式不对那么简单。这篇文章就用这次生产事故当引子,完整还原从报错、取证、定位到修复的全过程,顺便把 Parquet 文件格式和 Hudi 文件布局的底层细节一次性讲透。内容适合正在用 Flink 读写 Hudi 表、或者经常跟 Parquet 文件打交道的数仓工程师和数据开发同学,看完之后你至少能少走两小时弯路。
1. 问题定性:这条报错背后到底发生了什么
1.1 报错信息与直接触发点
我当时截到的核心日志长这样:
text复制Caused by: java.io.IOException: xxx.parquet is not a Parquet file. expected magic number at tail [70, 97, 82, 49] but found [99, 110, 115, 52]
at org.apache.parquet.hadoop.ParquetFileReader.readFooter(ParquetFileReader.java:342)
at org.apache.parquet.hadoop.ParquetFileReader.initializeFromFile(ParquetFileReader.java:209)
at org.apache.parquet.hadoop.ParquetFileReader.<init>(ParquetFileReader.java:193)
at org.apache.parquet.hadoop.ParquetInputFormat.getRecordReader(ParquetInputFormat.java:273)
看到 expected magic number at tail,懂行的应该已经明白了个七八分。Parquet 文件不是一个普通的二进制文件,它的头部和尾部各有一段固定的 4 字节魔数 PAR1,也就是十六进制的 50 41 52 31。任何读 Parquet 的引擎,包括 Flink 底层的 ParquetInputFormat,拿到文件后第一步就是校验文件头尾的魔法数字。头部不对,直接报 not a Parquet file;头部对但尾部不对,就报 expected magic number at tail。
这个报错的直接触发点非常简单粗暴:目标文件缺失了 Parquet 文件应该有的结构标识。但“缺结构”和“文件坏”之间不能划等号,我们需要找到的答案是,为什么这个文件长成了不是 Parquet 的样子。常见可能性就那么几类:文件写了一半、文件被其他格式顶替、读端扫错了文件。接下来要做的不是改配置,而是取证。
1.2 扩展名不等于真实格式
很多第一次遇到这个报错的同事会陷入一个误区:文件后缀明明是 .parquet,为什么还报不是 Parquet 文件?这里我打个比方,扩展名就像身份证上的户籍信息,它理论上应该和真实身份一致,但架不住有人把信息填错,或者干脆用了假证。在分布式文件系统上,扩展名只是名字的一部分,HDFS 甚至不关心扩展名是什么,它只是按字节存数据。
在 Flink + Hudi 的链路里,这个问题尤其容易发生。Hudi 表底层的数据文件有一套自己的布局和命名规范,读 Hudi 表时,读端会拿到一组文件路径,再挨个打开。假如这个过程中混进来一个真实格式是 Avro 或者 JSON 的文件,而它的名字恰好叫 xxx.parquet,那读端一校验头部,就会报 is not a Parquet file。同理,一个写了一半就宕机的文件,也可能只有头部没有尾部,同样会被判定为“不是合法的 Parquet 文件”。所以看到这个报错,第一反应应该是:这个文件到底是“天生不是 parquet”,还是“本来要是 parquet 但没写完”。
1.3 提前划出三条排查主线
经验告诉我,这个报错逃不出以下三条主线,后续所有动作都是围绕它们展开的。
- 文件没有写完整:Flink 作业在写数据过程中被 kill、节点宕机、HDFS 写入异常,都可能留下一个只有文件头的半截 parquet。这种文件本质上是“半成品”,读端校验失败是必然的。
- 文件被“狸猫换太子”:某个进程或人为操作,把一个非 Parquet 格式的文件(Avro、JSON、CSV 文本,甚至 0 字节空文件)硬生生命名成了
.parquet,然后放进了 Hudi 表目录。读端目录扫描到它,同样报错。 - 文件本身没问题,但读错了路径:文件确实是合法的 parquet,但读端因为分区选择、时间线解析、索引错乱等原因,拿到的路径指向了一个不属于当前表的文件。这种情况也要排查。
先明确这三种可能,再去动手,效率会高很多。如果你在没有任何证据的情况下直接删文件、换参数,很可能把一个小问题放大成一个大事故。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
