现在越来越多的人加入到了学习 Spark 源码的队伍中来。但是如果只是单纯阅读代码,不动手亲自去跟踪和调试,往往无法很连贯地学习和理解,经常会出现无法看清代码的上下文跳转,无法理解代码含义等问题。这篇文章为大家介绍如何在真实环境中远程调试 Spark 的源码,为大家能够深入理解 Spark 核心原理铺平道路。
环境
先介绍一下这篇文章的环境。
-
调试工具: IntelliJ IDEA CE + Scala 插件;
-
Hadoop 版本: hadoop-cdh5.1.0 版本;
-
Spark: spark-1.4.1 源码;
-
Spark 的集群部署采用 yarn-cluster 的模式;
准备工作
关于 Hadoop 的安装部署和 Spark 的安装部署,我假定大家已经完成了。如果有困难,可以到网上搜索。环境部署好后,在 IDEA 中安装 Scala 插件,并下载 Spark 源码导入到 IDEA 中。
整体介绍
先通过下图来看下 Spark on Yarn 的整体流程及各部分介绍。本文是以 yarn-cluster 模式为前提进行介绍,对于 yarn-client 模式,下图内容会稍有变化。而 yarn-client 模式的调试方法其实大同小异,读者可根据本文内容自行探寻。
图1: yarn-cluster 流程图
Spark on Yarn 的原理并非本篇文章的重点,这里就不做过多解释了。想深入了解更多细节的同学,可以后台留言给我们,后续我们可以根据需要安排更多文章进行讲解。
图中的实线代表的是 Spark 各模块的启动和执行流程的方向;虚线及虚线上的参数是我们今天重点介绍的东西。
由图中可以看出,Spark 的各执行模块,可以分为 3 个部分:
-
提交模块 spark-submit,我们称作 Client 端,在 Client 提交机上运行;
-
Spark Driver 模块,我们称为 Driver 端,在 App Master 中运行;
-
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 源文件中,如下图。
图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 源文件中,如下图。
图3: Driver 端设置 Executor 端的 JVM 参数
调试参数的转换及传递流程
参数的传递看起来比较绕,不容易一目了然地看清楚。我画了一幅参数传递的流程图,可以让大家比较直观的理解整个的逻辑流程,如下图。
图4: 参数的转化和传递
解释一下各种参数形式:
-
Spark 参数:就是我们提交程序时,使用 --conf 来定义的参数;
-
系统属性:就是 Java 程序用 -D 来定义的参数;
-
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 程序的截图吧:
图5: 调试 Driver 程序的现场
总结
本文介绍了比较全的调试 Spark 各模块的方法。调试源码、跟踪源码的执行流程、查看程序当时的执行环境及变量,可以让我们更高效、透彻地了解 Spark 的框架及核心原理。希望能通过此文帮助到大家。