深入理解SparkEnv

SparkEnv.scala注释

官方注释

SparkEnv对象会持有spark应用程序实例运行的声明周期中持有包括master节点和worker节点在内所有的运行时环境对象,例如序列化器,Rpc的环境变量,block manager块管理器,map输出的追踪器等。

当前的spark的代码会通过查找全局变量来获得SparkEnv对象,所以spark应用程序的所有子线程都能获得到相同的SparkEnv对象。例如在SparkContext创建之后,使用SparkEnv.get()来获取SparkEnv对象实例。

创建DriverEnv和ExecutorEnv

// SparkEnv.scala

// 创建Driver的环境
private[spark] def createDriverEnv(
      conf: SparkConf,
      isLocal: Boolean,
      ...
      // 每个executor中的core的数量
      numCores: Int,
      ...
    // 调用统一的create方法
    create(
      conf,
      // 用于声明这是对driver进行的环境创建
      SparkContext.DRIVER_IDENTIFIER,
      bindAddress,
      advertiseAddress,
      Option(port),
      isLocal,
      numCores,
      ioEncryptionKey,
      listenerBus = listenerBus,
      mockOutputCommitCoordinator = mockOutputCommitCoordinator
    )
  }

// 创建executor环境
private[spark] def createExecutorEnv(
      conf: SparkConf,
      executorId: String,
      hostname: String,
      numCores: Int,
      ioEncryptionKey: Option[Array[Byte]],
      isLocal: Boolean): SparkEnv = {
    // env是SparkEnv的实例化对象envInstance,下文会提及
    val env = create(
      conf,
      executorId,
      hostname,
      hostname,
      None,
      isLocal,
      numCores,
      ioEncryptionKey
    )
    SparkEnv.set(env)
    env
  }

通过以上代码我们可以发现,无论是在创建driverEnv或是在创建executorEnv的时候,都是通过调用统一的方法create()方法来实现的,区别在于

  1. 传入的参数不同
  2. driverEnv调用create并没有处理返回值,executorEnv获取到了SparkEnv的实例对象,并将其set进伴生对象中。所以在官方的注释中,可以使用SparkEnv.get直接获取到SparkEnv的实例。

统一调用的create方法

create函数头

// SparkEnv.scala

// create的参数及返回值声明
private def create(
      conf: SparkConf,
      executorId: String,
      bindAddress: String,
      advertiseAddress: String,
      port: Option[Int],
      isLocal: Boolean,
      numUsableCores: Int,
      ioEncryptionKey: Option[Array[Byte]],
      listenerBus: LiveListenerBus = null,
      mockOutputCommitCoordinator: Option[OutputCommitCoordinator] = None
      ): SparkEnv

create函数体

rpc加密过程的加密

// 判断driver与executor通信过程是否启用rpc加密
val securityManager = new SecurityManager(conf, ioEncryptionKey)
if (isDriver) {
      securityManager.initializeAuth()
}

ioEncryptionKey.foreach { _ =>
if (!securityManager.isEncryptionEnabled()) {
      logWarning("I/O encryption enabled without RPC encryption: keys will be visible on the wire.")
      }
}

// 创建rpc的env,在1.6版本之后的spark使用的是netty框架,之前使用的是akka
val systemName = if (isDriver) driverSystemName else executorSystemName
val rpcEnv = RpcEnv.create(systemName, bindAddress, advertiseAddress, port.getOrElse(-1), conf,
      securityManager, numUsableCores, !isDriver)

// 设置driver的端口为rpc端口
if (isDriver) {
      conf.set("spark.driver.port", rpcEnv.address.port.toString)
}

序列化器设置

// 设置序列化器,默认使用java的序列化器
val serializer = instantiateClassFromConf[Serializer](
      "spark.serializer", "org.apache.spark.serializer.JavaSerializer")
val serializerManager = new SerializerManager(serializer, conf, ioEncryptionKey)
// 用于闭包的序列化器
val closureSerializer = new JavaSerializer(conf)

广播变量管理器初始化

val broadcastManager = new BroadcastManager(isDriver, conf, securityManager)

map输出结果追踪

MapOutputTrackerMaster是一个位于drive端的类,它会持续跟踪同一个stage内map的输出位置。
DAGScheduler用这个类来注册(注销)map的输出状态,通过查找这些信息来减少位置敏感性的task的调度次数。
ShuffleMapStage使用该类来追踪可用的或丢失的输出,以确定哪一些task需要重跑。

val mapOutputTracker = if (isDriver) {
      new MapOutputTrackerMaster(conf, broadcastManager, isLocal)
} else {
      new MapOutputTrackerWorker(conf)
}

// endpoint不是本章重点
mapOutputTracker.trackerEndpoint = registerOrLookupEndpoint(MapOutputTracker.ENDPOINT_NAME,
      new MapOutputTrackerMasterEndpoint(rpcEnv, mapOutputTracker.asInstanceOf[MapOutputTrackerMaster], conf)
)

设置shuffle manager

目前留下来的shuffle manager有sort manager以及tungsten-sort mangager,但两个底层都是使用的SortShuffleManager类。

shuffle manager默认值 版本号 加入、删除版本
hash before 1.2 delete after 1.6
sort since 1.2 -
tunsten-sort - add since 1.5

// TODO三者的对比

val shortShuffleMgrNames = Map(
      "sort" -> classOf[org.apache.spark.shuffle.sort.SortShuffleManager].getName,
      "tungsten-sort" -> classOf[org.apache.spark.shuffle.sort.SortShuffleManager].getName)
val shuffleMgrName = conf.get("spark.shuffle.manager", "sort")
val shuffleMgrClass =
      shortShuffleMgrNames.getOrElse(shuffleMgrName.toLowerCase(Locale.ROOT), shuffleMgrName)
val shuffleManager = instantiateClass[ShuffleManager](shuffleMgrClass)

设置内存管理器

之后会对spark的内存管理进行深入探讨。值得注意的是如果没有特别的声明,driver与executor都会使用默认的统一内存管理。

// 判断是否使用旧的内存管理模式
val useLegacyMemoryManager = conf.getBoolean("spark.memory.useLegacyMode", false)

val memoryManager: MemoryManager =
if (useLegacyMemoryManager) {
        // 旧版本使用的是静态内存管理
        new StaticMemoryManager(conf, numUsableCores)
} else {
        // 新版本使用的是统一内存管理
        UnifiedMemoryManager(conf, numUsableCores)
}

设置块管理

val blockManagerPort = if (isDriver) {
      conf.get(DRIVER_BLOCK_MANAGER_PORT)
} else {
      conf.get(BLOCK_MANAGER_PORT)
}
// 使用netty去获取远端的block块,一次拿多个
// block在进行网络传输的时候仍旧使用的是java的序列化器
val blockTransferService =
      new NettyBlockTransferService(conf, securityManager, bindAddress, advertiseAddress,
        blockManagerPort, numUsableCores)

val blockManagerMaster = new BlockManagerMaster(registerOrLookupEndpoint(
      BlockManagerMaster.DRIVER_ENDPOINT_NAME,
      new BlockManagerMasterEndpoint(rpcEnv, isLocal, conf, listenerBus)),
      conf, isDriver)

// 块管理器需要调用initialize方法进行初始化之后才能使用
// 无论是driver还是executor,每个节点都会运行块管理器
// 块管理器是用来提供接口调用,使得能将本地或者远程收到的块能够被多种形式存储
// 如磁盘,内存,或是堆外内存
val blockManager = new BlockManager(executorId, rpcEnv, blockManagerMaster,
      serializerManager, conf, memoryManager, mapOutputTracker, shuffleManager,
      blockTransferService, securityManager, numUsableCores)

MetricsSystem

由特定的实例创建,由source、sink组成,定期将source的metrics数据 拉取到 sink目标端。
实例指定了什么角色能使用MetricsSystem。在Spark中,有几种角色,如master、worker、executor、driver。这些角色将创建MetricsSystem用于监控。所以,实例代表这些角色。目前在Spark中,已经实现了实例:master、worker、executor、driver、applications。
source指定从哪里(源端)收集Metrics数据。在MetricsSystem中,有两种source:

  1. spark的内部source,如MasterSource、WorkerSource等,它们可以收集spark组件的内部状态。这些source与实例相关,并在创建特定的MetricsSystem后添加。

  2. 公共source,如jvmsource,可以收集一些相对低级的状态,由配置配置并通过 反射 加载。

sink指定将Metrics数据输出到的位置(目标)。多个sink可以共存,并且可以将Metrics刷新到所有这些sink。

Matrics的配置格式为:

[instance].[sink|source].[name].[options] = xxxx

[instance]可以是master、worker、executor、driver、applications,代表特定的实例才能使用这个配置,甚至可以使用*,代表所有的实例都可以使用该配置。

[sink|source]该条配置属性属于哪一个source或者sink。只能填source或者sink中的一个。
[name]用来自定义source或者sink的名称
[options]代表了source或者sink中的一些其他属性

val metricsSystem = if (isDriver) {
      // 不要在启动driver时立即启动MetricsSystem
      // 需要等待任务调度器提供相应的app id,然后才能启动
      MetricsSystem.createMetricsSystem("driver", conf, securityManager)
} else {
      // 需要在创建MetricsSystem前设置executor id,因为配置文件中的指定的source和sink
      // 想要将executor id也加入report的matrics中
      conf.set("spark.executor.id", executorId)
      val ms = MetricsSystem.createMetricsSystem("executor", conf, securityManager)
      ms.start()
      ms
}

输出协调器

用于判定task是否拥有将output提交到HDFS的权限。使用 第一个提交者获胜 策略。

OutputCommitCoordinator在driver和executor中都有被实例化。在executor上,它配置为引用driver的OutputCommitCoordinatorEndpoint,因此提交输出的请求将转发到driver的OutputCommitCoordinator中。

这个类在 spark-4879 中引入

val outputCommitCoordinator = mockOutputCommitCoordinator.getOrElse {
      new OutputCommitCoordinator(conf, isDriver)
    }
val outputCommitCoordinatorRef = registerOrLookupEndpoint("OutputCommitCoordinator",
      new OutputCommitCoordinatorEndpoint(rpcEnv, outputCommitCoordinator))
    outputCommitCoordinator.coordinatorRef = Some(outputCommitCoordinatorRef)
Spark-4879简单描述

当预测执行功能被开启,job在将output文件进行保存的时候会显示保存成功,即使有一些通过预测执行任务输出的partitions出现了丢失情况。

出现问题的版本:1.3之前
修复版本:1.3

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章