spark 源码导读2 进一步窥探Master、Worker启动及通信机制
上一篇文章 spark 源码理解1 从spark启动脚本开始 是分析执行start_all.sh时,集群中启动了哪些进程,下面我们再深入一点看看这些进程都是做什么用的,它们之间又是如何通信的?
一、Master进程的启动
Master进程,它主要负责对Worker、Driver、App等资源的管理并与它们进行通信,这篇文章中我打算着重讲一下它与Worker的通信,其它的部分放在以后的章节再加以描述。
spark-daemon.sh start org.apache.spark.deploy.master.Master 1 --ip $SPARK_MASTER_IP --port $SPARK_MASTER_PORT --webui-port $SPARK_MASTER_WEBUI_PORT
前面说过,我们最终是通过上面的命令来启动Master进程的,其中org.apache.spark.deploy.master.Master是类名,其余为参数:1 表示实例个数,--ip 主机地址,--port 主机端口,默认7077,--webui-port web界面端口,默认8080,它们的解析是由org.apache.spark.deploy.master.MasterArguments 完成的。
在接下来深入之前,简单介绍一下Spark重要的通信协议Akka,它是基于消息传递的机制,scala原生支持的,具有一下特点:
Actors
Actors为你提供:
对并发/并行程序的简单的、高级别的抽象。
异步、非阻塞、高性能的事件驱动编程模型。
非常轻量的事件驱动处理(1G内存可容纳约270万个actors)。
容错性
使用“let-it-crash”语义和监管者树形结构来实现容错。非常适合编写永不停机、自愈合的高容错系统。监管者树形结构可以跨多个JVM来提供真正的高容错系统。
位置透明性
Akka的所有元素都为分布式环境而设计:所有actor都仅通过发送消息进行互操作,所有操作都是异步的。
事务性actors
事务性Actor是actor与STM(Software Transactional Memory)的组合。它使你能够使用自动重试和回滚来组合出原子消息流。
有一个很好的入门级教程可以参考 过忘记忆 的 Akka学习笔记 。
继续回到Master.scala这个文件,它由Master类和Master对象组成。在scala中,这个对象被叫做伴生对象(object Companion),是一个Singleton(注:类和单例对象存在于同一个文件中,才能被称之为Singleton,否则为孤立对象),对应的类叫做伴生类(class Companion),它们之间可以彼此访问对方的私有成员。
程序的入口是Master对象的main函数,它主要调用startSystemAndActor方法,启动Master并返回一个Tuple4的对象。
val (actorSystem, _, _, _) = startSystemAndActor(args.host, args.port, args.webUiPort, conf)
/** * Start the Master and return a four tuple of: * (1) The Master actor system * (2) The bound port * (3) The web UI bound port * (4) The REST server bound port, if any */ def startSystemAndActor( host: String, port: Int, webUiPort: Int, conf: SparkConf): (ActorSystem, Int, Int, Option[Int]) = { val securityMgr = new SecurityManager(conf) val (actorSystem, boundPort) = AkkaUtils.createActorSystem(systemName, host, port, conf = conf, securityManager = securityMgr) val actor = actorSystem.actorOf( Props(classOf[Master], host, boundPort, webUiPort, securityMgr, conf), actorName) val timeout = AkkaUtils.askTimeout(conf) val portsRequest = actor.ask(BoundPortsRequest)(timeout) val portsResponse = Await.result(portsRequest, timeout).asInstanceOf[BoundPortsResponse] (actorSystem, boundPort, portsResponse.webUIPort, portsResponse.restPort) }
生成Master实例时,会创建 Master Metrics System, Application Metrics System,并启动它们,然后根据RECOVERY_MODE来指定Master的容错恢复模式,这里可供选择的有ZOOKEEPER,FILESYSTEM,CUSTOM和其它BlackHole。
val (persistenceEngine_, leaderElectionAgent_) = RECOVERY_MODE match { case "ZOOKEEPER" => logInfo("Persisting recovery state to ZooKeeper") val zkFactory = new ZooKeeperRecoveryModeFactory(conf, SerializationExtension(context.system)) (zkFactory.createPersistenceEngine(), zkFactory.createLeaderElectionAgent(this)) case "FILESYSTEM" => val fsFactory = new FileSystemRecoveryModeFactory(conf, SerializationExtension(context.system)) (fsFactory.createPersistenceEngine(), fsFactory.createLeaderElectionAgent(this)) case "CUSTOM" => val clazz = Class.forName(conf.get("spark.deploy.recoveryMode.factory")) val factory = clazz.getConstructor(conf.getClass, Serialization.getClass) .newInstance(conf, SerializationExtension(context.system)) .asInstanceOf[StandaloneRecoveryModeFactory] (factory.createPersistenceEngine(), factory.createLeaderElectionAgent(this)) case _ => (new BlackHolePersistenceEngine(), new MonarchyLeaderAgent(this)) }
Master进程启来后,开始监听其它进程发来的消息,并在receiveWithLogging中对它们进行相应的处理。
二、Worker进程的启动及与Master的通信
同样地,当启动脚本执行到
spark-daemon.sh start org.apache.spark.deploy.worker.Worker "$@"
会启动Worker进程,启动过程跟Master的启动很相似,先是通过org.apache.spark.deploy.worker.WorkerArguments 进行参数解析,得到端口、内核数、内存、web ui 端口、工作目录等信息,然后生成Worker Actor并启动它。
Worker会初始化如下信息:
val actor = context.actorSelection(masterAkkaUrl) actor ! RegisterWorker(workerId, host, port, cores, memory, webUi.boundPort, publicAddress)
Master通过以下代码来响应此消息:
case RegisterWorker(id, workerHost, workerPort, cores, memory, workerUiPort, publicAddress) => { logInfo("Registering worker %s:%d with %d cores, %s RAM".format( workerHost, workerPort, cores, Utils.megabytesToString(memory))) if (state == RecoveryState.STANDBY) { // ignore, don't send response } else if (idToWorker.contains(id)) { sender ! RegisterWorkerFailed("Duplicate worker ID") } else { val worker = new WorkerInfo(id, workerHost, workerPort, cores, memory, sender, workerUiPort, publicAddress) if (registerWorker(worker)) { persistenceEngine.addWorker(worker) sender ! RegisteredWorker(masterUrl, masterWebUiUrl) schedule() } else { val workerAddress = worker.actor.path.address logWarning("Worker registration failed. Attempted to re-register worker at same " + "address: " + workerAddress) sender ! RegisterWorkerFailed("Attempted to re-register worker at same address: " + workerAddress) } } }
上面代码大致理解为:如果当前Master为Active状态(HA模式下),并且目标work没有成功注册过,此时将work信息添加到workers, idToWorker, addressToWorker, 并向Worker Actor 发送RegisteredWorker消息,否则发送 RegisterWorkerFailed 消息。
当Worker 收到 RegisteredWorker 消息后,更新Master信息,并向Master按心跳时间间隔不停的发送 Heartbeat 消息,以告诉当前Worker 是“活”的。
另外,Worker 还会创建 Worker Metrics System。
至此,Master、Worker(s)都已在各自主机启动,并连接成功。