文章目录
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