Spark2.2源码剖析——SparkContext初始化及Spark环境创建

  阅读指导:在《Spark2.2——SparkContext概述》一文中,曾经简单介绍了SparkEnv。本节内容将详细介绍SparkEnv的创建过程。

  SparkEnv是Spark的执行环境对象,其中包括与众多Executor执行相关的对象。Spark 对任务的计算都依托于 Executor 的能力,所有的 Executor 都有自己的 Spark 的执行环境 SparkEnv。有了 SparkEnv,就可以将数据存储在存储体系中;就能利用计算引擎对计算任务进行处理,就可以在节点间进行通信等。在local模式下Driver会创建Executor,local-cluster部署模式或者Standalone部署模式下Worker另起的CoarseGrainedExecutorBackend进程中也会创建Executor,所以SparkEnv存在于Driver或者CoarseGrainedExecutorBackend进程中。SparkContext中创建SparkEnv的实现见代码清单1。

代码清单1 创建SparkEnv

    private[spark] val listenerBus = new LiveListenerBus(this)
    _jobProgressListener = new JobProgressListener(_conf)
    listenerBus.addListener(jobProgressListener)
 
    _env = createSparkEnv(_conf, isLocal, listenerBus)
    SparkEnv.set(_env)

因为SparkEnv内的很多组件都将向LiveListenerBus的事件队列中投递事件,所以在代码清单1中首先创建LiveListenerBus和JobProgressListener,然后将JobProgressListener添加到LiveListenerBus的监听器列表中,最后将LiveListenerBus通过SparkEnv的构造器传递给SparkEnv以及SparkEnv内部的组件。JobProgressListener继承自SparkListener,LiveListenerBus和SparkListener的详细内容将在《Spark2.2之源码分析——事件总线》及其系列文章中介绍,此处不再赘述。

  createDriverEnv方法用于创建SparkEnv,根据createDriverEnv这个方法名,我们知道此方法将为Driver实例创建SparkEnv。调用createSparkEnv方法创建完SparkEnv后,SparkEnv实例的引用将通过SparkEnv的set方法设置到SparkEnv伴生对象的env属性中,类似于设置为Java类的静态属性,这将便于在任何需要SparkEnv的地方,通过伴生对象的get方法获取SparkEnv。

   createSparkEnv方法创建SparkEnv的代码如下:

  private[spark] def createSparkEnv(
      conf: SparkConf,
      isLocal: Boolean,
      listenerBus: LiveListenerBus): SparkEnv = {
    SparkEnv.createDriverEnv(conf, isLocal, listenerBus, SparkContext.numDriverCores(master))
  }

可以看到实际调用了SparkEnv的createDriverEnv方法来创建SparkEnv,有四个参数:conf、isLocal、listenerBus 以及在本地模式下driver运行executor需要的numberCores。SparkEnv的createDriverEnv方法的实现如下:

  private[spark] def createDriverEnv(
      conf: SparkConf,
      isLocal: Boolean,
      listenerBus: LiveListenerBus,
      numCores: Int,
      mockOutputCommitCoordinator: Option[OutputCommitCoordinator] = None): SparkEnv = {
    assert(conf.contains(DRIVER_HOST_ADDRESS),
      s"${DRIVER_HOST_ADDRESS.key} is not set on the driver!")
    assert(conf.contains("spark.driver.port"), "spark.driver.port is not set on the driver!")
    val bindAddress = conf.get(DRIVER_BIND_ADDRESS)
    val advertiseAddress = conf.get(DRIVER_HOST_ADDRESS)
    val port = conf.get("spark.driver.port").toInt
    val ioEncryptionKey = if (conf.get(IO_ENCRYPTION_ENABLED)) {
      Some(CryptoStreamUtils.createKey(conf))
    } else {
      None
    }
    //createDriverEnv最终调用的是create方法创建SparkEnv
    create(
      conf,
      SparkContext.DRIVER_IDENTIFIER,
      bindAddress,
      advertiseAddress,
      port,
      isLocal,
      numCores,
      ioEncryptionKey,
      listenerBus = listenerBus,
      mockOutputCommitCoordinator = mockOutputCommitCoordinator
    )
  }

createDriverEnv方法首先从SparkConf中获取4个属性。

  • bindAddress:Driver实例的host。此属性通过从SparkConf中获取DRIVER_BIND_ADDRESS指定的属性值。DRIVER_BIND_ADDRESS的定义如下:
  private[spark] val DRIVER_HOST_ADDRESS = ConfigBuilder("spark.driver.host")
    .doc("Address of driver endpoints.")
    .stringConf
    .createWithDefault(Utils.localHostName())
 
  private[spark] val DRIVER_BIND_ADDRESS = ConfigBuilder("spark.driver.bindAddress")
    .doc("Address where to bind network listen sockets on the driver.")
    .fallbackConf(DRIVER_HOST_ADDRESS)

根据DRIVER_BIND_ADDRESS的定义,说明按照优先级从高到低,可以通过spark.driver.bindAddress属性、spark.driver.host属性以及调用Utils的localHostName方法获得bindAddress。

  • advertiseAddress:Driver实例对外宣称的host。可以通过spark.driver.host属性或者Utils的localHostName方法获得。
  • port:Driver实例的端口,可以通过spark.driver.port属性指定。
  • ioEncryptionKey:I/O加密的密钥。当spark.io.encryption.enabled属性为true时,调用CryptoStreamUtils的createKey方法创建密钥。

SparkEnv的createDriverEnv方法的实现主要通过creat方法实现,代码实现如下:

/**
 * Helper method to create a SparkEnv for a driver or an executor.
    * 辅助方法来创建一个驱动程序或执行器sparkenv。
    *
    * SparkEnv的构造步骤如下:
    *     1.创建安全管理器SecurityManager
    *     2.创建给予AKKa的分布式消息系统ActorSystem;
    *     3.创建Map任务输出跟踪器mapOutputTracker;
    *     4.实例化ShuffleManager;
    *     5.创建MemoryManager;
    *     6.创建块传输服务BlockTransferService;
    *     7.创建BlockManagerMaster;
    *     8.创建块管理器BlockManager;
    *     9.创建广播管理器BroadcastManager;
    *     10.创建缓存管理器CacheManager;
    *     11.创建HTTP文件服务器HttpFileServer;
    *     12.创建测量系统MetricsSystem;
    *     13.创建输出提交控制器OutputCommitCoordinator;
    *     14.创建SparkEnv;
   */
  private def create(
      conf: SparkConf,
      executorId: String,
      bindAddress: String,
      advertiseAddress: String,
      port: Int,
      isLocal: Boolean,
      numUsableCores: Int,
      ioEncryptionKey: Option[Array[Byte]],
      listenerBus: LiveListenerBus = null,
      mockOutputCommitCoordinator: Option[OutputCommitCoordinator] = None): SparkEnv = {

    // 驱动程序的Executor的id。这里是一个布尔值,这里为什么要让他们相等呢?下面好多地方都用到了它
    val isDriver = executorId == SparkContext.DRIVER_IDENTIFIER   // isDriver:true executorId:"driver"

    // Listener bus is only used on the driver 监听总线仅仅用在驱动程序上
    // 如果没有创建监听总线如果尝试创建驱动的SparkEnv,将会报错
    if (isDriver) {
      assert(listenerBus != null, "Attempted to create driver SparkEnv with null listener bus!")
    }


    // ===================== 1.创建安全管理器SecurityManager ======================
    // 安全管理器是什么呢?请看http://blog.csdn.net/qq_21383435/article/details/78560364
    val securityManager = new SecurityManager(conf, ioEncryptionKey) // TODO:调试注释 securityManager:SecurityManager@2227 conf:SparkConf@2141 ioEncyptionKey:"none"
    ioEncryptionKey.foreach { _ =>
      // 检查是否应启用网络加密。
      if (!securityManager.isEncryptionEnabled()) {
        logWarning("I/O encryption enabled without RPC encryption: keys will be visible on the " +
          "wire.")
      }
    }

    /**
      * rpcEnv是个什么鬼?
      * 在SparkContext初始化环境时,初始化SparkEnv的时候使用下面代码创建RpcEnv
      */
    val systemName = if (isDriver) driverSystemName else executorSystemName //TODO: yarn-client模式下为 systemName:"sparkDriver"
    val rpcEnv = RpcEnv.create(systemName, bindAddress, advertiseAddress, port, conf,
      securityManager, clientMode = !isDriver)

    // Figure out which port RpcEnv actually bound to in case the original port is 0 or occupied.
    // In the non-driver case, the RPC env's address may be null since it may not be listening
    // for incoming connections.
    // 找出哪些端口rpcenv实际上必然在原来的端口是0或占领。在没有驱动的情况下,
    // RPC env的地址可能是零因为它可能不会侦听传入的连接。
    if (isDriver) {
      conf.set("spark.driver.port", rpcEnv.address.port.toString)
    } else if (rpcEnv.address != null) {
      conf.set("spark.executor.port", rpcEnv.address.port.toString)
      logInfo(s"Setting spark.executor.port to: ${rpcEnv.address.port.toString}")
    }

    // Create an instance of the class with the given name, possibly initializing it with our conf
    // 根据指定的名称创建一个类的实例,尽可能根据我们的配置初始化
    def instantiateClass[T](className: String): T = {
      val cls = Utils.classForName(className)
      // Look for a constructor taking a SparkConf and a boolean isDriver, then one taking just
      // SparkConf, then one taking no arguments
      // 寻找一个构造函数以sparkconf和isdriver的布尔值,然后仅仅告诉SparkConf,然后连接没有传递参数
      try {
        cls.getConstructor(classOf[SparkConf], java.lang.Boolean.TYPE)
          .newInstance(conf, new java.lang.Boolean(isDriver))
          .asInstanceOf[T]
      } catch {
        case _: NoSuchMethodException =>
          try {
            cls.getConstructor(classOf[SparkConf]).newInstance(conf).asInstanceOf[T]
          } catch {
            case _: NoSuchMethodException =>
              cls.getConstructor().newInstance().asInstanceOf[T]
          }
      }
    }

    // Create an instance of the class named by the given SparkConf property, or defaultClassName
    // if the property is not set, possibly initializing it with our conf
    // 根据指定的SparkConf的配置创建这个类的实例,或使用defaultclassname如果没有设置的属性,
    // 尽可能根据我们的配置初始化
    def instantiateClassFromConf[T](propertyName: String, defaultClassName: String): T = {
      instantiateClass[T](conf.get(propertyName, defaultClassName))
    }

    // 序列化工具这里指定使用java的序列化
    val serializer = instantiateClassFromConf[Serializer](
      "spark.serializer", "org.apache.spark.serializer.JavaSerializer")
    logDebug(s"Using serializer: ${serializer.getClass}")

    // ============================= 创建序列化管理器 SerializerManager ===================
    //  http://blog.csdn.net/qq_21383435/article/details/78581511
    val serializerManager = new SerializerManager(serializer, conf, ioEncryptionKey)

    val closureSerializer = new JavaSerializer(conf)

    // 如果当前应用程序是Driver,则创建BlockManagerMasterEndpoint,并且注册到RpcEnv中;
    // 如果当前应用程序是Executor,则从RpcEnv中找到BlockManagerMasterEndpoint的引用。
    def registerOrLookupEndpoint(
        name: String, endpointCreator: => RpcEndpoint):
      RpcEndpointRef = {
      if (isDriver) {
        // 17/12/05 11:56:50 INFO SparkEnv: Registering MapOutputTracker
        // 17/12/05 11:56:50 INFO SparkEnv: Registering BlockManagerMaster
        // 21=>17/12/05 11:56:50 INFO SparkEnv: Registering OutputCommitCoordinator
        logInfo("Registering " + name)
        rpcEnv.setupEndpoint(name, endpointCreator)
      } else {
        RpcUtils.makeDriverRef(name, conf, rpcEnv)
      }
    }


    // ============================== 9.创建广播管理器BroadcastManager;======================
    // BroadcastManager用于将配置信息和序列化后的RDD,job以及ShuffleDependency等信息在本地存储。
    // 如果为了容灾,也会复制到其他节点上。
    // spark学习-34-Spark的BroadcastManager广播管理
    // http://blog.csdn.net/qq_21383435/article/details/78592022
    val broadcastManager = new BroadcastManager(isDriver, conf, securityManager)


    // =============================3.创建Map任务输出跟踪器mapOutputTracker;==================
    /*
        mapOutputTracker用于跟踪map阶段任务的输出状态,此状态便于reduce阶段任务获取地址以及中间输出结果。每个map任务或者
        reduce任务都会有唯一的标识。分别为mapId和reduceId.每个reduce任务的输入可能是多个map任务的输出,reduce会到各个map
        任务的所有节点上拉去Block,这一过程交shuffle,每批shuffle过程都有唯一的表示shuffleId。
     */
    val mapOutputTracker = if (isDriver) {
      new MapOutputTrackerMaster(conf, broadcastManager, isLocal)
    } else {
      new MapOutputTrackerWorker(conf)
    }

    // Have to assign trackerEndpoint after initialization as MapOutputTrackerEndpoint
    // requires the MapOutputTracker itself
    /** 必须在初始化后指定trackerEndpoint,因为MapOutputTrackerEndpoint需要MapOutputTracker */
    mapOutputTracker.trackerEndpoint = registerOrLookupEndpoint(MapOutputTracker.ENDPOINT_NAME,
      new MapOutputTrackerMasterEndpoint(
        rpcEnv, mapOutputTracker.asInstanceOf[MapOutputTrackerMaster], conf))

    // Let the user specify short names for shuffle managers
    // 让用户给shuffle managers指定一个短名称
    /** 这里支持了两种类型的shuffle
      * 以前还有一种"hash"->"org.apache.spark.shuffle.hash.HashShuffleManager"
      * 但是HashShuffleManager被取消使用了
      * */
    val shortShuffleMgrNames = Map(
      "sort" -> classOf[org.apache.spark.shuffle.sort.SortShuffleManager].getName,
      "tungsten-sort" -> classOf[org.apache.spark.shuffle.sort.SortShuffleManager].getName)
    // 默认采取SortedBasedShuffle的方式。
    val shuffleMgrName = conf.get("spark.shuffle.manager", "sort")
    val shuffleMgrClass =
      shortShuffleMgrNames.getOrElse(shuffleMgrName.toLowerCase(Locale.ROOT), shuffleMgrName)

    // =========================创建ShuffleManager============================================
    /** ShuffleManager负责管理本地及远程的block数据的Shuffle操作。
      * 详情:http://blog.csdn.net/qq_21383435/article/details/78634471
      * */
    val shuffleManager = instantiateClass[ShuffleManager](shuffleMgrClass)


    // =======================创建MemoryManager==================================================================
    /**
      * 根据 spark.memory.useLegacyMode 值的不同,会创建 MemoryManager 不同子类的实例:
      * 值为 false:创建 UnifiedMemoryManager 类实例,该类为新的内存管理模块的实现
      * 值为 true:创建 StaticMemoryManager类实例,该类为1.6版本以前旧的内存管理模块的实现
      * */
    val useLegacyMemoryManager = conf.getBoolean("spark.memory.useLegacyMode", false)
    val memoryManager: MemoryManager =
      if (useLegacyMemoryManager) {
        // 如果还是采用之前的方式,则使用StaticMemoryManager内存管理模型,即静态内存管理
        // 配套博客:https://blog.csdn.net/qq_21383435/article/details/78641586
        new StaticMemoryManager(conf, numUsableCores)
      } else {
        // 否则,使用最新的UnifiedMemoryManager内存管理模型,即统一内存管理模型
        // 我们再看下UnifiedMemoryManager,即统一内存管理器。在SparkEnv中,它是通过如下方式完成初始化的:
        // 读者这里可能有疑问了,为什么没有new关键字呢?这正是scala语言的特点。
        // 它其实是通过UnifiedMemoryManager类的apply()方法完成初始化的。
        UnifiedMemoryManager(conf, numUsableCores)
      }




    // =================================.创建块传输服务BlockTransferService;===========================
    /*
        blockTransferService默认为NettyBlockTransferService(可以配置属相spark.shuffle.blockTransferService使用NioBlockTransferService)
        ,它使用Netty法人一步时间驱动的网络应用框架,提供web服务及客户端,获取远程节点上的Block集合。
     */
    val blockManagerPort = if (isDriver) {
      conf.get(DRIVER_BLOCK_MANAGER_PORT)
    } else {
      conf.get(BLOCK_MANAGER_PORT)
    }
    val blockTransferService =
      new NettyBlockTransferService(conf, securityManager, bindAddress, advertiseAddress,
        blockManagerPort, numUsableCores)

    // ================================7.创建BlockManagerMaster; ========================
    val blockManagerMaster = new BlockManagerMaster(registerOrLookupEndpoint(
      BlockManagerMaster.DRIVER_ENDPOINT_NAME,
      new BlockManagerMasterEndpoint(rpcEnv, isLocal, conf, listenerBus)),
      conf, isDriver)


    // =========================创建BlockManager==================================================
    // NB: blockManager is not valid until initialize() is called later.
    // BlockManager负责对Block的管理,只有在BlockManager的痴实话方法initialize被调用之后,它才是有效的。
    // Blockmanager作为存储系统的一部分。
    val blockManager = new BlockManager(executorId, rpcEnv, blockManagerMaster,
      serializerManager, conf, memoryManager, mapOutputTracker, shuffleManager,
      blockTransferService, securityManager, numUsableCores)


    // =======================创建测量系统MetricsSystem====================================================
    /**
        createMetricsSystem方法主要调用了new MetricsSystem(instance, conf, securityMgr)方法
        Instance:制定了谁在使用测量系统

        这里val isDriver = executorId == SparkContext.DRIVER_IDENTIFIER 而SparkContext.DRIVER_IDENTIFIER的值是driver
      如果executorId也是driver,那么isDriver就为真,创建的是driver的监测系统,否则就是创建executor的监测系统
     */
    val metricsSystem = if (isDriver) {
      // Don't start metrics system right now for Driver.
      // We need to wait for the task scheduler to give us an app ID.
      // Then we can start the metrics system.
      // 现在不要为Driver启动metrics system,我们需要task scheduler任务调度器给我们一个APP id,
      // 在这之后再启动 metrics system.
      MetricsSystem.createMetricsSystem("driver", conf, securityManager)
    } else {
      // We need to set the executor ID before the MetricsSystem is created because sources and
      // sinks specified in the metrics configuration file will want to incorporate this executor's
      // ID into the metrics they report.
      // 我们需要设置的executor 的ID在创建MetricsSystem之前,因为sources和sinks的标准配置文件指定了
      // 将要把这个 executor 的ID 传递到metrics的报告中。
      conf.set("spark.executor.id", executorId)
      val ms = MetricsSystem.createMetricsSystem("executor", conf, securityManager)
      ms.start()
      ms
    }


    // =======================创建输出提交控制器OutputCommitCoordinator====================================================
    val outputCommitCoordinator = mockOutputCommitCoordinator.getOrElse {
      new OutputCommitCoordinator(conf, isDriver)
    }
    val outputCommitCoordinatorRef = registerOrLookupEndpoint("OutputCommitCoordinator",
      new OutputCommitCoordinatorEndpoint(rpcEnv, outputCommitCoordinator))
    outputCommitCoordinator.coordinatorRef = Some(outputCommitCoordinatorRef)


    // =============================创建SparkEnv===================================================
    /**
      serializer和closureSerializer都是使用Class.forName反射生成的org.apache.spark.serializer.JavaSerializer类的
      实例。其中closureSerializer实例特别用来对Scala中的闭包进行序列化。
      */
    val envInstance = new SparkEnv(
      executorId,
      rpcEnv,
      serializer,
      closureSerializer,
      serializerManager,
      mapOutputTracker,
      shuffleManager,
      broadcastManager,
      blockManager,
      securityManager,
      metricsSystem,
      memoryManager,
      outputCommitCoordinator,
      conf)

    // Add a reference to tmp dir created by driver, we will delete this tmp dir when stop() is
    // called, and we only need to do it for driver. Because driver may run as a service, and if we
    // don't delete this tmp dir when sc is stopped, then will create too many tmp dirs.
    // driver创建一个临时目录的引用。我们将删除这些临时文件在stop()被调用之后而且我们仅仅需要为driver做这些。
    // 因为driver是运行一个服务,如果我们在sc停止后不删除这些临时文件夹的话,他将会创建很多这样的临时文件夹
    if (isDriver) {
      val sparkFilesDir = Utils.createTempDir(Utils.getLocalDir(conf), "userFiles").getAbsolutePath
      envInstance.driverTmpDir = Some(sparkFilesDir)
    }

    envInstance
  }

SparkEnv的构造步骤如下:

  • 1.创建安全管理器SecurityManager。Spark支持通过共享秘钥进行认证,SecurityManager判断I/O传输是否启动网络加密。可以通过以下过程来创建共享秘钥:1.在spark on YARN部署模式下,配置spark.authenticate为true,就可以自动产生并分发共享秘钥。每个应用程序都使用唯一的共享秘钥。 2.其他部署方式下,应当在每个节点上都配置参数spark.authenticate.secret。此秘钥将由所有Master、worker及应用程序来使用。 3.注意:实验性的Netty shuffle路径(spark.shuffle.use.netty)是不安全的,因此,如果启用认证功能就不要使用Netty for shuffle了。具体的详见《Spark2.2——SecurityManager》。
  • 2.根据Driver和Executor的区别创建rpcEnv。便于与其他集群节点通信,Spark2.x以后采用NettyRpcEnv实现。具体RpcEnv的实现详见《Spark2.2——RpcEnv(一)》。
  • 3.创建序列化管理器SerializerManager。具体的详见《Spark2.2——SerializerManager》。
  • 4.创建广播管理器BroadcastManager。BroadcastManager用于将配置信息和序列化后的RDD,job以及ShuffleDependency等信息在本地存储。如果为了容灾,也会复制到其他节点上。具体的详见《Spark2.2——BroadcastManager》。
  • 5.创建Map任务输出跟踪器mapOutputTracker。mapOutputTracker用于跟踪map阶段任务的输出状态,此状态便于reduce阶段任务获取地址以及中间输出结果。每个map任务或者reduce任务都会有唯一的标识。分别为mapId和reduceId.每个reduce任务的输入可能是多个map任务的输出,reduce会到各个map任务的所有节点上拉去Block,这一过程叫shuffle,每批shuffle过程都有唯一的表示shuffleId。具体的详见《Spark2.2——mapOutputTracker》。
  • 6.实例化ShuffleManager。ShuffleManager负责管理本地及远程的block数据的Shuffle操作。shuffleManager默认通过反射方式生成的SortShuffleManager(Spark2.0以后为SortShuffleManager,具体的详见《Spark2.2——Shuffle原理剖析》具体的实例。SortShuffleManager通过持有的IndexShuffleBlockManager间接操作BlockManager中的DiskBlockManager将map结果写入本地,并且根据shuffleId,mapId写入索引文件,也能通过MapOutputTrackerMaster中维护的mapStatuses从本地或者其他远程节点读取文件。
  • 7.创建MemoryManager。根据 spark.memory.useLegacyMode 值的不同,会创建 MemoryManager 不同子类的实例:值为 false:创建 UnifiedMemoryManager 类实例,该类为新的内存管理模块的实现,值为 true:创建 StaticMemoryManager类实例,该类为1.6版本以前旧的内存管理模块的实现。具体的详见《Spark2.2——MemoryManager》
  • 8.创建块传输服务BlockTransferService。blockTransferService默认为NettyBlockTransferService(可以配置属性spark.shuffle.blockTransferService使用NioBlockTransferService)
    ,它使用Netty一步时间驱动的网络应用框架,提供web服务及客户端,获取远程节点上的Block集合。
  • 9.创建BlockManagerMaster。
  • 10.创建块管理器BlockManager。BlockManager负责对Block的管理,只有在BlockManager的痴实话方法initialize被调用之后,它才是有效的。Blockmanager作为存储系统的一部分。具体的详见《Spark2.2——BlockManager》。
  • 11.创建测量系统MetricsSystem。具体的详见《Spark2.2——MetricsSystem》
  • 12.创建输出提交控制器OutputCommitCoordinator。
  • 13.创建SparkEnv。
Spark2.2源码剖析——SparkContext初始化及Spark环境创建_第1张图片
图1(在 Driver 上创建 SparkEnv )
Spark2.2源码剖析——SparkContext初始化及Spark环境创建_第2张图片
图2( 在 Executor 上创建 SparkEnv )

相关组件介绍:

Spark2.2源码剖析——SparkContext初始化及Spark环境创建_第3张图片

你可能感兴趣的:(大数据,Spark)