Hive on Spark源码分析(一)—— SparkTaskHive on Spark源码分析(二)—— SparkSession与HiveSparkClientHive on Spark源码分析(三)—— SparkClilent与SparkClientImpl(上)Hive on Spark源码分析(四)—— SparkClilent与SparkClientImpl(下)Hive on Spark源码分析(五)—— RemoteDriverHive 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的task太少 hive on spark 原理_hive

图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的提交。

@Override  public int execute(DriverContext driverContext) {    //返回码初始为0    int rc = 0;    //创建session,以及用来管理多个session的sessionManager    SparkSession sparkSession = null;    SparkSessionManager sparkSessionManager = null;
    try {      //打印一些提示能够控制sparkWork的reducer数目的参数的信息      printConfigInfo();      sparkSessionManager = SparkSessionManagerImpl.getInstance();      sparkSession = SparkUtilities.getSparkSession(conf, sparkSessionManager);

其实在getSparkSession的过程中,经过一系列调用,最终会创建一个RpcServer实例,这个RpcServer是与sparkSession和SparkClient在同一个线程中,用来与RemoteDriver端的clientRpc进行通信,提交任务,处理返回信息。

SparkSessionManagerImpl.setup => SparkClientFactory.initialize => new RpcServer。

new RpcServer中通过以下代码创建一个ServerBootstrap

public RpcServer(Map<String, String> mapConf) throws IOException, InterruptedException {    this.config = new RpcConfiguration(mapConf);    this.group = new NioEventLoopGroup(        this.config.getRpcThreadCount(),        new ThreadFactoryBuilder()            .setNameFormat("RPC-Handler-%d")            .setDaemon(true)            .build());    this.channel = new ServerBootstrap()      .group(group)      .channel(NioServerSocketChannel.class)      .childHandler(new ChannelInitializer<SocketChannel>() {          @Override          public void initChannel(SocketChannel ch) throws Exception {            SaslServerHandler saslHandler = new SaslServerHandler(config);            final Rpc newRpc = Rpc.createServer(saslHandler, config, ch, group);            saslHandler.rpc = newRpc;            Runnable cancelTask = new Runnable() {                @Override                public void run() {                  LOG.warn("Timed out waiting for hello from client.");                  newRpc.close();                }            };            saslHandler.cancelTask = group.schedule(cancelTask,                RpcServer.this.config.getServerConnectTimeoutMs(),                TimeUnit.MILLISECONDS);          }      })      .option(ChannelOption.SO_BACKLOG, 1)      .option(ChannelOption.SO_REUSEADDR, true)      .childOption(ChannelOption.SO_KEEPALIVE, true)      .bind(0)      .sync()      .channel();    this.port = ((InetSocketAddress) channel.localAddress()).getPort();    this.pendingClients = Maps.newConcurrentMap();    this.address = this.config.getServerAddress();  }

这个RpcServer,以及后面会在RemoteDriver中创建的Rpc,就是图1中HiveClient与RemoteDriver通信的基础。

下面继续回到execute方法中。上面创建好session后,接下来获取sparkWork,通过sparkSession.submit方法想SparkClient提交任务。提交任务具体的调用过程是:sparkSession.submit => RemoteHiveSparkClient.execute => RemoteHiveSparkClient.submit =>

jobRef是一个spark job的引用,包括jobId,jobStatus和jobHandle等信息,后面会用来监控任务执行状态

SparkWork sparkWork = getWork();      sparkWork.setRequiredCounterPrefix(getOperatorCounters());      perfLogger.PerfLogBegin(CLASS_NAME, PerfLogger.SPARK_SUBMIT_JOB);      SparkJobRef jobRef = sparkSession.submit(driverContext, sparkWork);

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

rc = jobRef.monitorJob();

以delegate的方式,通过jobHandle的相关方法返回具体信息

SparkJobStatus sparkJobStatus = jobRef.getSparkJobStatus();

下面根据rc的值进行不同的处理。当rc==0时,表示任务执行成功,这里则记录任务执行情况的统计数据

if (rc == 0) {        SparkStatistics sparkStatistics = sparkJobStatus.getSparkStatistics();        if (LOG.isInfoEnabled() && sparkStatistics != null) {          LOG.info(String.format("=====Spark Job[%s] statistics=====", jobRef.getJobId()));          logSparkStatistic(sparkStatistics);        }        LOG.info("Execution completed successfully");

当rc==2时,表示任务提交超时,此时取消任务

} else if (rc == 2) {         jobRef.cancelJob();      }      sparkJobStatus.cleanup();

在remoteSparkJobMonitor.startMonitor中,只有抛出异常时会将rc置为1,因此这里不需要单独判断,直接在catch中处理

} catch (Exception e) {      String msg = "Failed to execute spark task, with exception \'" + Utilities.getNameMessage(e) + "\'";      // Has to use full name to make sure it does not conflict with      // org.apache.commons.lang.StringUtils      console.printError(msg, "\\n" + org.apache.hadoop.util.StringUtils.stringifyException(e));      LOG.error(msg, e);      rc = 1;

最后通过Utilities.clearWork清楚work的相关信息,删除任务目录

} finally {      Utilities.clearWork(conf);      if (sparkSession != null && sparkSessionManager != null) {        rc = close(rc);        try {          sparkSessionManager.returnSession(sparkSession);        } catch (HiveException ex) {          LOG.error("Failed to return the session to SessionManager", ex);        }      }    }    return rc;  }

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

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

参考文献:

[1]. I ntel李锐:Hive on Spark解析