Spark集群启动过程分析

来源:互联网 发布:淘宝发布的宝贝不见了 编辑:程序博客网 时间:2024/05/21 20:21

  • 环境变量配置
    • 通过命令行参数配置
    • 通过spark-envsh配置
  • 集群的启动过程
    • Master的启动
      • 脚本的执行流程
      • Master的初始化
    • Worker的启动
      • 脚本的执行流程
      • Worker的初始化

环境变量配置

通过命令行参数配置

参数 用途 -h HOST, --host HOST Master或Workers的主机地址。 -p PORT, --port PORT Master或Worerks的端口号(默认是:7077,而Worker的端口号随机)。 --webui-port PORT WebUI界面的端口号(默认情况下,Master是8080,Worker是8081)。 -c CORES, --cores CORES 允许Spark Applications在机器上使用的总CPU内核数(默认是使用所有的内核),该配置仅限于Worker-m MEM, --memory MEM 允许Spark Applications在机器上使用的内存总量(默认是:1g),该配置仅限于Worker。每个Application使用的内存都使用其spark.executor.memory属性进行配置。 -d DIR, --work-dir DIR 用于临时空间和作业输出日志的目录(默认是:${SPARK_HOME}/work),该配置仅限于Worker--properties-file FILE 要加载的自定义Spark属性文件的路径(默认是:${SPARK_HOME}/conf/spark-defaults.conf)。

通过spark-env.sh配置

也可以通过在conf / spark-env.sh中设置环境变量来进一步配置集群,不过在该文件中配置的属性优先级没有命令行参数高。

  1. master环境变量
    • SPARK_MASTER_HOST:将Master绑定到特定的主机名或IP地址;
    • SPARK_MASTER_PORT:在指定的端口启动Master(默认:7077);
    • SPARK_MASTER_WEBUI_PORT:Master Web UI的端口(默认:8080);
    • SPARK_MASTER_OPTS:以“-Dx = y”(默认值:无)格式应用于Master的配置属性,具体支持的属性如下configuration.html#SPARK_MASTER_OPTS
  2. worker环境变量
    • SPARK_WORKER_CORES:允许Spark Applications在机器上使用的内核总数(默认:所有可用内核);
    • SPARK_WORKER_MEMORY:允许Spark Applications在机器上使用的内存总数(默认:所有1G)。请注意,每个Application使用的内存都使用其spark.executor.memory属性进行配置。
    • SPARK_WORKER_PORT:Worker的启用特定端口(默认值:随机分配);
    • SPARK_WORKER_WEBUI_PORT:Worker的WebUI端口号(默认值是8081);
    • SPARK_WORKER_DIR:在Worker中运行Application的目录,其中将包括日志和暂存空间(默认值:SPARK_HOME / work)。
    • SPARK_WORKER_OPTS:以“-Dx = y”(默认值:无)格式应用于Worker的配置属性,具体支持的属性如下configuration.html#SPARK_WORKER_OPTS
  3. 通用的环境变量
    • SPARK_LOCAL_DIRS:用于存储map输出文件和存储在磁盘上的RDD缓存。这应该在系统中的快速本地磁盘上,它也可以是不同磁盘上多个目录的逗号分隔列表。
    • SPARK_DAEMON_MEMORY:分配给Master和Workers守护进程的内存(默认是1G);
    • SPARK_DAEMON_JAVA_OPTS:Spark master和worker守护进程的JVM选项,格式为“-Dx = y”(默认值:无)。
    • SPARK_PUBLIC_DNS:Spark Worker和Master的公共DNS名称。

集群的启动过程

Master和Workers需要通过脚本来启动,可以使用start-all.sh一键启动Master和Workers,也可以使用start-master.shstart-slaves.sh分别启动MasterWorkers。总之,不管用什么脚本启动,它们之间的调用链如下图所示:

Master的启动

脚本的执行流程

  1. start-master.sh

    使用start-master.sh,在脚本执行的机器上启动Master实例,start-master.sh脚本的主要逻辑包括:

    • 确定了master的启动类org.apache.spark.master.Master
    • 通过执行spark-env.sh,获取到了master的运行环境变量,包括用户配置的主机地址端口号WebUI的端口号。如果没有配置,则使用默认配置。
    • 最后,将以上参数提交给 spark-daemon.sh,启动一个Master守护进程,具体的运行命令如下:
    "${SPARK_HOME}/sbin"/spark-daemon.sh start $CLASS 1 \ --host $SPARK_MASTER_HOST --port $SPARK_MASTER_PORT --webui-port $SPARK_MASTER_WEBUI_PORT \ $ORIGINAL_ARGS
  2. start-daemon.sh

    spark-daemon.sh为守护进程提供了一些管理操作,包括启动、停止、查看运行状态等,具体用法如下:

    # start|stop|submit|status : 表示option# spark-command : 表示启动命令,如org.apache.spark.master.Master# spark-instance-number:启动的实例数,一般为1,对于Slave可以在一个节点上启动多个Worker;# args...:表示提供给spark-command 的运行参数。$ spark-daemon.sh [--config <conf-dir>] (start|stop|submit|status) <spark-command> <spark-instance-number> <args...>

    spark-daemon.sh脚本执行的流程主要包括以下几个步骤:

    • 解析命令行参数,得到option、command和instance;

    • 初始化一些环境变量,包括日志目录、pid文件存储目录、调度的优先级、是否为daemon启动一个单独的进程,还是依附于执行脚本的bash所在的进程等;

    • 根据不同的option执行对应的操作,如果是stop或status,则根据pid文件执行对应的操作。如果是submit和start,接着会调用run_command方法:

      case $option in(submit) run_command submit "$@" ;;(start) run_command class "$@" ;;
      run_command() {mode="$1"shiftmkdir -p "$SPARK_PID_DIR"# 根据pid文件检查是否已经正在运行if [ -f "$pid" ]; then TARGET_ID="$(cat "$pid")" if [[ $(ps -p "$TARGET_ID" -o comm=) =~ "java" ]]; then   echo "$command running as process $TARGET_ID.  Stop it first."   exit 1 fifiif [ "$SPARK_MASTER" != "" ]; then echo rsync from "$SPARK_MASTER" rsync -a -e ssh --delete --exclude=.svn --exclude='logs/*' --exclude='contrib/hod/logs/*' "$SPARK_MASTER/" "${SPARK_HOME}"fi# 日志迁移,将旧的日志编号递增,保证每次启动后日志名称都为 **.Master-1-master0.out,而旧的日志名称则为 .Master-1-master0.out.1 .Master-1-master0.out.2spark_rotate_log "$log"echo "starting $command, logging to $log"# 根据不同的模式,确定执行命令所使用的脚本case "$mode" in (class)   execute_command nice -n "$SPARK_NICENESS" "${SPARK_HOME}"/bin/spark-class "$command" "$@"   ;; (submit)   execute_command nice -n "$SPARK_NICENESS" bash "${SPARK_HOME}"/bin/spark-submit --class "$command" "$@"   ;; (*)   echo "unknown mode: $mode"   exit 1   ;;esac}

      由于本次的执行模式mode是class,最终将执行命令交给了spark-class,来启动的可执行类。

  3. spark-class

    spark-class脚本会加载spark配置的环境变量信息spark-env.sh、获取类路径LAUNCH_CLASSPATH(默认是${SPARK_HOME}/jars)、选择执行此命令的执行器(这里是java),并调用build_command构建主类的启动命令。

    build_command() { "$RUNNER" -Xmx128m -cp "$LAUNCH_CLASSPATH" org.apache.spark.launcher.Main "$@" printf "%d\0" $? # 代表上一个命令执行是否成功的标志,如果执行成功则为0,否则不为0}

    可以看出,构建主类启动命令是通过org.apache.spark.launcher.Main类进行的,Main类作用是接收[class] [class args]格式的参数,并且提供了一下两种工作模式:

    • 如果使用的是spark-submit脚本提交,那么class一定为org.apache.spark.deploy.SparkSubmit,生成的启动命令是:

      /opt/jdk/1.8.0_151/bin/java -cp /opt/spark/2.2.0/conf/:/opt/spark/2.2.0/jars/* -Xmx 1g org.apache.spark.deploy.SparkSubmit --master spark://master0:7077 --deploy-mode cluster --class cn.vibrancy.spark.ComplexJob --name complexjob /home/deploy/complex-job.jar
    • 如果执行的是spark-class脚本,那么运行参数提供的class,生成的启动命令是:

      /opt/jdk/1.8.0_151/bin/java -cp /opt/spark/2.2.0/conf/:/opt/spark/2.2.0/jars/* -Xmx 1g org.apache.spark.master.Master -host master -p 7077 --webui-port 8080

    接着,将构建出的主class启动命令打印,bash可以使用read来获取打印的结果,并放到CMD中。

    // Main.java// 将生成的命令打印出,bash可以获取并收集到打印的结果List<String> bashCmd = prepareBashCommand(cmd, env);for (String c : bashCmd) { System.out.print(c); System.out.print('\0');}
    # spark-classset +o posixCMD=()while IFS= read -d '' -r ARG; do CMD+=("$ARG")done < <(build_command "$@")

    最后,执行CMD命令,这样就可以执行Master的main方法了。

    # spark-classCMD=("${CMD[@]:0:$LAST}")exec "${CMD[@]}"

Master的初始化

  1. 创建RpcEnv,并向RpcEnv实例中注册当前MasterEndpoint;
  2. 初始化Worker、Application和Driver相关的数据结构;
  3. 根据RECOVERY_MODE创建对应的Master元数据持久化引擎和领导选举机制,包括ZOOKEEPER、FILESYSTEM、CUSTOMER和NONE;
  4. 等待Worker的注册;

Worker的启动

脚本的执行流程

可以在执行start-slave.sh脚本所在机器上启动一个Worker,也可以在任意一个节点上执行start-slaves.sh脚本,则会启动slaves中记录的所有从节点上的Worker。

  1. start-slaves.sh

    在任意节点上启动集群上所有的Worker原理很简单,在start-slaves.sh中:

    # Launch the slaves"${SPARK_HOME}/sbin/slaves.sh" cd "${SPARK_HOME}" \; "${SPARK_HOME}/sbin/start-slave.sh" "spark://$SPARK_MASTER_HOST:$SPARK_MASTER_PORT"

    会调用slaves.sh脚本来启动集群中的Worker,其中cd后面的所有字符串都是传递给slaves.sh的参数。

  2. slaves.sh

    # slaves.shfor slave in `echo "$HOSTLIST"|sed  "s/#.*$//;/^$/d"`; do if [ -n "${SPARK_SSH_FOREGROUND}" ]; then   ssh $SPARK_SSH_OPTS "$slave" $"${@// /\\ }" \     2>&1 | sed "s/^/$slave: /" else   ssh $SPARK_SSH_OPTS "$slave" $"${@// /\\ }" \     2>&1 | sed "s/^/$slave: /" & fi if [ "$SPARK_SLAVE_SLEEP" != "" ]; then   sleep $SPARK_SLAVE_SLEEP fidone

    slaves.sh中,会解析slaves文件,获得当前集群中的所有从节点host,最后会执行上面的代码段,意思是使用ssh连接到某个slave中,然后执行cd "${SPARK_HOME}" \; "${SPARK_HOME}/sbin/start-slave.sh" "spark://$SPARK_MASTER_HOST:$SPARK_MASTER_PORT",这样就成功的在远程slave上启动了一个Worker实例。

  3. start-slave.sh

    该脚本中又会通过spark-daemon.sh脚本,来启动org.apache.spark.worker.Worker类,spark-daemon.sh上面已经详细介绍过了,只是启动类不同,这里不再复述了。

    "${SPARK_HOME}/sbin"/spark-daemon.sh start $CLASS $WORKER_NUM \    --webui-port "$WEBUI_PORT" $PORT_FLAG $PORT_NUM $MASTER "$@"

Worker的初始化

Worker的初始化比较简单,就是向masterRpcAddresses列表中的Master注册,如果Master是Active并且Worker没有注册过,那么Master会回复Worker消息RegisteredWorker,表示Worker注册成功;如果注册失败,那么回复RegisterWorkerFailed,Worker会退出。

Worker向Master注册的时候有重试机制,即在指定时间如果收不到Master的响应,那么Worker将会重新发送注册请求。目前重新次数至多为16次。为了避免所有的Worker同时刻向Master发送注册请求,每次重试的时间间隔是随机的,前6次的重试间隔在5~15s,而后10次的重试间隔在30~90秒。