http://blog.csdn.net/anzhsoft/article/details/30802603

Spark的Cluster Manager可以有几种部署模式:

  1. Standlone
  2. Mesos
  3. YARN
  4. EC2
  5. Local

在向集群提交计算任务后,系统的运算模型就是Driver Program定义的SparkContext向APP Master提交,有APP Master进行计算资源的调度并最终完成计算。具体阐述可以阅读《Spark:大数据的电花火石!》。

那么Standalone模式下,Client,Master和Worker是如何进行通信,注册并开启服务的呢?

1. node之间的RPC - akka

模块间通信有很多成熟的实现,现在很多成熟的Framework已经早已经让我们摆脱原始的Socket编程了。简单归类,可以归纳为基于消息的传递和基于资源共享的同步机制。

基于消息的传递的机制应用比较广泛的有Message Queue。Message Queue, 是一种应用程序对应用程序的通信方法。应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们。消 息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术。排队指的是应用程序通过 队列来通信。队列的使用除去了接收和发送应用程序同时执行的要求。其中较为成熟的MQ产品有IBM
WEBSPHERE MQ和RabbitMQ(AMQP的开源实现,现在由Pivotal维护)。

还有不得不提的是ZeroMQ,一个致力于进入Linux内核的基于Socket的编程框架。官方的说法: “ZeroMQ是一个简单好用的传输层,像框架一样的一个socket library,它使得Socket编程更加简单、简洁和性能更高。是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩。ZMQ的明确目标是“成为标准网络协议栈的一部分,之后进入Linux内核”。

Spark在很多模块之间的通信选择是Scala原生支持的akka,一个用 Scala 编写的库,用于简化编写容错的、高可伸缩性的 Java 和 Scala 的 Actor 模型应用。akka有以下5个特性:

  1. 易于构建并行和分布式应用 (Simple Concurrency & Distribution):  Akka在设计时采用了异步通讯和分布式架构,并对上层进行抽象,如Actors、Futures ,STM等。
  2. 可靠性(Resilient by Design): 系统具备自愈能力,在本地/远程都有监护。
  3. 高性能(High Performance):在单机中每秒可发送50,000,000个消息。内存占用小,1GB内存中可保存2,500,000个actors。
  4. 弹性,无中心(Elastic — Decentralized):自适应的负责均衡,路由,分区,配置
  5. 可扩展(Extensible):可以使用Akka 扩展包进行扩展。

在Spark中的Client,Master和Worker实际上都是一个actor,拿Client来说:

[java] view
plain
copy

  1. import akka.actor._
  2. import akka.pattern.ask
  3. import akka.remote.{AssociationErrorEvent, DisassociatedEvent, RemotingLifecycleEvent}
  4. private class ClientActor(driverArgs: ClientArguments, conf: SparkConf) extends Actor with Logging {
  5. var masterActor: ActorSelection = _
  6. val timeout = AkkaUtils.askTimeout(conf)
  7. override def preStart() = {
  8. masterActor = context.actorSelection(Master.toAkkaUrl(driverArgs.master))
  9. context.system.eventStream.subscribe(self, classOf[RemotingLifecycleEvent])
  10. println(s"Sending ${driverArgs.cmd} command to ${driverArgs.master}")
  11. driverArgs.cmd match {
  12. case "launch" =>
  13. ...
  14. masterActor ! RequestSubmitDriver(driverDescription)
  15. case "kill" =>
  16. val driverId = driverArgs.driverId
  17. val killFuture = masterActor ! RequestKillDriver(driverId)
  18. }
  19. }
  20. override def receive = {
  21. case SubmitDriverResponse(success, driverId, message) =>
  22. println(message)
  23. if (success) pollAndReportStatus(driverId.get) else System.exit(-1)
  24. case KillDriverResponse(driverId, success, message) =>
  25. println(message)
  26. if (success) pollAndReportStatus(driverId) else System.exit(-1)
  27. case DisassociatedEvent(_, remoteAddress, _) =>
  28. println(s"Error connecting to master ${driverArgs.master} ($remoteAddress), exiting.")
  29. System.exit(-1)
  30. case AssociationErrorEvent(cause, _, remoteAddress, _) =>
  31. println(s"Error connecting to master ${driverArgs.master} ($remoteAddress), exiting.")
  32. println(s"Cause was: $cause")
  33. System.exit(-1)
  34. }
  35. }
  36. /**
  37. * Executable utility for starting and terminating drivers inside of a standalone cluster.
  38. */
  39. object Client {
  40. def main(args: Array[String]) {
  41. println("WARNING: This client is deprecated and will be removed in a future version of Spark.")
  42. println("Use ./bin/spark-submit with \"--master spark://host:port\"")
  43. val conf = new SparkConf()
  44. val driverArgs = new ClientArguments(args)
  45. if (!driverArgs.logLevel.isGreaterOrEqual(Level.WARN)) {
  46. conf.set("spark.akka.logLifecycleEvents", "true")
  47. }
  48. conf.set("spark.akka.askTimeout", "10")
  49. conf.set("akka.loglevel", driverArgs.logLevel.toString.replace("WARN", "WARNING"))
  50. Logger.getRootLogger.setLevel(driverArgs.logLevel)
  51. // TODO: See if we can initialize akka so return messages are sent back using the same TCP
  52. //       flow. Else, this (sadly) requires the DriverClient be routable from the Master.
  53. val (actorSystem, _) = AkkaUtils.createActorSystem(
  54. "driverClient", Utils.localHostName(), 0, conf, new SecurityManager(conf))
  55. actorSystem.actorOf(Props(classOf[ClientActor], driverArgs, conf))
  56. actorSystem.awaitTermination()
  57. }
  58. }

其中第19行的含义就是向Master提交Driver的请求,

[java] view
plain
copy

  1. masterActor ! RequestSubmitDriver(driverDescription)

而Master将在receive里处理这个请求。当然了27行到44行的是处理Client Actor收到的消息。

可以看出,通过akka,可以非常简单高效的处理模块间的通信,这可以说是Spark RPC的一大特色。

2. Client,Master和Workerq启动通信详解

源码位置:spark-1.0.0\core\src\main\scala\org\apache\spark\deploy。主要涉及的类:Client.scala, Master.scala和Worker.scala。这三大模块之间的通信框架如下图。

Standalone模式下存在的角色:

  1. Client:负责提交作业到Master。

  2. Master:接收Client提交的作业,管理Worker,并命令Worker启动Driver和Executor。

  3. Worker:负责管理本节点的资源,定期向Master汇报心跳,接收Master的命令,比如启动Driver和Executor。

实际上,Master和Worker要处理的消息要比这多得多,本图只是反映了集群启动和向集群提交运算时候的主要消息处理。

接下来将分别走读这三大角色的源码。

2.1 Client源码解析

Client启动:

[java] view
plain
copy

  1. object Client {
  2. def main(args: Array[String]) {
  3. println("WARNING: This client is deprecated and will be removed in a future version of Spark.")
  4. println("Use ./bin/spark-submit with \"--master spark://host:port\"")
  5. val conf = new SparkConf()
  6. val driverArgs = new ClientArguments(args)
  7. if (!driverArgs.logLevel.isGreaterOrEqual(Level.WARN)) {
  8. conf.set("spark.akka.logLifecycleEvents", "true")
  9. }
  10. conf.set("spark.akka.askTimeout", "10")
  11. conf.set("akka.loglevel", driverArgs.logLevel.toString.replace("WARN", "WARNING"))
  12. Logger.getRootLogger.setLevel(driverArgs.logLevel)
  13. // TODO: See if we can initialize akka so return messages are sent back using the same TCP
  14. //       flow. Else, this (sadly) requires the DriverClient be routable from the Master.
  15. val (actorSystem, _) = AkkaUtils.createActorSystem(
  16. "driverClient", Utils.localHostName(), 0, conf, new SecurityManager(conf))
  17. // 使用ClientActor初始化actorSystem
  18. actorSystem.actorOf(Props(classOf[ClientActor], driverArgs, conf))
  19. //启动并等待actorSystem的结束
  20. actorSystem.awaitTermination()
  21. }
  22. }

从行21可以看出,核心实现是由ClientActor实现的。Client的Actor是akka.Actor的一个扩展。对于Actor,从它对recevie的override就可以看出它需要处理的消息。

[java] view
plain
copy

  1. override def receive = {
  2. case SubmitDriverResponse(success, driverId, message) =>
  3. println(message)
  4. if (success) pollAndReportStatus(driverId.get) else System.exit(-1)
  5. case KillDriverResponse(driverId, success, message) =>
  6. println(message)
  7. if (success) pollAndReportStatus(driverId) else System.exit(-1)
  8. case DisassociatedEvent(_, remoteAddress, _) =>
  9. println(s"Error connecting to master ${driverArgs.master} ($remoteAddress), exiting.")
  10. System.exit(-1)
  11. case AssociationErrorEvent(cause, _, remoteAddress, _) =>
  12. println(s"Error connecting to master ${driverArgs.master} ($remoteAddress), exiting.")
  13. println(s"Cause was: $cause")
  14. System.exit(-1)
  15. }

2.2 Master的源码分析

源码分析详见注释。

[java] view
plain
copy

  1. override def receive = {
  2. case ElectedLeader => {
  3. // 被选为Master,首先判断是否该Master原来为active,如果是那么进行Recovery。
  4. }
  5. case CompleteRecovery => completeRecovery() // 删除没有响应的worker和app,并且将所有没有worker的Driver分配worker
  6. case RevokedLeadership => {
  7. // Master将关闭。
  8. }
  9. case RegisterWorker(id, workerHost, workerPort, cores, memory, workerUiPort, publicAddress) =>
  10. {
  11. // 如果该Master不是active,不做任何操作,返回
  12. // 如果注册过该worker id,向sender返回错误
  13. sender ! RegisterWorkerFailed("Duplicate worker ID")
  14. // 注册worker,如果worker注册成功则返回成功的消息并且进行调度
  15. sender ! RegisteredWorker(masterUrl, masterWebUiUrl)
  16. schedule()
  17. // 如果worker注册失败,发送消息到sender
  18. sender ! RegisterWorkerFailed("Attempted to re-register worker at same address: " + workerAddress)
  19. }
  20. case RequestSubmitDriver(description) => {
  21. // 如果master不是active,返回错误
  22. sender ! SubmitDriverResponse(false, None, msg)
  23. // 否则创建driver,返回成功的消息
  24. sender ! SubmitDriverResponse(true, Some(driver.id), s"Driver successfully submitted as ${driver.id}")
  25. }
  26. }
  27. case RequestKillDriver(driverId) => {
  28. if (state != RecoveryState.ALIVE) {
  29. // 如果master不是active,返回错误
  30. val msg = s"Can only kill drivers in ALIVE state. Current state: $state."
  31. sender ! KillDriverResponse(driverId, success = false, msg)
  32. } else {
  33. logInfo("Asked to kill driver " + driverId)
  34. val driver = drivers.find(_.id == driverId)
  35. driver match {
  36. case Some(d) =>
  37. //如果driver仍然在等待队列,从等待队列删除并且更新driver状态为KILLED
  38. } else {
  39. // 通知worker kill driver id的driver。结果会由workder发消息给master ! DriverStateChanged
  40. d.worker.foreach { w => w.actor ! KillDriver(driverId) }
  41. }
  42. // 注意,此时driver不一定被kill,master只是通知了worker去kill driver。
  43. sender ! KillDriverResponse(driverId, success = true, msg)
  44. case None =>
  45. // driver已经被kill,直接返回结果
  46. sender ! KillDriverResponse(driverId, success = false, msg)
  47. }
  48. }
  49. }
  50. case RequestDriverStatus(driverId) => {
  51. // 查找请求的driver,如果找到则返回driver的状态
  52. (drivers ++ completedDrivers).find(_.id == driverId) match {
  53. case Some(driver) =>
  54. sender ! DriverStatusResponse(found = true, Some(driver.state),
  55. driver.worker.map(_.id), driver.worker.map(_.hostPort), driver.exception)
  56. case None =>
  57. sender ! DriverStatusResponse(found = false, None, None, None, None)
  58. }
  59. }
  60. case RegisterApplication(description) => {
  61. //如果是standby,那么忽略这个消息
  62. //否则注册application;返回结果并且开始调度
  63. }
  64. case ExecutorStateChanged(appId, execId, state, message, exitStatus) => {
  65. // 通过idToApp获得app,然后通过app获得executors,从而通过execId获得executor
  66. val execOption = idToApp.get(appId).flatMap(app => app.executors.get(execId))
  67. execOption match {
  68. case Some(exec) => {
  69. exec.state = state
  70. exec.application.driver ! ExecutorUpdated(execId, state, message, exitStatus)
  71. if (ExecutorState.isFinished(state)) {
  72. val appInfo = idToApp(appId)
  73. // Remove this executor from the worker and app
  74. logInfo("Removing executor " + exec.fullId + " because it is " + state)
  75. appInfo.removeExecutor(exec)
  76. exec.worker.removeExecutor(exec)
  77. }
  78. }
  79. case DriverStateChanged(driverId, state, exception) => {
  80. //  如果Driver的state为ERROR | FINISHED | KILLED | FAILED, 删除它。
  81. }
  82. case Heartbeat(workerId) => {
  83. // 更新worker的时间戳 workerInfo.lastHeartbeat = System.currentTimeMillis()
  84. }
  85. case MasterChangeAcknowledged(appId) => {
  86. //  将appId的app的状态置为WAITING,为切换Master做准备。
  87. }
  88. case WorkerSchedulerStateResponse(workerId, executors, driverIds) => {
  89. // 通过workerId查找到worker,那么worker的state置为ALIVE,
  90. // 并且查找状态为idDefined的executors,并且将这些executors都加入到app中,
  91. // 然后保存这些app到worker中。可以理解为Worker在Master端的Recovery
  92. idToWorker.get(workerId) match {
  93. case Some(worker) =>
  94. logInfo("Worker has been re-registered: " + workerId)
  95. worker.state = WorkerState.ALIVE
  96. val validExecutors = executors.filter(exec => idToApp.get(exec.appId).isDefined)
  97. for (exec <- validExecutors) {
  98. val app = idToApp.get(exec.appId).get
  99. val execInfo = app.addExecutor(worker, exec.cores, Some(exec.execId))
  100. worker.addExecutor(execInfo)
  101. execInfo.copyState(exec)
  102. }
  103. // 将所有的driver设置为RUNNING然后加入到worker中。
  104. for (driverId <- driverIds) {
  105. drivers.find(_.id == driverId).foreach { driver =>
  106. driver.worker = Some(worker)
  107. driver.state = DriverState.RUNNING
  108. worker.drivers(driverId) = driver
  109. }
  110. }
  111. }
  112. }
  113. case DisassociatedEvent(_, address, _) => {
  114. // 这个请求是Worker或者是App发送的。删除address对应的Worker和App
  115. // 如果Recovery可以结束,那么结束Recovery
  116. }
  117. case RequestMasterState => {
  118. //向sender返回master的状态
  119. sender ! MasterStateResponse(host, port, workers.toArray, apps.toArray, completedApps.toArray, drivers.toArray, completedDrivers.toArray, state)
  120. }
  121. case CheckForWorkerTimeOut => {
  122. //删除超时的Worker
  123. }
  124. case RequestWebUIPort => {
  125. //向sender返回web ui的端口号
  126. sender ! WebUIPortResponse(webUi.boundPort)
  127. }
  128. }

2.3 Worker 源码解析

通过对Client和Master的源码解析,相信你也知道如何去分析Worker是如何和Master进行通信的了,没错,答案就在下面:

[java] view
plain
copy

  1. override def receive

Spark技术内幕:Client,Master和Worker 通信源码解析的更多相关文章

  1. Spark技术内幕: Task向Executor提交的源码解析

    在上文<Spark技术内幕:Stage划分及提交源码分析>中,我们分析了Stage的生成和提交.但是Stage的提交,只是DAGScheduler完成了对DAG的划分,生成了一个计算拓扑, ...

  2. Spark技术内幕:Master的故障恢复

    Spark技术内幕:Master基于ZooKeeper的High Availability(HA)源码实现  详细阐述了使用ZK实现的Master的HA,那么Master是如何快速故障恢复的呢? 处于 ...

  3. Spark技术内幕:Stage划分及提交源码分析

    http://blog.csdn.net/anzhsoft/article/details/39859463 当触发一个RDD的action后,以count为例,调用关系如下: org.apache. ...

  4. Spark技术内幕:Master基于ZooKeeper的High Availability(HA)源码实现

    如果Spark的部署方式选择Standalone,一个采用Master/Slaves的典型架构,那么Master是有SPOF(单点故障,Single Point of Failure).Spark可以 ...

  5. Spark技术内幕:Master基于ZooKeeper的High Availability(HA)源代码实现

    假设Spark的部署方式选择Standalone,一个採用Master/Slaves的典型架构,那么Master是有SPOF(单点故障,Single Point of Failure).Spark能够 ...

  6. 6.Spark streaming技术内幕 : Job动态生成原理与源码解析

    原创文章,转载请注明:转载自 周岳飞博客(http://www.cnblogs.com/zhouyf/)   Spark streaming 程序的运行过程是将DStream的操作转化成RDD的操作, ...

  7. Spark技术内幕: Task向Executor提交的源代码解析

    在上文<Spark技术内幕:Stage划分及提交源代码分析>中,我们分析了Stage的生成和提交.可是Stage的提交,仅仅是DAGScheduler完毕了对DAG的划分,生成了一个计算拓 ...

  8. Spark技术内幕:Client,Master和Worker 通信源代码解析

    Spark的Cluster Manager能够有几种部署模式: Standlone Mesos YARN EC2 Local 在向集群提交计算任务后,系统的运算模型就是Driver Program定义 ...

  9. Spark技术内幕:Worker源码与架构解析

    首先通过一张Spark的架构图来了解Worker在Spark中的作用和地位: Worker所起的作用有以下几个: 1. 接受Master的指令,启动或者杀掉Executor 2. 接受Master的指 ...

随机推荐

  1. CUDA基本概念

    CUDA计算模型 CUDA中计算分为两部分,串行部分在Host上执行,即CPU,而并行部分在Device上执行,即GPU. 相比传统的C语言,CUDA增加了一些扩展,包括了库和关键字. CUDA代码提 ...

  2. 4.3 Reduction代码(Heterogeneous Parallel Programming class lab)

    首先添加上Heterogeneous Parallel Programming class 中 lab: Reduction的代码: myReduction.c // MP Reduction // ...

  3. nyoj 845 无主之地1

    无主之地1 时间限制:1000 ms  |  内存限制:65535 KB 难度:0   描述 子晓最近在玩无主之地1,他对这个游戏的评价不错,结合了FPS与RPG元素,可玩度很高.不过,他发现了一代的 ...

  4. [iOS基础控件 - 5.1] UIScrollView

    A.需要掌握 UIScrollView 是一个能够滚动的视图控件,可以用来展示大量内容,如手机的“设置” 1.常见属性 2.常用代理方法 3.缩放 4.UIScrollView和UIPageContr ...

  5. 详解Objective-C的meta-class

    比较简单的一篇英文,重点是讲解meta-class.翻译下,加深理解. 原文标题:What is a meta-class in Objective-C? 原文地址:http://www.cocoaw ...

  6. C++中实现从std::string类型到bool型的转换

    利用输入字符串流:std::istringstream bool b; std::string s = "true"; std::istringstream(s) >> ...

  7. java入门学习(十一)逻辑运算符和位运算符

    请关注我的博客:www.taomaipin.com 家里有急事 暂停了几天,抱歉,现在呢开始说说java的运算语句和运算符 如果想利用java的运算语句 那么就离不开java的运算符,前面第九章讲了j ...

  8. Android设计模式系列--原型模式

    CV一族,应该很容易理解原型模式的原理,复制,粘贴完后看具体情况是否修改,其实这就是原型模式.从java的角度看,一般使用原型模式有个明显的特点,就是实现cloneable的clone()方法.原型模 ...

  9. 2014 ACM/ICPC 鞍山赛区现场赛 D&amp;I 解题报告

    鞍山现场赛结束了呢-- 我们出的是D+E+I三道题-- 吾辈AC掉的是D和I两道,趁着还记得.先在这里写一写我写的两道水题D&I的解题报告吧^_^. D题的意思呢是说星云内有一堆排成一条直线的 ...

  10. ThinkPHP CURD方法盘点:field方法

    ThinkPHP的CURD操作中有很多非常实用的方法,从这篇开始,我们会为大家一一介绍. 首先为大家介绍下field方法的用法.field属于模型的连贯操作方法之一,主要目的是标识要返回或者操作的字段 ...