【原】Spark中Client源码分析(一)

本文深入解析了Spark Standalone模式下的AppClient组件,介绍了其主要职责、关键字段与方法。包括AppClient如何通过ClientEndpoint与Master通信,实现App的注册、更新、注销等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在Spark Standalone中我们所谓的Client,它的任务其实是由AppClient和DriverClient共同完成的。AppClient是一个允许app(Client)和Spark集群通信的中间人,接受master URL、app的信息、一个集群事件的监听器以及事件监听的回调函数,主要和Master交互App相关的信息,DriverClient主要用于和Master交互Driver相关的信息,比如启动、停止及运行状况等,本篇先介绍AppClient。

1.AppClient类主要字段、方法如下:

AppClient类成员
由上图我们可以知道,ClientEndpoint是作为AppClient的一个私有类存在的。
(1)stop方法如下所示,主要用于向master发送消息,停止并注销app。

def stop() {
if (endpoint != null) {
try {
//返回Rpc ask的超时时间120s
val timeout = RpcUtils.askRpcTimeout(conf)
//client向master发送注销app的信息,在120s内如果不响应,那么将抛RpcTimeoutException
timeout.awaitResult(endpoint.askBoolean)
} catch {
case e: TimeoutException =>
logInfo("Stop request to Master timed out; it may already be shut down.")
}
endpoint = null
}
}

下面我们重点看ClientEndpoint,它是线程安全的。

2.ClientEndpoint

2.1属性

(1)//设置一个boolean标识,用于避免多次调用listener.disconnected()
private var alreadyDisconnected = false
(2)//app向master申请注册的线程池,因为被maser注册是一个阻塞操作,所以线程池的个数是"masterRpcAddresses.size",这样app就能同时被所有的master注册
private val registerMasterThreadPool = new ThreadPoolExecutor(
0,
masterRpcAddresses.size, // Make sure we can register with all masters at the same time
60L, TimeUnit.SECONDS,
new SynchronousQueueRunnable,
ThreadUtils.namedThreadFactory("appclient-register-master-threadpool"))
(3)一个守护单线程用于申请注册操作
private val registrationRetryThread =
ThreadUtils.newDaemonSingleThreadScheduledExecutor("appclient-registration-retry-thread")

2.2方法

(1)构造函数为ClientEndpoint的主构造器。
(2)onStart方法,用于将App注册到所有的Master上

override def onStart(): Unit = {
try {
//“1”表示第几次注册,最大次数不超过3次,第n次申请注册到master上
registerWithMaster(1)详见下①
} catch {
case e: Exception =>
logWarning("Failed to connect to master", e)
//监听器停止并将boolen状态标识设置为true
markDisconnected()
//停止rpcendpoint
stop()
}
}

①registerWithMaster方法如下,用于异步注册到所有的master上,如果没有超过再次注册的次数(3次),那么每20s将会重新调用该方法申请注册,如果注册成功,所有的调用work和futures将会被取消。

private def registerWithMaster(nthRetry: Int) {
registerMasterFutures = tryRegisterAllMasters()
registrationRetryTimer = registrationRetryThread.scheduleAtFixedRate(new Runnable {
override def run(): Unit = {
Utils.tryOrExit {
if (registered) {
registerMasterFutures.foreach(.cancel(true))
registerMasterThreadPool.shutdownNow()
} else if (nthRetry >= REGISTRATION_RETRIES) {
markDead("All masters are unresponsive! Giving up.")
} else {
registerMasterFutures.foreach(
.cancel(true))
registerWithMaster(nthRetry + 1)
}
}
}
}, REGISTRATION_TIMEOUT_SECONDS, REGISTRATION_TIMEOUT_SECONDS, TimeUnit.SECONDS)
}

(3)onstop方法如下,释放资源。

override def onStop(): Unit = {
if (registrationRetryTimer != null) {
registrationRetryTimer.cancel(true)
}
registrationRetryThread.shutdownNow()
registerMasterFutures.foreach(_.cancel(true))
registerMasterThreadPool.shutdownNow()
}

(4)receive方法,receive接受到的消息分为5种,分别为

  • (1)app被master成功注册,并将注册成功的app添加到监听器中

    case RegisteredApplication(appId_, masterRef) => appId = appId_ registered = true master = Some(masterRef) listener.connected(appId)
  • (2)移除app,停止rpcendpoint

    case ApplicationRemoved(message) => markDead("Master removed our application: %s".format(message))
    stop()
  • (3)向master申请为app添加executor,并添加到监听器中

    case ExecutorAdded(id: Int, workerId: String, hostPort: String, cores: Int, memory: Int) =>val fullId = appId + "/" + id
    logInfo("Executor added: %s on %s (%s) with %d cores".format(fullId, workerId, hostPort, cores))
    sendToMaster(ExecutorStateChanged(appId, id, ExecutorState.RUNNING, None, None))
    listener.executorAdded(fullId, workerId, hostPort, cores, memory)
  • (4)Executor的信息发生改变,记录到日志中
    case ExecutorUpdated(id, state, message, exitStatus) =>
    val fullId = appId + "/" + id
    val messageText = message.map(s => " (" + s + ")").getOrElse("")
    logInfo("Executor updated: %s is now %s%s".format(fullId, state, messageText))
    if (ExecutorState.isFinished(state)) {
    listener.executorRemoved(fullId, message.getOrElse(""), exitStatus) }
  • (5)HA机制,为app更换master

    case MasterChanged(masterRef, masterWebUiUrl) =>
    logInfo("Master has changed, new master is at " + masterRef.address.toSparkURL)
    master = Some(masterRef)
    alreadyDisconnected = false
    masterRef.send(MasterChangeAcknowledged(appId))

转载于:https://www.cnblogs.com/yourarebest/p/5313006.html

`spark-submit` 是 Apache Spark 提供的个用于提交应用程序到集群运行的命令行工具。它简化了配置和启动过程,并且可以兼容多种集群管理器如 YARN、Mesos 或者 Kubernetes。 ### 源码解析 #### 主流程概述 当你通过 `spark-submit` 命令提交任务时,实际上是在调用 Scala 编写的主程序入口点——即 org.apache.spark.deploy.SparkSubmit 类里的 main 函数。以下是其大致工作流: 1. **加载默认配置** - 使用内部预设值初始化些必要的参数; 2. **处理用户输入选项** - 解析来自命令行提供的所有自定义设置并覆盖之前的默认配置项; 3. **准备环境变量与依赖包分发** - 根据指定模式将应用所需的 jar 文件等资源上传至分布式文件系统(例如 HDFS),同时调整 JVM 的 classpath 环境路径; 4. **构建SparkContext构造所需参数集** - 创建合适的 Java/Python/R Shell 进程实例化上下文对象; 5. **选择正确的Launcher去执行实际作业** - 根据所选部署模式 (standalone / yarn-client / mesos...) 来确定具体的调度策略; #### 关键步骤深入理解 为了更深入了解 `spark-submit`, 我们可以从以下几个方面着手: - 参数解析:利用 Argparse 库来读取命令行参数。 - 配置合并机制:如何优先级地组合不同来源(比如 system properties, environment variables 和 user-defined options) 的设定. - ClassLoader 加载顺序及隔离性的实现理。 - Application Master(ApplicationMaster.scala), Executor Backend(DriverWrapper.scala & ChildFirstClassLoader.java),Driver program(SparkSubmitArguments.java -> prepareSubmitEnvironment())之间的交互协议。 对于开发者来说,掌握这些细节有助于更好地调试错误以及优化性能.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值