Worker
对于Master,其启动脚本就是通过java mainClass的方式调用main方法启动Worker的java进程。
自然调用的就是org.apache.spark.deploy.worker.Worker的main方法。
于是就从org.apache.spark.deploy.worker.Worker中的main方法开始看起。
1.Worker#main
new SparkConf
Spark应用程序的配置,将spark的各种参数设置为键值对 ;
new WorkerArguments
解析命令行中的参数,例如--webui-port以及masterUrl等,其实端口号和主机这些一般不指定也是有默认值的,注意这里的masterUrl必须指定,不然运行报错
startRpcEnvAndEndpoint
创建rpc通信环境和启动通信终端
rpcEnv.awaitTermination()
阻塞程序直到RpcEnv退出
2.WorkerArguments#parse
这个函数的大部分和上一次分析的Master的启动的MasterArguments中的parse差不多,不过需要注意的不同点如上图,会解析命令行中的MasterUrl,因为Worker是要注册到Master上的。
点击parseStandaloneMasterUrls进入
发现就是我们输入的masterUrl必须以spark://为前缀,所以我在运行源码中的Worker是需要加上如下参数,其中192.168.10.1是本机ip,7077是与Master的通信端口。
3.Worker#startRpcEnvAndEndpoint
new SecurityManager
安全相关的,不多赘述
RpcEnv.create
创建RpcEnv,名字sparkWorker
masterUrls.map(RpcAddress.fromSparkURL)
获取master的地址
rpcEnv.setupEndpoint
注册一个通信终端,名字为Worker,启动发现参数里new了一个Worker
4.RpcEnv#create->RpcEnv#create
至于标题为什么这么写,就是源码经常有的一种套娃现象,点击进入create函数,发现里面又调用了一个create(这里用了函数重载)。
RpcEnvConfig
构建RpcEnvConfig
new NettyRpcEnvFactory().create(config)
这用到了设计模式中的工厂模式,单纯看命名就知道是用NettyRpcEnvFactory创建NettyRpcEnv,一般情况xx工厂就是用来创建xx的。
5.NettyRpcEnv#create
new NettyRpcEnv
创建NettyRpcEnv对象
!config.clientMode
判断配置中是否是客户端模式,不是则继续运行if中语句。
nettyEnv.startServer
创建一个将尝试绑定到特定主机和端口的Transport服务。
startServiceOnPort
启动Workerr服务
6.NettyRpcEnv#startServer
transportContext.createServer
创建TransportServer
dispatcher.registerRpcEndpoint
在dispatcher上注册RpcEndpointVerifier
7.Utils#startServiceOnPort
先对startPort进行参数校验,然后尝试maxRetries次启动服务,如果超过maxRetries次仍然没启动成功,那么就抛出异常
成功后也会打印sparkWorker成功启动日志
8.NettyRpcEnv#setupEndpoint->Dispatcher#registerRpcEndpoint
new NettyRpcEndpointRef
创建NettyRpcEndpointRef,它是RpcEndpoint的引用,一般用于发消息。
sharedLoop.register
匹配是否是使用专用线程池来传递消息,不是的话就执行这一步
9.MessageLoop#register
new Inbox
创建一个收件箱,此时会把OnStart消息放入收件箱中
setActive(inbox)
激活收件箱去处理onStart消息
此时收件箱有一个onStart消息(一旦RpcEndpoint注册到RpcEnv上,就会向inbox队列中加入onStart,处理该消息就会调用RpcEndpoint的onStart方法), 由于Worker是继承ThreadSafeRpcEndpoint,而ThreadSafeRpcEndpoint实现了特质RpcEndpoint,类似于Java中的接口,也就是说会调用Worker中的onStart方法。
10.Worker#onStart
首先是几个日志打印,我个人认为看源码日志很重要
createWorkDir()
创建worker的工作目录
new WorkerWebUI-》webUi.bind()
构建worker的web界面并绑定,启动绑定后会打印如下日志
registerWithMaster()
向Master进行注册
metricsSystem.start()
启动metricsSystem
11.Worker#registerWithMaster
注册Worker到Master上
12.Worker#tryRegisterAllMasters
masterRpcAddresses.map-》registerMasterThreadPool.submit
遍历masterRpcAddresses,每个masterRpcAddresses都启动一个新线程提交到线程池中。
rpcEnv.setupEndpointRef-》sendRegisterMessageToMaster(masterEndpoint)
之前也说过RpcEndpointRef是RpcEndpoint的引用,一般用于发消息,这里就是发送注册消息给masterRpcEndpoint
13.Worker#sendRegisterMessageToMaster
使用send方法发送RegisterWorker消息给master,send方法是不需要回复的,所以直接看Master的receive方法
14.Master#receive
logInfo("Registering worker %s:%d with %d cores, %s RAM".format(
workerHost, workerPort, cores, Utils.megabytesToString(memory)))首先是Master端打印日志
一般情况下,走最后一个else
new WorkerInfo
创建WorkerInfo
registerWorker(worker)
尝试将worker注册到Master上
workerRef.send
注册成功后,发送RegisteredWorker信息给当前要注册的worker,表示已经注册了该Worker了
schedule()
一个调度方法。在等待的应用程序中调度当前可用的资源。每当一个新的应用程序加入或资源可用性改变时(比如对集群资源的变更,节点状态的改变,任务的提交等等),这个方法将被调用。
15.Master#registerWorker
filter-》workers -= w
在同一个节点上可能会有一个或多个指向该状态为DEAD的worker的引用,删除它们。
removeWorker-》workers += worker
判断Master中有没有与当前注册Worker地址相同且状态为UNKNOWN的,状态为UNKNOWN的worker代表该worker在恢复期间被重启。此时old Worker一定死了,我们就把期初它并接受new Worker。
16.Worker#receive
点击handleRegisterResponse进入
之间就说过Master发送RegisteredWorker信息给当前要注册的worker,收到消息匹配后,
打印成功注册到Master的日志
self.send(SendHeartbeat)
Worker给自己发一个SendHeartbeat消息,一看就是与心跳有关的。需要看Worker的
receive函数
17.Worker#receive
发送心跳信息给Master
仍然是通过send方法发送 ,可以直接去看Master的receive方法
18.Master#receive
workerInfo.lastHeartbeat = System.currentTimeMillis()
更新注册在Master上的workerInfo中的上次心跳为当前时间
结果
运行起来后,我们访问http://localhost:8081/,可以看到如下worker的web界面:
然后我们又发现之间的Master的web界面出现了注册的worker的信息