Hive on Spark源码分析(一)—— SparkTask

本文详细解析了Hive on Spark的任务提交流程及远程模式下的工作原理,介绍了SparkTask的核心方法execute如何创建SparkSession,提交SparkWork任务,并通过SparkJobRef监控任务执行状态。

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

Hive on Spark源码分析(一)—— SparkTask
Hive on Spark源码分析(二)—— SparkSession与HiveSparkClient
Hive on Spark源码分析(三)—— SparkClilent与SparkClientImpl(上)
Hive on Spark源码分析(四)—— SparkClilent与SparkClientImpl(下)
Hive on Spark源码分析(五)—— RemoteDriver
Hive on Spark源码分析(六)—— RemoteSparkJobMonitor与JobHandle 


之所以首先分析SparkTask的源码的原因,是根据Hive on Spark的运行模式和任务提交流程的出来的。首先我们看一下Hive on Spark运行模式:

       Hive on Spark(HOS)目前支持两种运行模式:本地(local)和远程(remote)。当用户把Spark Master URL设置为local时,采用本地模式;其余情况则采用远程模式。本地模式下,SparkContext与客户端运行在同一个JVM中;远程模式下,SparkContext运行在一个独立的JVM中。本地模式通常仅用于调试。因此我们主要分析一下远程模式(Remote SparkContext,RSC)。下图展示了RSC的工作原理。



图1

        用户的每个Session会创建一个SparkClient,SparkClient会启动RemoteDriver进程,并由RemoteDriver创建SparkContext。SparkTask执行时,通过Session提交任务,任务的主体就是相应的SparkWork。SparkClient将任务提交给RemoteDriver,并返回一个SparkJobRef,通过该SparkJobRef,客户端可以监控任务执行进度,进行错误处理,以及采集统计信息等。由于最终的RDD计算没有返回结果,因此客户端只需要监控执行进度而不需要处理返回值。RemoteDriver通过SparkListener收集任务级别的统计数据,通过Accumulator收集Operator级别的统计数据(Accumulator被包装为SparkCounter),并在任务结束时返回给SparkClient。

       SparkClient与RemoteDriver之间通过基于Netty的RPC进行通信。除了提交任务,SparkClient还提供了诸如添加Jar包、获取集群信息等接口。如果客户端需要使用更一般的SparkContext的功能,可以自定义一个任务并通过SparkClient发送到RemoteDriver上执行。

       因此在接下里的几篇文章里,我将会针对上面提到的Session、SparkClient、RemoteDriver,以及与job监控相关的JobMonitor进行分析,并且主要针对远程模式的相关实现类。

       首先根据上面的运行模式介绍可知,Session是HOS提交任务的起点。而在HOS的代码中,Session是在SparkTask中创建的,然后一步一步将Task中包含的SparkWork进行提交。SparkTask继承了Hive中各种任务类型统一的父类Task。SparkTask的核心是execute方法,该方法负责session的创建,以及SparkWork的提交。

下面结合代码看一下具体实现。首先是创建session和管理session的sessionManager
     
  1. @Override
  2. public int execute(DriverContext driverContext) {
  3. //返回码初始为0
  4. int rc = 0;
  5. //创建session,以及用来管理多个session的sessionManager
  6. SparkSession sparkSession = null;
  7. SparkSessionManager sparkSessionManager = null;
  8. try {
  9. //打印一些提示能够控制sparkWork的reducer数目的参数的信息
  10. printConfigInfo();
  11. sparkSessionManager = SparkSessionManagerImpl.getInstance();
  12. sparkSession = SparkUtilities.getSparkSession(conf, sparkSessionManager);
其实在getSparkSession的过程中,经过一系列调用,最终会创建一个RpcServer实例,这个RpcServer是与sparkSession和SparkClient在同一个线程中,用来与RemoteDriver端的clientRpc进行通信,提交任务,处理返回信息。
具体的调用链为:SparkUtilities.getSession => SparkSessionManagerImpl.getSession =>  SparkSessionManagerImpl.setup => SparkClientFactory.initialize => new RpcServer。

new RpcServer中通过以下代码创建一个ServerBootstrap
      
  1. public RpcServer(Map<String, String> mapConf) throws IOException, InterruptedException {
  2. this.config = new RpcConfiguration(mapConf);
  3. this.group = new NioEventLoopGroup(
  4. this.config.getRpcThreadCount(),
  5. new ThreadFactoryBuilder()
  6. .setNameFormat("RPC-Handler-%d")
  7. .setDaemon(true)
  8. .build());
  9. this.channel = new ServerBootstrap()
  10. .group(group)
  11. .channel(NioServerSocketChannel.class)
  12. .childHandler(new ChannelInitializer<SocketChannel>() {
  13. @Override
  14. public void initChannel(SocketChannel ch) throws Exception {
  15. SaslServerHandler saslHandler = new SaslServerHandler(config);
  16. final Rpc newRpc = Rpc.createServer(saslHandler, config, ch, group);
  17. saslHandler.rpc = newRpc;
  18. Runnable cancelTask = new Runnable() {
  19. @Override
  20. public void run() {
  21. LOG.warn("Timed out waiting for hello from client.");
  22. newRpc.close();
  23. }
  24. };
  25. saslHandler.cancelTask = group.schedule(cancelTask,
  26. RpcServer.this.config.getServerConnectTimeoutMs(),
  27. TimeUnit.MILLISECONDS);
  28. }
  29. })
  30. .option(ChannelOption.SO_BACKLOG, 1)
  31. .option(ChannelOption.SO_REUSEADDR, true)
  32. .childOption(ChannelOption.SO_KEEPALIVE, true)
  33. .bind(0)
  34. .sync()
  35. .channel();
  36. this.port = ((InetSocketAddress) channel.localAddress()).getPort();
  37. this.pendingClients = Maps.newConcurrentMap();
  38. this.address = this.config.getServerAddress();
  39. }
这个RpcServer,以及后面会在RemoteDriver中创建的Rpc,就是图1中HiveClient与RemoteDriver通信的基础。

下面继续回到execute方法中。上面创建好session后,接下来获取sparkWork,通过sparkSession.submit方法想SparkClient提交任务。提交任务具体的调用过程是:sparkSession.submit => RemoteHiveSparkClient.execute => RemoteHiveSparkClient.submit =>
SparkClientImpl.submit => ClientProtocol.submit,并且最终的提交是异步的。返回的 jobRef是一个spark job的引用,包括jobId,jobStatus和jobHandle等信息,后面会用来监控任务执行状态
      
  1. SparkWork sparkWork = getWork();
  2. sparkWork.setRequiredCounterPrefix(getOperatorCounters());
  3. perfLogger.PerfLogBegin(CLASS_NAME, PerfLogger.SPARK_SUBMIT_JOB);
  4. SparkJobRef jobRef = sparkSession.submit(driverContext, sparkWork);

调用monitorJob监控提交的job。monitorJob通过最终调用的是RemoteSparkJobMonitor的startMonitor,循环获取job的执行状态,并根据不同状态修改rc并返回
       
  1. rc = jobRef.monitorJob();

远程模式下jobRef最终获得一个RemoteSparkJobStatus,该类中定义了获得具体的job信息的方法.其实现通过类中维护的JobHandle引用, 以delegate的方式,通过jobHandle的相关方法返回具体信息
      
  1. SparkJobStatus sparkJobStatus = jobRef.getSparkJobStatus();

下面根据rc的值进行不同的处理。当rc==0时,表示任务执行成功,这里则记录任务执行情况的统计数据
      
  1. if (rc == 0) {
  2. SparkStatistics sparkStatistics = sparkJobStatus.getSparkStatistics();
  3. if (LOG.isInfoEnabled() && sparkStatistics != null) {
  4. LOG.info(String.format("=====Spark Job[%s] statistics=====", jobRef.getJobId()));
  5. logSparkStatistic(sparkStatistics);
  6. }
  7. LOG.info("Execution completed successfully");

当rc==2时,表示任务提交超时,此时取消任务
      
  1. } else if (rc == 2) {
  2. jobRef.cancelJob();
  3. }
  4. sparkJobStatus.cleanup();

在remoteSparkJobMonitor.startMonitor中,只有抛出异常时会将rc置为1,因此这里不需要单独判断,直接在catch中处理
       
  1. } catch (Exception e) {
  2. String msg = "Failed to execute spark task, with exception \'" + Utilities.getNameMessage(e) + "\'";
  3. // Has to use full name to make sure it does not conflict with
  4. // org.apache.commons.lang.StringUtils
  5. console.printError(msg, "\\n" + org.apache.hadoop.util.StringUtils.stringifyException(e));
  6. LOG.error(msg, e);
  7. rc = 1;

最后通过Utilities.clearWork清楚work的相关信息,删除任务目录
        
  1. } finally {
  2. Utilities.clearWork(conf);
  3. if (sparkSession != null && sparkSessionManager != null) {
  4. rc = close(rc);
  5. try {
  6. sparkSessionManager.returnSession(sparkSession);
  7. } catch (HiveException ex) {
  8. LOG.error("Failed to return the session to SessionManager", ex);
  9. }
  10. }
  11. }
  12. return rc;
  13. }

简单总结:spark session将SparkTask中的SparkWork进行异步提交(具体提交任务链会在后面的文章中一步一步都分析到),并获得一个所提交任务的引用jobRef,通过这个引用可以对job进行监控,在一定时间阈值内循环获取异步任务的执行状态,并相应的修改返回码为不同的值,最终根据返回码的不同值进行不同的处理,对任务结果做出标识。

SparkTask中还有如上面提到的printConfigInfo、addToHistory,以及其他一些方法,实现都比较简单,而且不涉及到任务提交流程,所以感兴趣的同学可以自己阅读一些相关源码。

参考文献:






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值