《深入理解Spark-核心思想与源码分析》读书笔记(1)

前两章

第一章主要是讲如何安装和配置spark,以及如何导入spark源码调试运行;第二章主要讲的是上次那本书《Spark快速大数据分析》的内容,科普一下spark的知识。

第三章 SparkContext的初始化

1. 概述

这章的主要内容就是讲解SparkContext的初始化。SparkContext就是所有Spark应用基础环境而配置Spark任务则是由SparkConf来完成。SparkContext的初始化一共有以下几步
1)创建 Spark 执行环境 SparkEnv;
2)创建 RDD 清理器 metadataCleaner;
3)创建并初始化 Spark UI;
4)Hadoop 相关配置及 Executor 环境变量的设置;
5)创建任务调度 TaskScheduler;
6)创建和启动 DAGScheduler;
7)TaskScheduler 的启动;
8)初始化块管理器 BlockManager(BlockManager 是存储体系的主要组件之一,将在第 4章介绍);
9)启动测量系统 MetricsSystem;
10)创建和启动 Executor 分配管理器 ExecutorAllocationManager;
11)ContextCleaner 的创建与启动;
12)Spark 环境更新;
13)创建 DAGSchedulerSource 和 BlockManagerSource;
14)将 SparkContext 标记为激活。
SparkContext构造器的参数就是SparkConf

    class SparkContext(config: SparkConf) extends Logging with ExecutorAllocationClient

2. 创建执行环境SparkEnv

SparkEnv包含众多和Executor(执行器)相关的对象。Executor就是Worker(工作节点)的一个进程。包括以下内容
1)创建安全管理器 SecurityManager;
2)创建基于 Akka 的分布式消息系统 ActorSystem;
3)创建 Map 任务输出跟踪器 mapOutputTracker;
4)实例化 ShuffleManager;
5)创建 ShuffleMemoryManager;
6)创建块传输服务 BlockTransferService;
7)创建 BlockManagerMaster;
8)创建块管理器 BlockManager;
9)创建广播管理器 BroadcastManager;
10)创建缓存管理器 CacheManager;
11)创建 HTTP 文件服务器 HttpFileServer;
12)创建测量系统 MetricsSystem;
13)创建 SparkEnv。

2.1 安全管理器SecurityManager
用来管理系统的口令

            //Set our own authenticator鉴别器 to properly专有的 negotiate协商
            //userpassword for HTTP connections. This is needed by the HTTP client
            //fetching from the HttpServer. Put here so its only set once.
              if (authOn) {
                Authenticator.setDefault(
                  new Authenticator() {
                override def getPasswordAuthentication(): PasswordAuthentication = {
                  var passAuth: PasswordAuthentication = null
                  val userInfo = getRequestingURL().getUserInfo()
                  if (userInfo != null) {
                    val  parts = userInfo.split(":", 2)
                    passAuth = new PasswordAuthentication(parts(0), parts(1).toCharArray())
                }
                  return passAuth
                }
                  }
                )
              }

2.2 基于Akka的分布式消息系统ActorSystem
ActorSystem是Akka提供的用于创建分布式消息通信系统的基础类。SparkEnv使用了AkkaUtils.createActorSystem方法完成,而createActorSystem实际上使用了doCreaterActorSystem来创造ActorSystem。

2.3 map任务输出跟踪器mapOutputTracker
跟踪map阶段任务的输出状态,便于reduce阶段任务获取地址和中间输出结果。所以这个mapOutputTracker就是用来管下面这些map和shuffle的,比如知道map输出block之类的,让reduce能找得到map的结果。
mapOutputTracker
下面的代码是创建MapOutputTrackerMasterActor的。map任务的状态是由Executor像持有的MapOutputTrackerMasterActor发送消息,讲map任务状态同步到mapOutputTracker的mapStatuses和cachedSerializedStatuses。

2.4 实例化ShuffleManager
这个就是用来管理上面那个图里的shuffle的

2.5 块传输服务BlockTransferService
获取远程节点上的block的,第四章讲

2.6 BlockManagerMaster介绍
这个负责对block进行管理,具体操作借助BlockManagerMasterActor,在初始化之后,创建BlockManager

2.7 创建广播管理器BroadcastManager
BroadcastManager是用于配置信息和序列化后的RDD、Job以及ShuffleDEpendency等信息在本地储存。

2.8 创建缓存管理器CacheManager
用于缓存RDDM某个分区计算后的中间结果,第四章解释。

2.9 HTTP文件服务器HttpFileServer
提供对文件的HTTP访问。开始时要初始化,创建文件服务器的根目录和临时目录。创建jar包及其他文件的文件目录。用start()方法启动,而这个方法用了doStart方法,doStart方法就是各种配置server对象,然后启动它。


def initialize() {
    baseDir = Utils.createTempDir(Utils.getLocalDir(conf), "httpd")
    fileDir = new File(baseDir, "files")
    jarDir = new File(baseDir, "jars")
    fileDir.mkdir()
    jarDir.mkdir()
    logInfo("HTTP File server directory is " + baseDir)
    httpServer = new HttpServer(conf, baseDir, securityManager, requestedPort, "HTTP file server")
    httpServer.start()
    serverUri = httpServer.uri
    logDebug("HTTP file server started at: " + serverUri)
}

2.10 创建测量系统MetricsSystem
MetricsSystem是Spark的测量系统,其作用是定期将数据指标从数据源(source)拉到数据汇(sink)。

2.11 创建SparkEnv
当所有基础组件准备好后,使用new Spark(……)来创建执行环境SparkEnv。

3. 创建metadataCleaner

metadataCleaner的功能是清楚过期的持久化RDD。

            /**
             * Runs a timer task to periodically定期地 clean up metadata (e.g. old files or hashtable entries)
             */
            private[spark] class MetadataCleaner(
                cleanerType: MetadataCleanerType.MetadataCleanerType,
                cleanupFunc: (Long) => Unit,
                conf: SparkConf)
              extends Logging
            {
              val name = cleanerType.toString

              private val delaySeconds = MetadataCleaner.getDelaySeconds(conf, cleanerType)
              private val periodSeconds = math.max(10, delaySeconds / 10)
              private val timer = new Timer(name + " cleanup timer", true)


              private val task = new TimerTask {
                override def run() {
                  try {
                  //就这,定期清理,用cleanupFunc
                    cleanupFunc(System.currentTimeMillis() - (delaySeconds * 1000))
                    logInfo("Ran metadata cleaner for " + name)
                  } catch {
                    case e: Exception => logError("Error running cleanup task for " + name, e)
                  }
                }
              }

              if (delaySeconds > 0) {
                logDebug(
                  "Starting metadata cleaner for " + name + " with delay of " + delaySeconds + " seconds " +
                  "and period of " + periodSeconds + " secs")
                timer.schedule(task, delaySeconds * 1000, periodSeconds * 1000)
              }

              def cancel() {
                timer.cancel()
              }
            }
而clean函数如下

            private[spark] def cleanup(cleanupTime:Long){                       persistentRdds.clearOldValues(cleanupTime)
            }

4. SparkUI详解

SparkUI
DAGScheduler是主要的产生各种Event的源头,它将各种SparkListenerEvent发送到listenerBus的时间队列中,然后BUS把事件和具体的sparklistener匹配,最终由sparkUI展示。

4.1 listenerBus详解
由三个部分组成。

  • 事件阻塞队列
  • 监听器数组
  • 事件匹配监听器线程

事件阻塞队列相当于排队上车的人,而线程就是公交车,不停地拉去排事件阻塞队列里的事件与监听器数组匹配,然后对事件进行操作。

private val EVENT_QUEUE_CAPACITY = 10000
  private val eventQueue = new LinkedBlockingQueue[SparkListenerEvent](EVENT_QUEUE_CAPACITY)
  private var queueFullErrorMessageLogged = false
  private var started = false
  // A counter that represents the number of events produced and consumed in the queue
  private val eventLock = new Semaphore(0)

  private val listenerThread = new Thread("SparkListenerBus") {
    setDaemon(true)
    override def run(): Unit = Utils.logUncaughtExceptions {
      while (true) {
        eventLock.acquire()
        // Atomically remove and process this event
        LiveListenerBus.this.synchronized {
          val event = eventQueue.poll
          if (event == SparkListenerShutdown) {
            // Get out of the while loop and shutdown the daemon thread
            return
          }
          Option(event).foreach(postToAll)
        }
      }
    }
  }

  def start() {
    if (started) {
      throw new IllegalStateException("Listener bus already started!")
    }
    listenerThread.start()
    started = true
  }
def post(event: SparkListenerEvent) {
    val eventAdded = eventQueue.offer(event)
    if (eventAdded) {
      eventLock.release()
    } else {
      logQueueFullErrorMessage()
    }
  }

  def listenerThreadIsAlive: Boolean = synchronized { listenerThread.isAlive }

  def queueIsEmpty: Boolean = synchronized { eventQueue.isEmpty }

  def stop() {
    if (!started) {
      throw new IllegalStateException("Attempted to stop a listener bus that has not yet started!")
    }
    post(SparkListenerShutdown)
    listenerThread.join()
  }

4.2 构造JobProgressListener
JobProgressListener用来统计Job的信息和状态。并在它们发生变化时进行反应。

4.3 SparkUI的创建与初始化
用create()方法加入一些listener,然后initialize()连接tab。再之后就是利用render()方法对页面进布局,实现显示。

5.Hadoop相关配置及Executor环境变量

获取Hadoop相关配置信息和对Executor的环境变量进行配置

6.创建任务调度器TaskScheduler

z这个就是用来负责任务的提交,并且请求集群管理器对任务调度。可以看做是任务调度的客户端。首先createTaskScheduler要创建TaskSchedulerImpl。
6.1 创建TaskSchedulerImpl

  • 从SparkConf中读取配置信息,包括每个任务分配的CPU数,调度模式(分为FAIR和FIFO两种,默认为FIFO)
  • 创建TaskResultGetter,它的作用是通过线程池对Worker上的Executor发送的Task的执行结果进行处理。
    调度方式最终落实到接口SchedulerBackend上实现

6.2 TaskSchedulerImpl的初始化

1)使 TaskSchedulerImpl 持有 LocalBackend 的引用。
2)创建 Pool,Pool 中缓存了调度队列、调度算法及 TaskSetManager 集合等信息。
3)创建 FIFOSchedulableBuilder,FIFOSchedulableBuilder 用来操作 Pool 中的调度队列。

7.创建和启动DAGScheduler

DAGScheduler 主要用于在任务正式交给 TaskSchedulerImpl 提交之前做一些准备工作,包 括: 创 建 Job, 将 DAG 中 的 RDD 划 分 到 不 同 的 Stage, 提 交 Stage, 等 等。
此节留以后详细说明

8.TashScheduler的启动

此节留以后详细说明

9.启动测量系统MetricsSystem

这个测量系统有三个概念。

  • Instance:指定了谁在使用测量系统
  • Source:指定了从哪里收集测量数据
  • Sink:指定了往哪里输出测量数据

启动过程包括 1)注册Source 2)注册Sinks 3)给Sinks增加Jetty的ServletContextHandler

  private def registerSources() {
    val instConfig = metricsConfig.getInstance(instance)
    val sourceConfigs = metricsConfig.subProperties(instConfig, MetricsSystem.SOURCE_REGEX)

    // Register all the sources related to instance
    sourceConfigs.foreach { kv =>
      val classPath = kv._2.getProperty("class")
      try {
        val source = Utils.classForName(classPath).newInstance()
        registerSource(source.asInstanceOf[Source])
      } catch {
        case e: Exception => logError("Source class " + classPath + " cannot be instantiated", e)
      }
    }
  }

给Sinks增加Jetty的ServletContextHandler主要是为了和SparkUI同步,用到了getServletHandler方法,最终生成处理 /metrics/json请求的ServletContextHandler。

10.启动和创建ExecutorAllocationManager

ExecutorAllocationManager用与对已经分配的Executor进行管理。

  def start(): Unit = {
    listenerBus.addListener(listener)

    val scheduleTask = new Runnable() {
      override def run(): Unit = {
        try {
          schedule()
        } catch {
          case ct: ControlThrowable =>
            throw ct
          case t: Throwable =>
            logWarning(s"Uncaught exception in thread ${Thread.currentThread().getName}", t)
        }
      }
    }
    executor.scheduleAtFixedRate(scheduleTask, 0, intervalMillis, TimeUnit.MILLISECONDS)
  }

其中start方法就将listener加入bus中,通过监听事件,动态添加、删除Executor。

11.ContextCleaner的创建和启动

用于清理那些超出应用范围的RDD、ShuffleDepency和Broadcast对象。

/** Keep cleaning RDD, shuffle, and broadcast state. */
  private def keepCleaning(): Unit = Utils.tryOrStopSparkContext(sc) {
    while (!stopped) {
      try {
        val reference = Option(referenceQueue.remove(ContextCleaner.REF_QUEUE_POLL_TIMEOUT))
          .map(_.asInstanceOf[CleanupTaskWeakReference])

// Synchronize here to avoid being interrupted on stop()
        synchronized {
          reference.map(_.task).foreach { task =>
            logDebug("Got cleaning task " + task)
            referenceBuffer -= reference.get
            task match {
              case CleanRDD(rddId) =>
                doCleanupRDD(rddId, blocking = blockOnCleanupTasks)
              case CleanShuffle(shuffleId) =>
                doCleanupShuffle(shuffleId, blocking = blockOnShuffleCleanupTasks)
              case CleanBroadcast(broadcastId) =>
                doCleanupBroadcast(broadcastId, blocking = blockOnCleanupTasks)
              case CleanAccum(accId) =>
                doCleanupAccum(accId, blocking = blockOnCleanupTasks)
              case CleanCheckpoint(rddId) =>
                doCleanCheckpoint(rddId)
            }
          }
        }
      } catch {
        case ie: InterruptedException if stopped => // ignore
        case e: Exception => logError("Error in cleaning thread", e)
      }
    }
  }

12.Spark环境更新

在SparkContext的初始化过程中,可能对其环境造成影响,所以需要更新环境,代码如下。
postEnviromentUpdate()
postApplicationStart()

13.创建DAGSchedulerSource和BlockManagerSource

private def initDriveMetrics(){
 SparkEnv.get.metricsSystem.registerSource(dagSchedulerSource)
 SparkEnv.get.metricsSystem.registerSource(blockManagerSource)
}
initDriveMetrics()

14.将SparkContext标记为激活

SparkContext.setActiveContext(this,allowMultipleContexts)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值