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

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的工作原理。


Hive on Spark源码分析(一)—— SparkTask_第1张图片

图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,以及其他一些方法,实现都比较简单,而且不涉及到任务提交流程,所以感兴趣的同学可以自己阅读一些相关源码。

参考文献:
[1]. I ntel李锐:Hive on Spark解析






你可能感兴趣的:(Hive,on,Spark)