Spark-Core中通讯架构解析|启动流程分析|集群启动的Master源码跟踪分析|集群启动的Worker源码跟踪分析|总结

通讯架构

(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())	  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值