spark-core_22: SparkDeploySchedulerBackend,TaskSchedulerImpl的初始化源码分析

本文详细解析了Apache Spark在Standalone模式下的任务调度流程,包括SparkContext初始化时如何创建TaskSchedulerImpl和SparkDeploySchedulerBackend,以及它们之间的交互机制。

1,  在sparkContext初始时会执行

// Create and startthe scheduler,这个master是在sparkSubmit.Main方法得到:spark://luyl152:7077,luyl153:7077,luyl154:7077
//如果集群管理器是standalone模式:该方法返回(SparkDeploySchedulerBackend,TaskSchedulerImpl)

val (sched, ts)= SparkContext.createTaskScheduler(this, master)

2,当前集群管理器是Standalone,通过正则匹配,返回(SparkDeploySchedulerBackend,TaskSchedulerImpl)

/**
 * Create a task scheduler based on agiven master URL.
 * Return a 2-tuple of the schedulerbackend and the task scheduler.
  * 该方法是由sparkContext初始化时调用的,创建tastScheduler,并将SchedulerBackend也返回
  * master:  spark://luyl152:7077,luyl153:7077,luyl154:7077
  * 如果standalone模式:该方法返回(SparkDeploySchedulerBackend,TaskSchedulerImpl)
 */

private def createTaskScheduler(
   
sc: SparkContext,
   
master: String): (SchedulerBackend, TaskScheduler) = {
 
import SparkMasterRegex._

 
// When running locally, don't try to re-execute tasks onfailure.
 
val MAX_LOCAL_TASK_FAILURES= 1
 
master match{
。。。。
        //standalone
会匹配这个"""spark://(.*)""".r
    case SPARK_REGEX(sparkUrl)=>
  
   //master到sparkUrl变成luyl152:7077,luyl153:7077,luyl154:7077
     
/**
        * 它是这么实现的,pattern 就是"""spark://(.*)""".r ,这个s是匹配的串
        * val m = pattern matcher s
           if (runMatcher(m)) Some((1 tom.groupCount).toList map m.group)
        * 其中runMatcher(m)实际上就是调用了Matcher.matches(),groupCount()得到的值是正则的括号数加1,索引从0开始,m.group(0)就是整个串
        * m.group(1)就对应正则的第一个括号串。
        * 1 to 1 一个左右都闭合的,所以,SPARK_REGEX(sparkUrl),正则里面的一个参数对应第一组,第二个参数对应第二组
        */

     
val scheduler = new TaskSchedulerImpl(sc)
     
//masterUrls :Array("spark://luyl152:7077","spark://luyl153:7077","spark://luyl154:7077")
      val masterUrls= sparkUrl.split(",").map("spark://" + _)
     
//在standalone中调度类CoarseGrainedSchedulerBackend,由它的子类SparkDeploySchedulerBackend做调度
      val backend= new SparkDeploySchedulerBackend(scheduler, sc, masterUrls)
     
scheduler.initialize(backend)
      (backend, scheduler)

3,TaskSchedulerImpl初始化过程:

*
  * 通过SchedulerBackend它是调度系统的后端接口,不同的集群管理器有不同的调度类,如standalone:CoarseGrainedSchedulerBackend.
   每种调度下面都可以使用:TaskSchedulerImpl来执行具体的任务。 它处理常见的逻辑问题,例如确定各个工作的调度顺序,唤醒发起投机任务等。

  客户端应该首先调用initialize()和start(),然后通过runTasks()提交任务集。
  线程级别:SchedulerBackends和任务提交客户端可以从多个线程调用这个类,所以它需要在公共API方法中lock以维持其状态。 另外,当他们想在这里发送事件时,
   一些SchedulerBackends会自动进行同步,然后获取对我们的Lock,所以我们需要确保在我们Lock自己的同时,我们不会尝试Lock后端。不然肯定会死锁
 */

private[spark] class TaskSchedulerImpl(
   
val sc:SparkContext,
   
val maxTaskFailures: Int, //默认重试3次
    isLocal: Boolean= false)
 
extends TaskScheduler withLogging
{
 
//在放弃工作之前的单个任务失败次数。 应该大于或等于1.允许的重试次数=此值 - 1。
  def this(sc: SparkContext) = this(sc, sc.conf.getInt("spark.task.maxFailures", 4))

 
val conf= sc.conf

 
// How often to check for speculative tasks
  //默认使用100ms的时间周期去检测投机任务

  val SPECULATION_INTERVAL_MS= conf.getTimeAsMs("spark.speculation.interval", "100ms")
//单线程的调度线程池
  private val speculationScheduler =
   
ThreadUtils.newDaemonSingleThreadScheduledExecutor("task-scheduler-speculation")

 
// Threshold above which we warn user initial TaskSet maybe starved
  //超过此阈值,我们警告用户初始TaskSet可能会被饿死

  val STARVATION_TIMEOUT_MS= conf.getTimeAsMs("spark.starvation.timeout", "15s")

 
// CPUs to request per task.  每个task要求一个cpu
  val CPUS_PER_TASK= conf.getInt("spark.task.cpus", 1)

 
// TaskSetManagers are not thread safe, so any access toone should be synchronized  on thisclass.
  //TaskSetManagers
不是线程安全的,因此任何访问都应该在这个类上同步。
  private val taskSetsByStageIdAndAttempt = new HashMap[Int, HashMap[Int, TaskSetManager]]

 
private[scheduler]val taskIdToTaskSetManager = new HashMap[Long, TaskSetManager]
 
val taskIdToExecutorId= new HashMap[Long, String]

 
@volatile private var hasReceivedTask= false
 
@volatile private var hasLaunchedTask= false
 
private val
starvationTimer = new Timer(true)

  // Incrementing task IDs
  val nextTaskId= new AtomicLong(0)

 
// Number of tasks running on each executor
 
private val executorIdToTaskCount = new HashMap[String,Int]

 
// The set of executors we have on each host; this isused to compute hostsAlive, which
  // in turn is used to decide when we canattain data locality on a given host
  //我们在每个主机上拥有的executors集合; 这用于计算hostsAlive,这反过来又被,用来决定我们何时可以在给定主机上获得数据局部性

  protected val executorsByHost = new HashMap[String, HashSet[String]]

 
protected val hostsByRack = new HashMap[String, HashSet[String]]

 
protected val executorIdToHost = new HashMap[String, String]

 
// Listener object to pass upcalls into
 
var dagScheduler: DAGScheduler = null

 
var
backend:SchedulerBackend = null

 
/** mapOutputTracker : new MapOutputTrackerMaster(conf) | new MapOutputTrackerWorker(conf)
      MapOutputTrackerMaster属于driver,这里使用TimeStampedHashMap来跟踪 map的输出信息,也可以将旧信息进行清理(spark.cleaner.ttl)
    * MapOutputTrackerMaster和MapOutputTrackerWorker(运行在Executor中)都继承了MapOutputTracker
    * 1,MapOutputTrackerMaster是用来记录ShuffleMapTasks所需的map out的源
    *   a,shuffleReader读取shuffle文件之前就是去请求MapOutputTrackerMaster 要自己处理的数据 在哪里
    *   b,MapOutputTracker给它返回一批 MapOutputTrackerWorker的列表(地址,port等信息)
    * 2,MapOutputTrackerWorker是仅仅作为cache用来执行shuffle计算
    */

 
val mapOutputTracker= SparkEnv.get.mapOutputTracker

 
var schedulableBuilder: SchedulableBuilder = null
 
var
rootPool: Pool = null
 
// default scheduler is FIFO,调度以先进先出的方式进行
  private val schedulingModeConf = conf.get("spark.scheduler.mode", "FIFO")
 
val schedulingMode: SchedulingMode = try {
   
//枚举方法,通过字符串"FIFO"得到枚举值
    SchedulingMode.withName(schedulingModeConf.toUpperCase)
 
} catch {
   
case e:java.util.NoSuchElementException =>
     
throw new SparkException(s"Unrecognizedspark.scheduler.mode: $schedulingModeConf")
 
}

  // This is a var so that we can reset it for testingpurposes.
  //
运行反序列化并远程提取(如果需要)任务结果的线程池。
  private[spark]var taskResultGetter = new TaskResultGetter(sc.env, this)

4,接着createTaskScheduler()再开始初始化,SparkDeploySchedulerBackend,并将TaskSchedulerImpl,sparkContext还有格式化之后spark的Master和standby放进去

      val scheduler = new TaskSchedulerImpl(sc)
      //masterUrls :Array("spark://luyl152:7077","spark://luyl153:7077","spark://luyl154:7077")
      val masterUrls =sparkUrl.split(",").map("spark://" + _)
      //在standalone中调度类CoarseGrainedSchedulerBackend,由它的子类SparkDeploySchedulerBackend做调度
      val backend = newSparkDeploySchedulerBackend(scheduler, sc, masterUrls)
      scheduler.initialize(backend)
      (backend, scheduler)

a,查看SparkDeploySchedulerBackend初始化时:继承了CoarseGrainedSchedulerBackend,同时混入的AppClientListener并实现它的四个事件方法,然后初始化了一些成员变量

//构造方法就可以看到每种集群模式的调度类,使用TaskSchedulerImpl来执行task,
// 网友说是由SparkDeploySchedulerBackend给Task分配计算资源的,这个自己还有验证到
//masters : Array("spark://luyl152:7077","spark://luyl153:7077","spark://luyl154:7077")

private[spark] class SparkDeploySchedulerBackend(
   
scheduler: TaskSchedulerImpl,
   
sc: SparkContext,
   
masters: Array[String])
 
extends CoarseGrainedSchedulerBackend(scheduler, sc.env.rpcEnv)//可以看出它是粗粒度调度的子类
 
with AppClientListener
 
with Logging{
 
/**
    * AppClientListener 靠部署的客户端来回调各种事件的发生, 目前有四个事件:
      连接到群集,断开连接,被授予执行者,并删除执行者(无论是由于失败还是由于撤销)。
      用户不能阻塞在回调方法中
    */

 
private var client:AppClient = null  //Application与Master的接口
  private var stopping = false
 
private val
launcherBackend = new LauncherBackend(){
   
override protected def onStopRequest(): Unit = stop(SparkAppHandle.State.KILLED)
 
}

  @volatile var shutdownCallback: SparkDeploySchedulerBackend => Unit = _
 
@volatile private var appId: String = _

 
private val registrationBarrier = new Semaphore(0)
 
//请求应用程序的最大CPU内核数量,如果没有设置,会使用spark.deploy.defaultCores
  private val maxCores = conf.getOption("spark.cores.max").map(_.toInt)
 
private val totalExpectedCores = maxCores.getOrElse(0)

b,然后就是调用TaskSchedulerImpl.initialize(SparkDeploySchedulerBackend)

def initialize(backend: SchedulerBackend) {
  //SparkContext.createTaskScheduler()调用了该方法,同时会将SparkDeploySchedulerBackend注入进来,设置到TaskSchedulerImpl.backend成员上
  this.backend = backend
 
// temporarily set rootPool name to empty
  //一个可调度的实体,表示Pools或TaskSetManagers的集合

  rootPool = new Pool("", schedulingMode, 0, 0)
 
schedulableBuilder = {
   
schedulingMode match {
     
case SchedulingMode.FIFO =>
       
new FIFOSchedulableBuilder(rootPool)
     
case SchedulingMode.FAIR =>
       
new FairSchedulableBuilder(rootPool, conf)
   
}
  }
  //如果是FIFOSchedulableBuilder.buildPools()什么也没有干
  schedulableBuilder.buildPools()
}

最后createTaskScheduler()方法返回:元组(SparkDeploySchedulerBackend,TaskSchedulerImpl)


<think>我们正在使用Bitnami的Spark镜像版本3.4.1,用户询问是否需要修改配置文件以实现正常运行。根据引用[1]和引用[3],Bitnami的Spark镜像在3.4.0以上版本运行PySpark时可能存在一些问题(如报错`Error: pysparkdoes notsupport anyapplication options`),但这个问题与配置文件无关,而是镜像本身的问题。引用[1]中提到,用户通过构建官方镜像解决了这个问题。 然而,用户现在使用的是Bitnami镜像3.4.1,所以我们需要关注配置文件的修改是否必要。通常,Bitnami镜像已经预配置了合理的默认设置,但根据实际集群环境和应用需求,可能需要对某些配置进行调整。 ###一、BitnamiSpark镜像的默认配置BitnamiSpark镜像已经内置了以下关键配置(位于`/opt/bitnami/spark/conf`): 1. **`spark-defaults.conf`**:包含默认的Spark配置,如执行器内存、核心数等。 2. **`log4j.properties`**:日志配置。 3. **`spark-env.sh`**:环境变量设置(如`SPARK_MASTER_HOST`等)。 ###二、需要检查/修改的配置文件场景|配置文件|通常需要修改的场景| Bitnami镜像注意事项| |---------|-------------------|---------------------|| `spark-defaults.conf`|调整资源分配(如`spark.executor.memory`)、动态分配等|镜像已设置默认值,但需根据实际资源调整[^3] || `spark-env.sh`|设置Master主机地址、端口、Worker参数等 |在Docker环境中,通常通过环境变量覆盖(如`SPARK_MODE=master`)[^3] || `log4j.properties` |调整日志级别(如`log4j.rootCategory=ERROR`) |非必要不改动| ###三、Bitnami3.4.1镜像的特定问题根据引用[1]的GitHub issue,3.4.0+版本的Bitnami镜像运行PySpark时可能报错,但这与配置文件无关。解决方案是: 1. **改用官方镜像**(推荐),或2. **修改启动命令**(临时方案):```bash#错误方式(会触发上述问题)dockerexec-itspark-master pyspark--optionvalue#正确方式(避免传递应用选项)docker exec-itspark-master pyspark``` ###四、配置文件修改操作指南若仍需使用Bitnami镜像并调整配置:1.**挂载自定义配置文件**:```yaml#docker-compose.yml示例services:spark-worker:image:bitnami/spark:3.4.1volumes:- ./custom-spark-defaults.conf:/opt/bitnami/spark/conf/spark-defaults.conf``` 2. **通过环境变量覆盖**(仅限`spark-env.sh`中的参数):```yamlenvironment:- SPARK_WORKER_MEMORY=4g``` ###五、验证配置生效1.进入容器检查配置文件:```bashdockerexec -it spark-workercat /opt/bitnami/spark/conf/spark-defaults.conf``` 2.在SparkUI的`Environment`标签页查看实际生效配置。>**结论**:Bitnami镜像的默认配置在多数场景下可直接运行,但若遇到性能问题或特定需求,需调整资源参数。PySpark报错问题非配置导致,建议换用官方镜像[^1]。 ---§§相关问题§§1.如何为SparkonDocker配置集群节点间的SSH免密登录?2.Bitnami Spark镜像与官方Spark镜像在目录结构上有哪些差异?3.在Docker中运行Spark时,如何持久化历史事件日志(EventLog)?4.如何动态调整SparkExecutor的堆外内存(off-heapmemory)?[^1]:[Bitnami容器问题#38139](https://github.com/bitnami/containers/issues/38139)[^3]:[Win11 Docker快速部署spark](参考链接)</think>### Spark 配置文件修改指南(Bitnami 镜像 3.4.1 版本) #### 一、必须修改的核心配置 根据 Bitnami 镜像特性[^1][^3],以下配置文件需优先调整: 1. **`spark-defaults.conf`**(资源分配) ```properties # 调整内存分配(默认值常导致 OOM) spark.executor.memory 4g spark.driver.memory 2g spark.memory.offHeap.enabled true spark.memory.offHeap.size 1g # 解决 PySpark 兼容性问题(3.4.0+ 关键修复) spark.pyspark.python python3 spark.sql.execution.arrow.pyspark.enabled true ``` 2. **`spark-env.sh`**(集群设置) ```bash # 指定 Master 节点(Docker 部署必需) export SPARK_MASTER_HOST=spark-master # 解决端口冲突(Bitnami 默认端口可能被占用) export SPARK_MASTER_WEBUI_PORT=8081 export SPARK_WORKER_WEBUI_PORT=8082 ``` #### 二、可选优化配置 ```properties # 提升 Shuffle 性能 spark.shuffle.service.enabled true spark.sql.shuffle.partitions 200 # 解决日志警告(常见于 Bitnami 镜像) spark.eventLog.enabled false log4j.logger.org.apache.spark=ERROR ``` #### 三、Bitnami 特定配置 1. **镜像构建修复**(解决 PySpark 报错问题) 在 Dockerfile 中添加: ```dockerfile FROM bitnami/spark:3.4.1 # 修复应用选项解析问题 RUN sed -i 's/exec "$@"/exec "$@" --conf spark.driver.extraJavaOptions="-Dio.netty.tryReflectionSetAccessible=true"/' /opt/bitnami/scripts/spark/run.sh ``` 2. **Docker Compose 覆盖配置** ```yaml services: spark-worker: image: your_custom_image # 使用修复后的镜像 environment: - SPARK_WORKER_CORES=2 - SPARK_WORKER_MEMORY=8g volumes: - ./custom-conf:/opt/bitnami/spark/conf # 挂载自定义配置 ``` #### 四、验证配置生效 1. 在容器内检查配置合并: ```bash docker exec -it spark-worker cat /opt/bitnami/spark/conf/spark-defaults.conf ``` 2. 在 Spark UI 的 `Environment` 标签页确认配置生效: ``` http://localhost:8081/environment/ ``` > **关键说明**:Bitnami 镜像的默认配置在 3.4.0+ 版本存在 PySpark 兼容性问题[^1],必须通过配置覆盖或镜像重建解决。资源参数需根据集群硬件调整,否则易触发 Executor KILLED(143) 错误。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值