Spark源碼初探-從spark-submit開始

深入瞭解spark的運行機制是爲了搭建集羣,編寫應用能達到更好的性能表現,甚至更加深入的瞭解還可以自己修改源碼,實現定製功能來適配自己的需求。僅僅通過一些spark架構和運行原理的描述來了解spark是遠遠不夠的,所以打算系統的看一下spark的源碼,以添加註釋的方式寫下自己的理解(儘量寫的詳細,很可能會顯得囉嗦)。spark源碼的版本爲2.0.1,是當前最新的版本,平時抽點時間慢慢看,能看多少算多少了。----序

下面從我們提交spark應用使用的spark-submit腳本開始spark的源碼閱讀之旅。如果有看官發現有錯誤的地方,還望不吝賜教。

file://bin/spark-submit

#檢查環境變量中是否有SPARK_HOME,如果不存在則設置環境變量
#dirname命令:輸出已經去除了尾部的"/"字符部分的名稱;如果名稱中不包含"/",顯示"."
#反引號在Linux中起着命令替換的作用。命令替換是指shell能夠將一個命令的標準輸出插在一個命令行中任何位置。
#$0表示當前bash文件絕對路徑名,下面一段的作用就是:如果SPARK_HOME不存在,則設置這個環境變量
if [ -z "${SPARK_HOME}" ]; then
  export SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi

# disable randomized hash for string in Python 3.3+
export PYTHONHASHSEED=0
#執行spark-class腳本,添加參數org.apache.spark.deploy.SparkSubmit,$@表示將spark-submit後面的參數也傳給spark-class
exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"

#可以看到spark-submit只檢查了環境變量,並沒有做更多的事情,接下來看看spark-class腳本是如何運作的

file://bin/spark-class

#再次檢查SPARK_HOME環境變量,如果不存在就export一個。
if [ -z "${SPARK_HOME}" ]; then
  export SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi

#調用了load-spark-env.sh,該腳本的作用是將conf/spark-env.sh中的變量加入環境變量。以及設置SPARK_SCALA_VERSION(scala版本號)。
#需要注意的是該腳本中會判斷名爲SPARK_ENV_LOADED的環境變量是否存在,如果不存在則export SPARK_ENV_LOADED=1並加載spark-env.sh中的環境變量;
. "${SPARK_HOME}"/bin/load-spark-env.sh

# 找到java命令,爲運行jar做準備,如果沒有設置JAVA_HOME會報錯誤,並退出。
if [ -n "${JAVA_HOME}" ]; then
  RUNNER="${JAVA_HOME}/bin/java"
else
  if [ `command -v java` ]; then
    RUNNER="java"
  else
  #如果java命令找不到或者不存在就會輸出錯誤信息 >&2 表示重定向到錯誤輸出
    echo "JAVA_HOME is not set" >&2
    exit 1
  fi
fi

# 找到spark的依賴jar
#RELEASE文件可以在官網下載的預編譯包的根目錄下看到,裏面記載了編譯說明和編譯參數,源碼編譯後會生成這個文件
#值得注意的是在2.0版本以後依賴jar都放到了${SPARK_HOME}/jars目錄下,而在1.X版本放在${SPARK_HOME}/lib目錄下
if [ -f "${SPARK_HOME}/RELEASE" ]; then
  SPARK_JARS_DIR="${SPARK_HOME}/jars"
else
#如果是用戶自己用mvn或sbt編譯的話,依賴也可能在{SPARK_HOME}/assembly/target/scala-$SPARK_SCALA_VERSION/jars目錄下
  SPARK_JARS_DIR="${SPARK_HOME}/assembly/target/scala-$SPARK_SCALA_VERSION/jars"
fi

#如果SPARK_JARS_DIR不是目錄或者$SPARK_TESTING$SPARK_SQL_TESTING這兩個變量(這兩個變量猜測可能是編譯源碼時設定的spark測試標誌)不存在,就報錯,退出。
#$SPARK_TESTING和$SPARK_SQL_TESTING不一定要存在,只需要找到SPARK_JARS_DIR就可以了,在spark2.X版本中依賴全部放在./jars目錄下。而1.X版本中是放在lib目錄下。
if [ ! -d "$SPARK_JARS_DIR" ] && [ -z "$SPARK_TESTING$SPARK_SQL_TESTING" ]; then
  echo "Failed to find Spark jars directory ($SPARK_JARS_DIR)." 1>&2
  echo "You need to build Spark with the target \"package\" before running this program." 1>&2
  exit 1
else
  LAUNCH_CLASSPATH="$SPARK_JARS_DIR/*"
fi

# Add the launcher build dir to the classpath if requested.如果需要,將啓動程序構建目錄添加到類路徑。
if [ -n "$SPARK_PREPEND_CLASSES" ]; then
  LAUNCH_CLASSPATH="${SPARK_HOME}/launcher/target/scala-$SPARK_SCALA_VERSION/classes:$LAUNCH_CLASSPATH"
fi

# For tests 測試
if [[ -n "$SPARK_TESTING" ]]; then
  unset YARN_CONF_DIR
  unset HADOOP_CONF_DIR
fi

上面的腳本內容就是Spark應用執行前的環境檢查等的準備工作。下面是核心部分,注意接下來會兩次調用Spark中的代碼。

# The launcher library will print arguments separated by a NULL character, to allow arguments with
# characters that would be otherwise interpreted by the shell. Read that in a while loop, populating
# an array that will be used to exec the final command.
#
# The exit code of the launcher is appended to the output, so the parent shell removes it from the
# command array and checks the value to see if the launcher succeeded.
# 這個方法就是進入jvm的入口,下面會調用
build_command() {
  #用-cp 將SPARK_HOME/jars目錄下的依賴全部添加進來。將運行org.apache.spark.launcher包下的Main類,並將原來spark-submit後面的參數全部傳過來。
  "$RUNNER" -Xmx128m -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@"
  printf "%d\0" $?
}
分析:在源碼中,這個org.apache.spark.luncher.Main類是Java編寫的,主要負責對運行參數進行預編譯、檢查,即在正式執行spark應用之前檢查參數是否有誤。

CMD=()#定義存放執行結果$?的數組
while IFS= read -d '' -r ARG; do
  CMD+=("$ARG")
done < <(build_command "$@")

COUNT=${#CMD[@]}
LAST=$((COUNT - 1))
LAUNCHER_EXIT_CODE=${CMD[$LAST]}

# Certain JVM failures result in errors being printed to stdout (instead of stderr), which causes
# the code that parses the output of the launcher to get confused. In those cases, check if the
# exit code is an integer, and if it's not, handle it as a special error case.
if ! [[ $LAUNCHER_EXIT_CODE =~ ^[0-9]+$ ]]; then
  echo "${CMD[@]}" | head -n-1 1>&2
  exit 1
fi

if [ $LAUNCHER_EXIT_CODE != 0 ]; then
  exit $LAUNCHER_EXIT_CODE
fi

CMD=("${CMD[@]:0:$LAST}")
exec "${CMD[@]}"

最後必須確定org.apache.spark.luncher.Main對參數的檢查結果是沒有問題,即exit code爲0,纔會指定存放到CMD中的命令。
最重要的是發現程序加載的入口類:org.apache.spark.deploy.SparkSubmit ,這個類就是Spark加載應用並運行的起點,下面將沿着這個類進入Spark源碼世界。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章