文章目錄
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()
方法來實現的,區別在於
- 傳入的參數不同
- 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:
-
spark的內部source,如MasterSource、WorkerSource等,它們可以收集spark組件的內部狀態。這些source與實例相關,並在創建特定的MetricsSystem後添加。
-
公共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