当我们在命令行中输入spark-shell的时候,会自动转为spark shell界面。这个界面中我们可以完成spark的操作。那么,这个过程是怎么进行的呢?
当我们在命令行中输入spark-shell的时候,调用的是spark/bin/spark-shell脚本。以下是spark-shell脚本中的部分代码:
function main() {
if $cygwin; then
stty -icanonmin 1 -echo > /dev/null 2>&1
export SPARK_SUBMIT_OPTS="$SPARK_SUBMIT_OPTS -Djline.terminal=unix"
"$FWDIR"/bin/spark-submit --class org.apache.spark.repl.Main "${SUBMISSION_OPTS[@]}" spark-shell "${APPLICATION_OPTS[@]}"
sttyicanon echo > /dev/null 2>&1
else
export SPARK_SUBMIT_OPTS
"$FWDIR"/bin/spark-submit --class org.apache.spark.repl.Main "${SUBMISSION_OPTS[@]}" spark-shell "${APPLICATION_OPTS[@]}"
fi
}
通过上方代码我们可以清楚的看见,main()方法调用了spark-submit脚本。我们前往spark-submit脚本去查看,发现spark-submit又调用了spark-class脚本。
exec "$SPARK_HOME"/bin/spark-class org.apache.spark.deploy.SparkSubmit "${ORIG_ARGS[@]}"
调用了spark-class脚本,并传入若干个参数,其中一个参数是org.apache.spark.deploy.SparkSubmit。spark-class脚本接收到传入的参数,并执行下述语句。
if [ -n "${JAVA_HOME}" ]; then
RUNNER="${JAVA_HOME}/bin/java"
else
if [ `command -v java` ]; then
RUNNER="java"
else
echo "JAVA_HOME is not set" >&2
exit 1
fi
fi
exec "$RUNNER" -cp "$CLASSPATH" $JAVA_OPTS "$@"
上述语句的最后一条表示启动java程序(org.apache.spark.deploy.SparkSubmit)。这个时候,我们应该知道Spark启动了以SparkSubmit为主类的jvm进程。
上述是脚本之间的调用,那么进入spark shell环境时候,像系统自动生成的conf,sc又是怎么得到的呢?
通过使用jvisualvm工具来dump main线程的信息,信息显示如下:
"main" - Thread t@1
java.lang.Thread.State: RUNNABLE
...
at org.apache.spark.repl.SparkILoop.process(SparkILoop.scala:916)
at org.apache.spark.repl.SparkILoop.process(SparkILoop.scala:1011)
...
at org.apache.spark.repl.Main.main(Main.scala)
...
at org.apache.spark.deploy.SparkSubmit$.main(SparkSubmit.scala:75)
at org.apache.spark.deploy.SparkSubmit.main(SparkSubmit.scala)
...
从main线程的栈信息中看出程序的调用顺序:SparkSubmit.main→repl.Main→SparkILoop.process。SparkILoop.process方法中会调用initializeSpark方法,initializeSpark实现如下:
def initializeSpark() {
intp.beQuietDuring {
command("""
@transient val sc = {
val _sc = org.apache.spark.repl.Main.interp.createSparkContext()
println("Spark context available as sc.")
_sc
}
""")
command("import org.apache.spark.SparkContext._")
}
}
从上述代码中,我们可以发现sc的创建是通过createSparkContext()方法完成的。然后,输出“Spark context available as sc.”,并将sc返回,同时导入SparkContext包下的所有类或对象。我们再看下createSparkContext()方法,看看他的内部是怎么实现的。
def createSparkContext(): SparkContext = {
val execUri = System.getenv("SPARK_EXECUTOR_URI")
val jars = SparkILoop.getAddedJars
val conf = new SparkConf()
.setMaster(getMaster())
.setAppName("Spark shell")
.setJars(jars)
.set("spark.repl.class.uri", intp.classServer.uri)
if (execUri != null) {
conf.set("spark.executor.uri", execUri)
}
sparkContext = new SparkContext(conf)
logInfo("Created spark context..")
sparkContext
}
在这个方法中,我们创建了conf对象,并对conf做了一些配置,比如说setMaster(),setAppName()等操作,然后,调用new SparkContext(conf)来创建sc。紧接着,打印出日志“INFO SparkILoop: Created spark context..”,并将sc返回。
通过上述的一系列操作,对spark shell环境的初始化就初步完成了。如下图所示: