目录
1.2 startRpcEnvAndEndpoint(...)
一、Master启动主流程
1.1 Master main方法
执行 $SPARK_HOME/sbin/start_master.sh 脚本时,执行的是Master.scala中的main方法。下面我们来看代码,看Master启动过程中都干了哪些事:
1.2 startRpcEnvAndEndpoint(...)
截图中有注释,我就不一一说了。我们看重点:startRpcEnvAndEndpoint(...)
1.3 rpcEnv.setupEndpoint(...)
这里我们重点看一下第3步: val masterEndpoint = rpcEnv.setupEndpoint(...)
这个方法需要两个参数:endPoint的名字,这里是ENDPOINT_NAME,也就是“Master”,还有一个endPoint对象,也就是Master对象,Master对象就是在此处实例化 的(稍后讲解)。
rpcEnv.setupEndpoint(...)最终调用的是dispatcher.registerRpcEndpoint(name, endpoint),我们来看一下这个方法:
这里第4步和第7步需要强调一下:
第4步:向Dispatcher中注册EndPoint时,需要传入一个EndPointData,初始化EndPointData时会创建该EndPoint的Inbox,而InBox的初始化完成后,会向InBox的messages中添加一个OnStart信息(OnStart是InBox处理的第一条信息)。
org/apache/spark/rpc/netty/Inbox.scala:80
第7步:向Dispatcher的receviers中添加一个EndPointData,这个data是为OnStart准备的。
rpcEnv.setupEndpoint(...)方法执行完后,返回一个endpointRef,Master.scala中的启动相关的代码就看完了(Master初始化还没看),在看Master对象初始化之前我们先回头看一下Dispatcher。
二、Master启动需要注意的地方
2.1 Dispatcher的线程池
还记得Dispatcher的线程池吗?这里我们再来看一下:
2.2 Inbox.process(...)
Dispatcher初始化了一个线程池,并启动。我们在看一下MessageLoop
Master的注册过程中向dispatcher的receivers中添加了一个EndPointData,Inbox中添加了一个OnStart。所以线程运行到if(data == PoisonPill)时,条件不成立,进入第3步,执行data.inbox.process(Dispatcher.this)。我们再来看看这个方法:
首先取出inbox中的数据,做非空判断,处理线程数问题。下面开始处理数据:
我们看一下3.3 OnStart信息的处理。InBox初始化时往messages中添加了一个OnStart,所以Master第一次启动时,Dispatcher-EndPoint-InBox中存的第一条信息是OnStart。3.2.1调用的onstart()方法即是Master的onStart方法。也就是说,在Master初始化完成后,便开始执行其onStart()方法。
三、Master的初始化过程
下面我们看一下Master的初始化过程,随后再看下Master的onStart()方法都干了哪些事:
Master是在Master.scala startRpcEnvAndEndpoint(...)方法中向env注册时通过new关键字初始化的。我们直接看Master类有哪些属性,之后再看其onStart方法。
3.1 参数初始化
Master初始化时初始化了很多属性,这里我们捡几个重要的列一下:
// hadoop配置
private val hadoopConf = SparkHadoopUtil.get.newConfiguration(conf)
// Worker超时时间,默认60s
private val WORKER_TIMEOUT_MS = conf.getLong("spark.worker.timeout", 60) * 1000
// executor最大重试次数(默认10次,也就是说最多执行11次)
private val MAX_EXECUTOR_RETRIES = conf.getInt("spark.deploy.maxExecutorRetries", 10)
// worker列表,里面存放worker的id,地址,端口,核心数,内存,worker引用,webUi地址
val workers = new HashSet[WorkerInfo]
// 等待中的apps(已提交未处理)
private val waitingApps = new ArrayBuffer[ApplicationInfo]
// app列表
val apps = new HashSet[ApplicationInfo]
// 处理完成的apps
private val completedApps = new ArrayBuffer[ApplicationInfo]
// 下一个App编号
private var nextAppNumber = 0
// 驱动
private val drivers = new HashSet[DriverInfo]
// 已完成的driver
private val completedDrivers = new ArrayBuffer[DriverInfo]
// Drivers currently spooled for scheduling
private val waitingDrivers = new ArrayBuffer[DriverInfo]
// 下一个驱动的编号
private var nextDriverNumber = 0
// After onStart, webUi will be set
private var webUi: MasterWebUI = null
// master url
private val masterUrl = address.toSparkURL
// master weburl
private var masterWebUiUrl: String = _
// master节点状态
private var state = RecoveryState.STANDBY
// 如果没有指定,则默认最大核心数为Int最大值
private val defaultCores = conf.getInt("spark.deploy.defaultCores", Int.MaxValue)
3.2 Master的onStart()方法
下面我们来看下Master的onstart方法干了哪些事:
我们看一下 self.send(CheckForWorkerTimeOut) :
这个self是RpcEndpointRef,这里也就是Master的引用,我把实现贴在下面:
NettyRpcEnv.scala
Dispatcher发送出一条OneWayMessage(不需要回复的)。我们接着往下看:
看一下data.inbox.post(message):
这时,消息已经存放在Master的inbox中,当Dispatcher的轮询线程执行到时,会调用inbox.process处理这条消息,inbox.process()方法这里不再细说(2.1.2),我们直接看对应片段(由dispatcher.postOneWayMessage(message)知道是一条OneWayMessage):
此处的endpoint.receive即是Master的receive方法,我们看一下:
Master的receive方法根据接收到的数据类型进行模式匹配,我们看下CheckForWorkerTimeOut数据类型干了什么:
我们看下timeOutDeadWorkers()
至于worker删除的细节,这里就不看了。我们回到Master的onStart方法。
至此,Master的启动已经完成。我们简单总结一下:
首先,初始化了Master rpcEnv环境,初始化了Rpc通信的相关组件,并在Dispatcher和Inbox中添加了启动数据。初始化一个Master对象,定义了一大堆变量:hadoop配置,worker超时时间、任务恢复模式、worker列表、app列表,驱动、webui等等。之后调用Master的onStart方法,正式启动Master。
Master的onStart方法给自己发送了一个CheckForWorkerTimeOut,用于检查是否有超时的worker,如果有则移除。
这一篇到这里吧,以后有新的理解再更新。下一篇我们学习一下Worker的启动过程。