网站和一些书籍都有介绍SparkSQL(DataFrame)会根据相应的操作生成最终运行的语句。这里从一个简单的、低级的问题入手到最后通过查看生成的代码查找问题的根源,并简单介绍怎么来调试SparkSQL。
问题来源:
1 2 3 4 5 6 7 8 9 |
|
运行之后 compute 总是报 NullPointerException 异常。按RDD以及Scala的操作都是没法理解的,怎么就变成 Access(null,null,null)
了呢?后面尽管改成 df.flatMap(Access(_)).map(_.compute)
后运行正常了,但是还是想看看SparkSQL到底干了啥!!!
SparkSQL干了什么
Spark RDD是在 RDD#compute 中明确定义好了操作的。而SparkSQL的操作最终转换成了LogicalPlan,看不出它做了什么东东。
其实,与数据库SQL的explain看执行计划类似,SparkSQL也有explain的方法来查看程序的执行计划。(这里代码全部贴出来了,根据情况自己去掉注释啊)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
|
运行上面的代码,在console窗口输出了任务的执行计划:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
OK,看到执行计划了,那生成的代码长什么样呢?以及怎么调试这些生成的代码呢?
Hack 源码
在进行调试之前,先改一下代码重新编译下catalyst用于调试,并替换maven下面的spark-catalyst_2.11 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
|
SparkSQL生成代码用的是janino,官网文档有提供debugging的资料:http://janino-compiler.github.io/janino/#debugging 。简单说明下三处修改:
- 查看org.codehaus.janino.Scanner构造方法,如果配置了debugging以及optionalFileName==null就会把源码保存到临时文件。
- 一开始没想到要注释掉setClassName的,后面把CodeGenerator#doCompile拷贝出来慢慢和官网提供的例子对,就把setClassName换成setExtendedClass竟然成了弹出了源码页面。又看到下面就setExtendedClass就注释掉setClassName就ok了。
- 源代码里面的参数不能查看的,就是编译的时刻把这个选项去掉了。把debugVars设置为true。
运行调试
先做好调试准备工作:
- 在compute方法里面打一个断点然后调试运行
- 修改log4j日志级别: log4j.logger.org.apache.spark.sql.catalyst.expressions.codegen=DEBUG
- 把项目导入eclipse(IDEA弹不出源代码)
然后运行。点击Debug视图的GeneratedIterator,在弹出的代码视图点击查找源码按钮,再弹出的添加源代码对话框(Edit Source Lookup Path)添加路径target/generated-sources(注意这里要用绝对路径)!接下来就一步步的调就行了。
调试着生成的代码能更好的理解前面explain的执行计划。看到代码就好理解最开始的Access(null,null,null)了:对象到字段反序列化的问题。
–END