最近在测试 Spark 2.3 的性能,其中踩的一些坑,调试源码的一些技巧,适当记录一下。本文是个人随笔,也许逻辑不够清晰,内容较多,敬请谅解。
首先 Spark 源码的基本断点调试,包括修改,仍然首推老大翟士丹的blog,其中有一些远程调试的操作。我们对于一个 Spark Application 是可以开启远程调试端口的,方法类似于蓝天的IT生涯里记录的。
对于 Driver 端的代码调试
- 例如 Spark SQL 的 Analyser 阶段的改进及调试。
- 直接在 Spark Application 开启远程调试端口,然后在 IntelliJ IDEA 中连接该端口,之后打断点调试就可以了。
对于 Executor 端的代码调试
- 用于 Executor 执行时报错。
- 对于 Executor 端的代码进行调试,看起来比较麻烦,直观看起来得知道在哪个 Executor 执行的代码,然后在这个 Executor 开远程调试端口。有没有更简单的办法呢?Local模式!!。Local模式下,执行任务的 Executor 与 Driver 在同一个JVM进程内。
对于 codegen 的代码调试
- 这次测试 Spark 2.3 发现一个报错:
Caused by: java.lang.ClassFormatError: org/apache/spark/sql/catalyst/expressions/GeneratedClass$GeneratedIteratorForCodegenStage3
at org.apache.spark.sql.catalyst.expressions.GeneratedClass.generate(Unknown Source)
at org.apache.spark.sql.execution.WholeStageCodegenExec$$anonfun$10.apply(WholeStageCodegenExec.scala:610)
at org.apache.spark.sql.execution.WholeStageCodegenExec$$anonfun$10.apply(WholeStageCodegenExec.scala:608)
at org.apache.spark.rdd.RDD$$anonfun$mapPartitionsWithIndex$1$$anonfun$apply$26.apply(RDD.scala:847)
at org.apache.spark.rdd.RDD$$anonfun$mapPartitionsWithIndex$1$$anonfun$apply$26.apply(RDD.scala:847)
at org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:38)
at org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:324)
at org.apache.spark.rdd.RDD.iterator(RDD.scala:288)
at org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:38)
at org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:324)
at org.apache.spark.rdd.RDD.iterator(RDD.scala:288)
at org.apache.spark.rdd.MapPartitionsRDD.compute(MapPartitionsRDD.scala:38)
at org.apache.spark.rdd.RDD.computeOrReadCheckpoint(RDD.scala:324)
at org.apache.spark.rdd.RDD.iterator(RDD.scala:288)
at org.apache.spark.scheduler.ResultTask.runTask(ResultTask.scala:87)
at org.apache.spark.scheduler.Task.run(Task.scala:109)
at org.apache.spark.executor.Executor$TaskRunner.run(Executor.scala:345)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
- 看起来是 codegen 生成的代码有问题,对 SQL 进行分析之后看起来是参数过多导致。之后的问题就是如何拿到 codegen 生成的代码。在此记录一下解决方案:
import org.apache.spark.sql.execution.debug._
import java.io._
val mysql = "select * from test" // or other SQL
val codeGenString = codegenString(sql(mysql).queryExecution.executedPlan)
val writer = new PrintWriter(new File("testCodeGen" ))
writer.write(codeGenString)
writer.close()
- 这样就可以获取 codegen 生成的代码。
- 最后发现这个是 Spark 2.3 的 bug,很遗憾发现的时候社区已经将这个 bug 修复一个月了,错失成为 Spark Contributor 的机会;右转 JIRA 查看详情。