文章目录
大致的整体启动流程如下:

1.main方法
在StandAlone模式下,Spark集群中存在两种重要的节点,分别是Master和Worker。那么作为集群管理器的Master到底是怎样启动的呢?我们通过源码来查看一下。
在运行了start-all.sh脚本之后,最终Master类会启动(至于脚本的启动过程在此省略)。
下面是Master的伴生对象的main方法:
// 启动 Master 的入口函数
def main(argStrings: Array[String]) {
Utils.initDaemon(log)
val conf = new SparkConf
// 构建用于参数解析的实例 --host hadoop201 --port 7077 --webui-port 8080
val args: MasterArguments = new MasterArguments(argStrings, conf)
// 启动 RPC 通信环境和 MasterEndPoint
val (rpcEnv, _, _): (RpcEnv, Int, Option[Int]) = startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, conf)
// 防止master进程退出
rpcEnv.awaitTermination()
}
可以看出:main方法中主要是对启动Master类时穿阿迪进来的参数封装成MasterArguments;然后最重要的是调用startRpcEnvAndEndpoint()方法来创建Netty通信环境和MasterEndpoint。
1.1 startRpcEnvAndEndpoint()
那么startRpcEnvAndEndpoint()是如何创建的呢?可以查看以下源码:
def startRpcEnvAndEndpoint(
host: String,
port: Int,
webUiPort: Int,
conf: SparkConf): (RpcEnv, Int, Option[Int]) = {
// 安全管理器, 主要对账号, 权限及身份认证进行设置和管理
val securityMgr = new SecurityManager(conf)
// 创建 Master 端的 RpcEnv 环境, 并启动 RpcEnv
// 参数: sparkMaster hadoop201 7077 conf securityMgr
// 返回值 的实际类型是: NettyRpcEnv
val rpcEnv: RpcEnv = RpcEnv.create(SYSTEM_NAME, host, port, conf, securityMgr) // ->
// 创建 Master对象, 该对象就是一个 RpcEndpoint, 在 RpcEnv 中注册这个 RpcEndpoint
// 返回该 RpcEndpoint 的引用(master 的RpcEndpointRef)
val masterEndpoint: RpcEndpointRef = rpcEnv.setupEndpoint(ENDPOINT_NAME,
new Master(rpcEnv, rpcEnv.address, webUiPort, securityMgr, conf)) //->
// 向 Master 的通信终端发法请求,获取 BoundPortsResponse 对象
// BoundPortsResponse 是一个样例类包含三个属性: rpcEndpointPort webUIPort restPort
val portsResponse: BoundPortsResponse = masterEndpoint.askWithRetry[BoundPortsResponse](BoundPortsRequest)
(rpcEnv, portsResponse.webUIPort, portsResponse.restPort)
}
可以看到在其中主要是调用了两个方法:RpcEnv.create()方法和rpcEnv.setupEndpoint()方法。
那么接下来我们就从这两个方法入手开始分析。
1.1.1 create
private[spark] object RpcEnv {
def create(
name: String,
host: String,
port: Int,
conf: SparkConf,
securityManager: SecurityManager,
clientMode: Boolean = false): RpcEnv = {
create(name, host, host, port, conf, securitanager, clientMode)
}
// 创建RpcEnv
def create(
name: String,
bindAddress: String,
advertiseAddress: String,
port: Int,
conf: SparkConf,
securityManager: SecurityManager,
clientMode: Boolean): RpcEnv = {
// 保存 RpcEnv 的配置信息 ->
val config = RpcEnvConfig(conf, name, bindAddress, advertiseAddress, port, securityManager,
clientMode)
// 创建 NettyRpcEvn
new NettyRpcEnvFactory().create(config) // create ->
}
}
可见最终调用的是NettyRpcEnvFactory对象的create方法,再次查看源码:
def create(config: RpcEnvConfig): RpcEnv = {
val sparkConf: SparkConf = config.conf
// Use JavaSerializerInstance in multiple threads is safe. However, if we plan to support
// KryoSerializer in future, we have to use ThreadLocal to store SerializerInstance
// 用于 Rpc 传输对象时的序列化
val javaSerializerInstance: JavaSerializerInstance = new JavaSerializer(sparkConf)
.newInstance()
.asInstanceOf[JavaSerializerInstance]
// 实例化 NettyRpcEnv
val nettyEnv = new NettyRpcEnv(
sparkConf,
javaSerializerInstance,
config.advertiseAddress,
config.securityManager)
if (!config.clientMode) {
// 2. 定义 NettyRpcEnv 的启动函数
val startNettyRpcEnv: Int => (NettyRpcEnv, Int) = { actualPort =>
nettyEnv.startServer(config.bindAddress, actualPort)
(nettyEnv, nettyEnv.address.port)
}
try {
// 1. 启动 NettyRpcEnv 使用Utils工具类: Utils.startServiceOnPort
Utils.startServiceOnPort(config.port, startNettyRpcEnv, sparkConf, config.name)._1
} catch {
case NonFatal(e) =>
nettyEnv.shutdown()
throw e
}
}
nettyEnv
}
可见该方法的作用有四个:
(1)创建Rpc对象传输时的序列化器
(2)实例化NettyRpcEnv
(3)startServer方法启动NettyRpcEnv
(4)最终返回NettyRpcEnv
1.1.2 setupEndpoint()
那么接下来我们看一下setupEndpoint()方法:
val masterEndpoint: RpcEndpointRef = rpcEnv.setupEndpoint(ENDPOINT_NAME,
new Master(rpcEnv, rpcEnv.address, webUiPort, securityMgr, conf))
它的作用主要有两个:
(1)创建 Master对象, 该对象就是一个 RpcEndpoint, 在 RpcEnv 中注册这个 RpcEndpoint
(2)返回该 RpcEndpoint 的引用(master 的RpcEndpointRef)
该方法调用的是RpcEnv的唯一的实现类NettyRpcEnv中的setupEndpoint()方法:
override def setupEndpoint(name: String, endpoint: RpcEndpoint): RpcEndpointRef = {
dispatcher.registerRpcEndpoint(name, endpoint)
}
可见其作用主要是在dispatcher中注册RpcEndpoint,然后返回端点的引用RpcEndpointRef。
1.2 Constructor
可以注意到在setupEndpoint()方法中创建了一个Master对象。
那么Master在创建的时候会做出哪些初始化呢?
private val WORKER_TIMEOUT_MS = conf.getLong("spark.worker.timeout", 60) * 1000
private val RETAINED_APPLICATIONS = conf.getInt("spark.deploy.retainedApplications", 200)
private val RETAINED_DRIVERS = conf.getInt("spark.deploy.retainedDrivers", 200)
private val REAPER_ITERATIONS = conf.getInt("spark.dead.worker.persistence", 15)
private val RECOVERY_MODE = conf.get("spark.deploy.recoveryMode", "NONE")
private val MAX_EXECUTOR_RETRIES = conf.getInt("spark.deploy.maxExecutorRetries", 10)
val workers = new HashSet[WorkerInfo]
val idToApp = new HashMap[String, ApplicationInfo]
private val waitingApps = new ArrayBuffer[ApplicationInfo]
val apps = new HashSet[ApplicationInfo]
// id -> wokerInfo
private val idToWorker = new HashMap[String, WorkerInfo]
private val addressToWorker = new HashMap[RpcAddress, WorkerInfo]
private val endpointToApp = new HashMap[RpcEndpointRef, ApplicationInfo]
private val addressToApp = new HashMap[RpcAddress, ApplicationInfo]
private val completedApps = new ArrayBuffer[ApplicationInfo]
private var nextAppNumber = 0
private val drivers = new HashSet[DriverInfo]
private val completedDrivers = new ArrayBuffer[DriverInfo]
// Drivers currently spooled for scheduling
private val waitingDrivers = new ArrayBuffer[DriverInfo]
private var nextDriverNumber = 0
主要是保存和查找WorkerInfo的HashSet和HashMap是比较重要的。
而Master自身其实也是一个RpvEndpoint:
private[deploy] class Master(
override val rpcEnv: RpcEnv,
address: RpcAddress,
webUiPort: Int,
val securityMgr: SecurityManager,
val conf: SparkConf)
extends ThreadSafeRpcEndpoint with Logging with LeaderElectable {......}
2. onStart()方法
在RpcEndpoint特质中有这样的描述
/**
* An end point for the RPC that defines what functions to trigger given a message.
*
* It is guaranteed that `onStart`, `receive` and `onStop` will be called in sequence.
*
* The life-cycle of an endpoint is:
*
* constructor -> onStart -> receive* -> onStop
*/
private[spark] trait RpcEndpoint {}
可以知道一个Endpoint的生命周期方法是:
constructor -> onStart -> receive* -> onStop 按照这样的顺序执行的。
那么我们再来看一下Master的onStart()方法:
核心代码如下:
override def onStart(): Unit = {
logInfo("Starting Spark master at " + masterUrl)
logInfo(s"Running Spark version ${org.apache.spark.SPARK_VERSION}")
// 1. 创建 WebUI 服务器
webUi = new MasterWebUI(this, webUiPort)
webUi.bind()
masterWebUiUrl = "http://" + masterPublicAddress + ":" + webUi.boundPort
if (reverseProxy) {
masterWebUiUrl = conf.get("spark.ui.reverseProxyUrl", masterWebUiUrl)
logInfo(s"Spark Master is acting as a reverse proxy. Master, Workers and " +
s"Applications UIs are available at $masterWebUiUrl")
}
// 2. 按照固定的频率去启动线程来检查 Worker 是否超时. 其实就是给自己发信息: CheckForWorkerTimeOut
// 默认是每分钟检查一次.
checkForWorkerTimeOutTask = forwardMessageThread.scheduleAtFixedRate(new Runnable {
override def run(): Unit = Utils.tryLogNonFatalError {
self.send(CheckForWorkerTimeOut) // 自己给自己发送信息
}
}, 0, WORKER_TIMEOUT_MS, TimeUnit.MILLISECONDS)
......
}
可以看出主要有两个作用:
(1)master WebUi 服务器
(2)周期性的检测work是否超时(每分钟检查一次)
在self.send(CheckForWorkerTimeOut)之后,Master会自己接收信息,在receive方法中:
override def receive: PartialFunction[Any, Unit] = {
......
case CheckForWorkerTimeOut =>
// 移除超时的workers
timeOutDeadWorkers()
}
查看timeOutDeadWorkers()方法:
/** Check for, and remove, any timed-out workers */
private def timeOutDeadWorkers() {
// Copy the workers into an array so we don't modify the hashset while iterating through it
val currentTime = System.currentTimeMillis()
// 把超时的 Worker 从 workers 中移除
val toRemove: Array[WorkerInfo] = workers.filter(_.lastHeartbeat < currentTime - WORKER_TIMEOUT_MS).toArray
for (worker <- toRemove) {
// 如果 worker 的状态不是 DEAD
if (worker.state != WorkerState.DEAD) {
logWarning("Removing %s because we got no heartbeat in %d seconds".format(
worker.id, WORKER_TIMEOUT_MS / 1000))
// 移除超时的worker
removeWorker(worker) // ->
} else {
// 移除worker的尸体
if (worker.lastHeartbeat < currentTime - ((REAPER_ITERATIONS + 1) * WORKER_TIMEOUT_MS)) {
workers -= worker // we've seen this DEAD worker in the UI, etc. for long enough; cull it
}
}
}
}
至此Master的启动流程分析完毕。
来源:CSDN
作者:满岛菜鸟
链接:https://blog.csdn.net/weixin_43616627/article/details/104214363