深入理解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

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