通讯架构
(1)RpcEndpoint:RPC端点。Spark针对每个节点(Client/Master/Worker)都称之为一个Rpc端点,且都实现RpcEndpoint接口,内部根据不同端点的需求,设计不同的消息和不同的业务处理,如果需要发送(询问)则调用Dispatcher;
(2) RpcEnv:RPC上下文环境,每个RPC端点运行时依赖的上下文环境称为RpcEnv;
(3)Dispatcher:消息分发器,针对于RPC端点需要发送消息或者从远程RPC接收到的消息,分发至对应的指令收件箱/发件箱。如果指令接收方是自己则存入收件箱,如果指令接收方不是自己,则放入发件箱;
(4)Inbox:指令消息收件箱。一个本地RpcEndpoint对应一个收件箱,Dispatcher在每次向Inbox存入消息时,都将对应EndpointData加入内部ReceiverQueue中,另外Dispatcher创建时会启动一个单独线程进行轮询ReceiverQueue,进行收件箱消息消费;
(5)RpcEndpointRef(client):RpcEndpointRef是对远程RpcEndpoint的一个引用。当我们需要向一个具体的RpcEndpoint发送消息时,一般我们需要获取到该RpcEndpoint的引用,然后通过该应用发送消息。
(6)OutBox:指令消息发件箱。对于当前RpcEndpoint来说,一个目标RpcEndpoint对应一个发件箱,如果向多个目标RpcEndpoint发送信息,则有多个OutBox。当消息放入Outbox后,紧接着通过TransportClient将消息发送出去。消息放入发件箱以及发送过程是在同一个线程中进行;
(7)RpcAddress:表示远程的RpcEndpointRef的地址,Host + Port。
(8)TransportClient:Netty通信客户端,一个OutBox对应一个TransportClient,TransportClient不断轮询OutBox,根据OutBox消息的receiver信息,请求对应的远程TransportServer;
(9)TransportServer:Netty通信服务端,一个RpcEndpoint对应一个TransportServer,接受远程消息后调用Dispatcher分发消息至对应收发件箱;
启动流程
(1)start-all.sh脚本,实际是执行“java -cp Master”和“java -cp Worker”;
(2)Master启动时首先创建一个RpcEnv对象,负责管理所有通信逻辑;
(3)Master通过RpcEnv对象创建一个Endpoint,Master就是一个Endpoint,Worker可以与其进行通信;
(4)Worker启动时也是创建一个RpcEnv对象;
(5)Worker通过RpcEnv对象创建一个Endpoint;
(6)Worker通过RpcEnv对象建立到Master的连接,获取到一个RpcEndpointRef对象,通过该对象可以与Master通信;
(7)Worker向Master注册,注册内容包括主机名、端口、CPU Core数量、内存数量;
(8)Master接收到Worker的注册,将注册信息维护在内存中的Table中,其中还包含了一个到Worker的RpcEndpointRef对象引用;
(9)Master回复Worker已经接收到注册,告知Worker已经注册成功;
(10)Worker端收到成功注册响应后,开始周期性向Master发送心跳。
Master源码跟踪
源码首页,进入ThreadSafeRpcEndpoint类查看
找到了RpcEndpoint,进入其中
找到生命周期和通讯架构图中的最原始的RpcEndpoint
回到Master找到main()方法和main()方法中的startRpcEnvAndEndpoint()方法进入其中
startRpcEnvAndEndpoint() : 启动通讯环境以及节点
参数:对应–host hadoop102 --port 7077 --webui-port 8080
startRpcEnvAndEndpoint()中的方法
// 构建RPC通信环境:new NettyRpcEnvFactory().create(config)
val rpcEnv = RpcEnv.create(SYSTEM_NAME, host, port, conf, securityMgr)
// 创建Master对象并将自身设置进通信环境:new Master()代表生命周期的第一个constructor构造方法,也就是Master进程的开始
val masterEndpoint = rpcEnv.setupEndpoint(ENDPOINT_NAME,new Master())
当进行构建和绑定完毕后开始生命周期第二阶段onStart(),直接搜索到onStart()方法
跟踪到send()方法
// 打印日志
logInfo("Starting Spark master at " + masterUrl)
logInfo(s"Running Spark version ${org.apache.spark.SPARK_VERSION}")
// 端口绑定
webUi.bind()
// 以固定的频率校验Worker是否超时
scheduleAtFixedRate{
//
self.send(CheckForWorkerTimeOut)
}
nettyEnv.send()继续跟踪
跟踪dispatcher.postOneWayMessage(message)
// 接受地址如果在本地 则是本地模式
val remoteAddr = message.receiver.address
if (remoteAddr == address) {
// Message to a local RPC endpoint.
try {
// dispatcher, 如果是本地,则讲消息放在dispatcher的inbox,我们跟踪一下
dispatcher.postOneWayMessage(message)
} else {
// Message to a remote RPC endpoint.
// 走远程给outBox
postToOutbox(message.receiver, OneWayOutboxMessage(serialize(message)))
}
回到send()方法找到else中的方法跟踪 postToOutbox(message.receiver, OneWayOutboxMessage(serialize(message)))
val remoteAddr = message.receiver.address
if (remoteAddr == address) {
// Message to a local RPC endpoint.
try {
dispatcher.postOneWayMessage(message)
} catch {
case e: RpcEnvStoppedException => logWarning(e.getMessage)
}
} else {
// Message to a remote RPC endpoint.
postToOutbox(message.receiver, OneWayOutboxMessage(serialize(message)))
}
}
// 一个address,对应一个outBox
private val outboxes = new ConcurrentHashMap[RpcAddress, Outbox]()
以上在通讯结构图中可以对应看到若发送地址为自身,则将消息直接发送到本地的InBox,若是远程则将消息发送到OutBox(s)
回到Master,同样的自己发送自己,则ChechForWorkerTimeOut则应该在本类中有接受的类,我们进行搜索
这个类是receive,用来接收消息并初处理,然后跟踪处理方式timeOutDeadWorkers()
receive用来处理send发送的消息
receiveAndReply用来处理ask询问的消息
override def receive: PartialFunction[Any, Unit] = {
...
case CheckForWorkerTimeOut => timeOutDeadWorkers()
}
master从worker中获取心跳信息,保持整个流式计算一直成功,直到stop后master结束
// 获取当前时间
val currentTime = System.currentTimeMillis()
// 过滤,上一次的心跳信息小于当前时间减去超时时间
// WORKER_TIMEOUT_MS默认为60秒
val toRemove = workers.filter(_.lastHeartbeat < currentTime - WORKER_TIMEOUT_MS).toArray
// 通过遍历将没用的workers减去 : workers -= worker
for (worker <- toRemove) {
if (worker.state != WorkerState.DEAD) {
logWarning("Removing %s because we got no heartbeat in %d seconds".format(
worker.id, WORKER_TIMEOUT_MS / 1000))
removeWorker(worker)
} else {
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
}
}
}
Worker源码跟踪
main()方法找到startRpcEnvAndEndpoint()方法进行跟踪
//1.程序入口
main()
//2.启动通信环境以及节点,
// 因为主动找master,所以有master参数
// 因为是资源的封装 所以有cores和memory
startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, args.cores,
args.memory, args.masters, args.workDir, conf = conf)
构建RPC通信环境和创建Worker对象并将自身设置进通信环境
// 与Master同样的构建RPC通信环境
val rpcEnv = RpcEnv.create(systemName, host, port, conf, securityMgr)
// 创建Worker对象并将自身设置进通信环境
// masterAddresses : master通讯地址,找master进行通讯
rpcEnv.setupEndpoint(ENDPOINT_NAME, new Worker(rpcEnv, webUiPort, cores, memory, masterAddresses, ENDPOINT_NAME, workDir, conf, securityMgr))
生命周期与Master一致,当创建号Worker对象后,进行onStart()方法,在onStart中找到registerWithMaster()方法进行跟踪
// 核心代码,向Master进行注册
registerWithMaster()
找到tryRegisterAllMasters()进行跟踪,看如何注册和选择的Master
// 尝试与所有的Master进行注册(高可用----有多个Master)
registerMasterFutures = tryRegisterAllMasters()
找到具体registerWithMaster()跟踪,查看
// 跟具体的某一个Master进行注册
registerWithMaster(masterEndpoint)
注册消息由master中的receiveAndReply()方法接受处理,
// 向Master发送注册消息
masterEndpoint.ask[RegisterWorkerResponse](RegisterWorker(..))
我们在Master中搜索worker中向master发送的注册信息RegisterWorker()
// 如果状态为standby,则回执消息为MasterInStandby
if (state == RecoveryState.STANDBY) {
context.reply(MasterInStandby)
// 如果这个masterid已经被注册了,则发送重复id
}else if (idToWorker.contains(id)) {
context.reply(RegisterWorkerFailed("Duplicate worker ID"))
else {
val worker = new WorkerInfo(id, workerHost, workerPort, cores, memory,
workerRef, workerWebUiUrl)
if (registerWorker(worker)) {
persistenceEngine.addWorker(worker)
// 注册成功回执消息
context.reply(RegisteredWorker(self, masterWebUiUrl))
schedule()
} else {
val workerAddress = worker.endpoint.address
logWarning("Worker registration failed. Attempted to re-register worker at same " +
"address: " + workerAddress)
context.reply(RegisterWorkerFailed("Attempted to re-register worker at same address: "
+ workerAddress))
}
}
回到Worker中,匹配绘制消息是否成功或失败,如果成功,则 handleRegisterResponse(msg),跟踪这个方法
自己给自己发送send 则Worker方法中会有一个receive()方法去接收这个SendHeartBeat,我们跟踪搜索这个SendHeartBeat
// 以固定频率给自己发送心跳信息
forwordMessageScheduler.scheduleAtFixedRate(new Runnable {
override def run(): Unit = Utils.tryLogNonFatalError {
self.send(SendHeartbeat)
}
当跟踪sendToMaster()方法可知调用masterRef.send(message),在master自己调用sent发送给自己,则在master的reveice()中会接收这个Heartbeat。我们跟踪到master中查看
// 向master发送心跳信息
case SendHeartbeat =>
if (connected) { sendToMaster(Heartbeat(workerId, self)) }
在receive()方法中有一个case匹配Heartbeat(workerId, worker),至此Worker流程走完
master在校验worker发送来的时间信息
worker一直在发送自己的心跳信息
则master和worker会一直存在关联
// 将当前的时间,赋值给最后一次心跳信息
workerInfo.lastHeartbeat = System.currentTimeMillis()
总结
Master源码小结
org.apache.spark.deploy.master.Master[RpcEndpoint]
life-cycle:constructor -> onStart -> receive* -> onStop
//1.程序入口
main()
//2.启动通信环境以及节点
startRpcEnvAndEndpoint
//3.构建RPC通信环境
val rpcEnv = RpcEnv.create(SYSTEM_NAME, host, port, conf, securityMgr)
//4.创建Master对象并将自身设置进通信环境
rpcEnv.setupEndpoint(ENDPOINT_NAME,new Master())
//5.启动节点
onStart()
//6.以固定的频率校验Worker是否超时
scheduleAtFixedRate{
self.send(CheckForWorkerTimeOut)
}
//7.接收消息并初处理
receive(){
case CheckForWorkerTimeOut => timeOutDeadWorkers()
}
//8.遍历判断Worker是否超时
val toRemove = workers.filter(_.lastHeartbeat < currentTime - WORKER_TIMEOUT_MS).toArray
Worker源码小结
org.apache.spark.deploy.worker.Worker[RpcEndpoint]
life-cycle:constructor -> onStart -> receive* -> onStop
//1.程序入口
main()
//2.启动通信环境以及节点
startRpcEnvAndEndpoint(args.host, args.port, args.webUiPort, args.cores,
args.memory, args.masters, args.workDir, conf = conf)
//3.构建RPC通信环境
val rpcEnv = RpcEnv.create(systemName, host, port, conf, securityMgr)
//4.创建Worker对象并将自身设置进通信环境
rpcEnv.setupEndpoint(ENDPOINT_NAME, new Worker(rpcEnv, webUiPort, cores, memory,
masterAddresses, ENDPOINT_NAME, workDir, conf, securityMgr))
//5.向Master进行注册
registerWithMaster()
//6.尝试与所有的Master进行注册(高可用有多个Master)
tryRegisterAllMasters
//7.跟具体的某一个Master进行注册
registerWithMaster(masterEndpoint)
//8.向Master发送注册消息
masterEndpoint.ask[RegisterWorkerResponse](RegisterWorker())