阅读指导:在《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。
![](https://images2018.cnblogs.com/blog/202708/201803/202708-20180323110641430-848599500.png)
![](https://i-blog.csdnimg.cn/blog_migrate/da1eb53de33cc74dedeb78c54fced12a.png)
相关组件介绍:
![](https://i-blog.csdnimg.cn/blog_migrate/8c66900fd470954c976d3f47c216b94b.png)