深夜,突然被告警电话吵醒。虽然心里万马奔腾但你依然睡眼惺忪地爬起来打开了电脑,一看,例行的调度任务报错了。你一筹莫展,走投无路之下,忐忑不安地拨通了离线存储计算引擎值班同学的电话求助。
引擎Daddy发来一个消息,说异常是这样的:
1 | Error: java.io.IOException: java.io.IOException: java.lang.ClassCastException: org.apache.hadoop.io.IntWritable cannot be cast to org.apache.hadoop.io.Text |
然后,跟你说:“你排查下上游表是不是有字段变更?”
是的你没有听错,是不是很熟悉?是不是经常听到引擎值班同学帮你排查问题时跟你说:
“你排查下上游表是不是有字段变更?”
天啦撸,我哪知道有没有变更… 你弱弱地说:“能帮我看看是哪个表哪个字段有变更吗?” 这个脚本里涉及到很多个表。
此时,善良靠谱的引擎小哥哥不忍拒绝你的请求(万一对面是个妹子呢),开始了排查…
最后发现,在一个隐秘的角落,那年,上游表Owner偷偷地修改了某个表的某个字段类型。
什么乱七八糟的,言归正传,我们说说当表字段类型发生修改触发ClassCastException异常时,如何定位到该修改的字段。
背景
Orc格式文件的元数据保存在文件中,当修改了MetaStore中hive表的字段类型之后,Spark引擎在读取该文件时会抛出ClassCastException。但往往我们很难准确定位到是哪个字段发生了修改。
以下是一个简单的复现demo:
1 | -- 测试数据准备 |
如果在发生此类异常时,日志中打印出对应的字段,这样会更直观排查出是因为修改元数据导致的异常,而不是脏数据引起的异常。这个需要Spark引擎侧做开发支持,这个问题得解。
Spark源码修复
通过跟踪异常栈发现,该异常是在org.apache.spark.sql.hive.HadoopTableReader#fillObject方法中抛出的。如果我们在该方法中捕获到ClassCastException异常时,将对应的字段信息输出到异常日志中,便可以很方便地快速定位是由于哪个字段的变化引起的异常。
源码修复前:
1 | var i = 0 |
源码修复后:
1 | var i = 0 |
修复后我们在日志中打印出了异常触发的字段名称,如下所示:
1 | 20/04/09 17:22:05 ERROR hive.HadoopTableReader: Exception thrown in field <c1> |
总结
hive查询正常的原因:hive引擎在查询时,会获取对应分区的元数据信息,因此不会出现类型异常;
spark查询异常的原因:spark在查询时,并没有获取对应分区的元数据信息,而是获取表的元数据信息(此时表的字段元数据和分区的字段元数据不同),因此出现了类型转换异常的错误。
那些年你偷偷改过的字段,我们悄悄地将它记录到了日志中。下次遇到ClassCastException异常,快从我们提供的新功能中去发现蛛丝马迹,探寻那隐秘的角落。
该优化修复,已经提交了pr,贡献给了社区 SPARK-32192。
本文链接: https://stefanxiepj.github.io/archives/62aea827.html
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!
![知识共享许可协议](https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png)