spark on yarn源碼解析

本文章,原創 若澤數據 ,禁止所有閱讀,轉載,分享及評論

spark on yarn 執行流程前置

構建Spark Application的運行環境(啟動SparkContext),SparkContext向資源管理器(可以是Standalone、Mesos或YARN)注冊并申請運行Executor資源;
資源管理器分配Executor資源并啟動StandaloneExecutorBackend,Executor運行情況將隨著心跳發送到資源管理器上;
SparkContext構建成DAG圖,將DAG圖分解成Stage,并把Taskset發送給Task Scheduler。Executor向SparkContext申請Task,Task Scheduler將Task發放給Executor運行同時SparkContext將應用程序代碼發放給Executor。
Task在Executor上運行,運行完畢釋放所有資源。

流程大概我們都清楚,但詳細的執行流程要從源碼里分析
當我們在開發完成一個appliction后,要用spark-submit提交到集群,通常都在提交到yarn上執行,執行的命令如下:

./bin/spark-submit \
  --master yarn-cluster \
  --num-executors 100 \
  --executor-memory 6G \
  --executor-cores 4 \
  --driver-memory 1G \
  --conf spark.default.parallelism=1000 \
  --conf spark.storage.memoryFraction=0.5 \
  --conf spark.shuffle.memoryFraction=0.3 \

spark提供了一個命令spark-submit,那么我們第一步就去看一下這個文件,發現里面只有這一行,說明實際上是去找另一個spark-class的文件,參數是SparkSubmit

exec "${SPARK_HOME}"/bin/spark-class org.apache.spark.deploy.SparkSubmit "$@"

在spark-class里,前面是一些Jdk,Spark assembly,還有環境變量的檢查與設置,
首先我們看這個文件:
首先檢查SPARK_HOME目錄若為空,剛設置SPARK_HOME目錄,接著加載load.spark-env.sh文件

if [ -z "${SPARK_HOME}" ]; then
  export SPARK_HOME="$(cd "`dirname "$0"`"/..; pwd)"
fi

. "${SPARK_HOME}"/bin/load-spark-env.sh

那么我們進入load-spark-env.sh后,我們可以看出這個文件做了下面幾件事:

檢查SPARK_HOME是否設定
檢查SPARK_ENV_LOADED,得到SPARK_HOME/conf,加載sparn-env.sh
檢查并設置SPARK_SCALA_VERSION

if [ -z "$SPARK_ENV_LOADED" ]; then
  export SPARK_ENV_LOADED=1

  # Returns the parent of the directory this script lives in.
  parent_dir="${SPARK_HOME}"

  user_conf_dir="${SPARK_CONF_DIR:-"$parent_dir"/conf}"

  if [ -f "${user_conf_dir}/spark-env.sh" ]; then
    # Promote all variable declarations to environment (exported) variables
    set -a
    . "${user_conf_dir}/spark-env.sh"
    set +a
  fi
fi
if [ -z "$SPARK_SCALA_VERSION" ]; then

  ASSEMBLY_DIR2="${SPARK_HOME}/assembly/target/scala-2.11"
  ASSEMBLY_DIR1="${SPARK_HOME}/assembly/target/scala-2.10"

  if [[ -d "$ASSEMBLY_DIR2" && -d "$ASSEMBLY_DIR1" ]]; then
    echo -e "Presence of build for both scala versions(SCALA 2.10 and SCALA 2.11) detected." 1>&2
    echo -e 'Either clean one of them or, export SPARK_SCALA_VERSION=2.11 in spark-env.sh.' 1>&2
    exit 1
  fi

  if [ -d "$ASSEMBLY_DIR2" ]; then
    export SPARK_SCALA_VERSION="2.11"
  else
    export SPARK_SCALA_VERSION="2.10"
  fi
fi

返回到spark class文件,下面shell里可以看到,通過上面加載的spark-evn.sh,就可以檢查并找到JAVA_HOME,
接著,找到SPARK_ASSEMBLY_JAR及所在目錄,如下圖:

# Find the java binary
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
# Find assembly jar
SPARK_ASSEMBLY_JAR=
if [ -f "${SPARK_HOME}/RELEASE" ]; then
  ASSEMBLY_DIR="${SPARK_HOME}/lib"
else
  ASSEMBLY_DIR="${SPARK_HOME}/assembly/target/scala-$SPARK_SCALA_VERSION"
fi

其實核心就是下面這句了:


CMD=()
while IFS= read -d '' -r ARG; do
  CMD+=("$ARG")
done < <("$RUNNER" -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@")
exec "${CMD[@]}"

循環讀取ARG參數,加入到CMD中。然后執行了"$RUNNER" -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@ 這個是真正執行的第一個spark的類
org.apache.spark.launcher.Main,下面是對這個類的理解,其實說白了,就是把我們上面submit時的參數拼裝,成為一個可命令的commend

 * Usage: Main [class] [class args]
   * <p>
   * This CLI works in two different modes:
   * <ul>
   *   <li>"spark-submit": if <i>class</i> is "org.apache.spark.deploy.SparkSubmit", the
   *   {@link SparkLauncher} class is used to launch a Spark application.</li>
   *   <li>"spark-class": if another class is provided, an internal Spark class is run.</li>
   * </ul>

那么下面的exec "${CMD[@]}"就是去執行這個命令
寫到這里,我們的寫的application jar才開始執行了,上面的全是一些在執行前的準備工作。

spark on yarn執行流程

當上面的準備工作全部完成后,那么執行命令,就開始我們真正的spark執行流程了
首先在通過命令的參數,找到我們打包里開發好的主類(java反射),
回想一下,之前我們寫的spark應用的第一行是不是先構造一個sparkConf,接著通過sparkConfs構造一個非常重要的對象:SparkContext

val conf = new SparkConf()
val sc = new SparkContext(conf)

現在我們進入SparkContext這個類中,發現在在初始化這個類的時候,實際上做了好多事,比如檢查--master是哪種運行模式等,如下面的源碼,是不是在new SparkContext時,去調用createTaskScheduler方法

 // Create and start the scheduler
    val (sched, ts) = SparkContext.createTaskScheduler(this, master)
    _schedulerBackend = sched
    _taskScheduler = ts
    _dagScheduler = new DAGScheduler(this)
    _heartbeatReceiver.ask[Boolean](TaskSchedulerIsSet)

其中在createTaskScheduler做的最重要的兩件事:

1:構造出DAGScheduler
2:構造出TaskScheduler

如下面的源碼里分析:

 //當發現--master是Yarn-client模式后,那么是不是根據java的反射類加載,得到TaskSchedulerImpl對象
 //也就是上面的構造出TaskScheduler
 case "yarn-client" =>
        val scheduler = try {
          val clazz = Utils.classForName("org.apache.spark.scheduler.cluster.YarnScheduler")
          val cons = clazz.getConstructor(classOf[SparkContext])
          cons.newInstance(sc).asInstanceOf[TaskSchedulerImpl]
                } catch {
          case e: Exception => {
            throw new SparkException("YARN mode not available ?", e)
          }
        }
     //同樣,代碼真的很相似,YarnClientSchedulerBackend對象也創建出來了,也就是上面的構造出DAGScheduler
        val backend = try {
          val clazz =
            Utils.classForName("org.apache.spark.scheduler.cluster.YarnClientSchedulerBackend")
          val cons = clazz.getConstructor(classOf[TaskSchedulerImpl], classOf[SparkContext])
          cons.newInstance(scheduler, sc).asInstanceOf[CoarseGrainedSchedulerBackend]
        } catch {
          case e: Exception => {
            throw new SparkException("YARN mode not available ?", e)
          }
        }

        scheduler.initialize(backend)
        (backend, scheduler)

其實我們同相看下yarn-cluster的代碼,也非常的相似,只不過生成的
TaskScheduler的對象是YarnClusterScheduler
DAGScheduler的對象是YarnClusterSchedulerBackend
如圖:

case "yarn-standalone" | "yarn-cluster" =>
        if (master == "yarn-standalone") {
          logWarning(
            "\"yarn-standalone\" is deprecated as of Spark 1.0. Use \"yarn-cluster\" instead.")
        }
        val scheduler = try {
          val clazz = Utils.classForName("org.apache.spark.scheduler.cluster.YarnClusterScheduler")
          val cons = clazz.getConstructor(classOf[SparkContext])
          cons.newInstance(sc).asInstanceOf[TaskSchedulerImpl]

see tomorrow

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容