使用IDEA Debug Spark源码

现在越来越多的人加入到了学习 Spark 源码的队伍中来。但是如果只是单纯阅读代码,不动手亲自去跟踪和调试,往往无法很连贯地学习和理解,经常会出现无法看清代码的上下文跳转,无法理解代码含义等问题。这篇文章为大家介绍如何在真实环境中远程调试 Spark 的源码,为大家能够深入理解 Spark 核心原理铺平道路。

环境

先介绍一下这篇文章的环境。

  1. 调试工具: IntelliJ IDEA CE + Scala 插件;

  2. Hadoop 版本: hadoop-cdh5.1.0 版本;

  3. Spark: spark-1.4.1 源码;

  4. Spark 的集群部署采用 yarn-cluster 的模式;

准备工作

关于 Hadoop 的安装部署和 Spark 的安装部署,我假定大家已经完成了。如果有困难,可以到网上搜索。环境部署好后,在 IDEA 中安装 Scala 插件,并下载 Spark 源码导入到 IDEA 中。

整体介绍

先通过下图来看下 Spark on Yarn 的整体流程及各部分介绍。本文是以 yarn-cluster 模式为前提进行介绍,对于 yarn-client 模式,下图内容会稍有变化。而 yarn-client 模式的调试方法其实大同小异,读者可根据本文内容自行探寻。

使用IDEA Debug Spark源码_第1张图片

图1: yarn-cluster 流程图

Spark on Yarn 的原理并非本篇文章的重点,这里就不做过多解释了。想深入了解更多细节的同学,可以后台留言给我们,后续我们可以根据需要安排更多文章进行讲解。

图中的实线代表的是 Spark 各模块的启动和执行流程的方向;虚线及虚线上的参数是我们今天重点介绍的东西。

由图中可以看出,Spark 的各执行模块,可以分为 3 个部分:

  1. 提交模块 spark-submit,我们称作 Client 端,在 Client 提交机上运行;

  2. Spark Driver 模块,我们称为 Driver 端,在 App Master 中运行;

  3. Executor 模块,我们称为 Executor 端,在 NodeManager 上的 Container 中运行;

一般网上介绍调试 Spark 源码的文章,只会告诉你如何调试提交模块的代码,而真正重要的 Drvier 程序以及 Executor 中运行的 Task 程序,很少有文章涉及。看完这篇文章后,相信你想调试哪个模块,都可以轻松办到!

调试方法

Client 端调试方法

正常提交 Spark 任务时,都是使用 spark-submit脚 本进行提交,该脚本内部执行时实际调用了 spark-class 脚本。spark-class 脚本是提交程序的必经入口 (spark-shell, pyspark-shell, sparkr-shell 等都会通过这个脚本执行),而这个脚本最终会使用下面这行命令返回的结果,来提交执行命令:

$RUNNER -cp $LAUNCH_CLASSPATH org.apache.spark.launcher.Main $@

在 spark-submit 脚本中,增加如下配置:

export JAVA_OPTS="${JAVA_OPTS} -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8440"

然后将前述命令修改为

$RUNNER -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main $JAVA_OPTS "$@"

即可(红色字为修改内容)。

Driver 端调试方法

yarn-cluster 模式下,Spark Driver 程序是运行在 ApplicationMaster 进程中的。所以对 Driver 进行调试,就转化为了对 App Master 进程的调试。

从 Client 端提交程序到执行 ApplicationMaster 程序的路径为:

    Client --提交申请--> ResourceManager --启动--> ApplicationMaster

而 ApplicationMaster 的启动参数是 Client 传递给 ResourceManager 的,控制 ApplicationMaster 的 JVM 参数为:spark.driver.extraJavaOptions。

所以调试模式的提交命令为:

spark-submit --conf spark.driver.extraJavaOptions='-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8441' --master yarn-cluster ./cuiyang_test.jar

使用这个参数的源码在 Client 端的 org.apache.spark.deploy.yarn.Client.scala 源文件中,如下图。

使用IDEA Debug Spark源码_第2张图片
图2: 从 Client 端设置 Driver 端的 JVM 参数

另外也可从源码中看到,对于 yarn-cluster 模式,spark.yarn.am.extraJavaOptions 参数是不管用的。

Executor 端调试方法

Executor 端有一个和 ApplicationMaster 端对应的控制 JVM 的参数,为 spark.executor.extraJavaOptions。这个参数也由 Client 端提交程序时,通过命令行参数传入。

所以提交命令为:

spark-submit --conf spark.executor.extraJavaOptions="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8442" --master yarn-cluster ./cuiyang_test.jar

使用这个参数的源码在 Driver 端的 org.apache.spark.deploy.yarn.ExecutorRunnable.scala 源文件中,如下图。

使用IDEA Debug Spark源码_第3张图片
图3: Driver 端设置 Executor 端的 JVM 参数

调试参数的转换及传递流程

参数的传递看起来比较绕,不容易一目了然地看清楚。我画了一幅参数传递的流程图,可以让大家比较直观的理解整个的逻辑流程,如下图。

使用IDEA Debug Spark源码_第4张图片
图4: 参数的转化和传递

解释一下各种参数形式:

  1. Spark 参数:就是我们提交程序时,使用 --conf 来定义的参数;

  2. 系统属性:就是 Java 程序用 -D 来定义的参数;

  3. JVM 参数:就是 JVM 直接识别的,用 -X 来定义的参数;

spark.driver.extraJavaOptions 和 spark.executor.extraJavaOptions 参数由于书写较长,在图中分别用 "-d" 和 "-e" 来简写了。

spark-shell 的调试方法

spark-shell 虽然也是通过调用 spark-class 脚本来执行的,但是不能简单地使用文中提到的修改 spark-class 脚本的方法来实现调试。其实如下变换一下修改的内容,就可以实现 spark-shell 的调试,至于为什么这么修改的原因,这里就不做解释了,有兴趣的同学可以自行研究一下 org.apache.spark.launcher.Main 类的实现。

debug_flag="1"

CMD=()while IFS= read -d '' -r ARG; do    if [ ${ARG:0:3} = "org" ] && [ ${debug_flag} = "1" ]; then        CMD+=($JAVA_OPTS)        debug_flag="0"    fi    CMD+=("$ARG")done <<("$RUNNER" -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@")

使用 IDEA 远程连接调试

IDEA 远端调试代码的方法网上有很多资料,这里就不细说了。其实使用 Eclipse 也是完全可以的,不过个人认为 Eclipse 对 Scala 的支持不如 IDEA,而且 Scala 官方出的 Eclipse 导入 Spark 源码后,也慢的要命。

附一幅我调试 Driver 程序的截图吧:

使用IDEA Debug Spark源码_第5张图片
图5: 调试 Driver 程序的现场

总结

本文介绍了比较全的调试 Spark 各模块的方法。调试源码、跟踪源码的执行流程、查看程序当时的执行环境及变量,可以让我们更高效、透彻地了解 Spark 的框架及核心原理。希望能通过此文帮助到大家。

你可能感兴趣的:(spark)