Hive on Spark源码分析(六)—— RemoteSparkJobMonitor与JobHandle

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 


RemoteSparkJobMonitor负责监控一个RSC远程任务的执行状态。它会循环获取任务的执行情况,直到任务完成/失败/kill掉,并将当前任务的状态打印到console。RemoteSparkJobMonitor中主要的方法就是startMonitor,它会根据任务执行的不同情况修改返回码,调用过程是:

SparkTask.execute => rc=jobRef.monitorJob() => RemoteSparkJobRef.monitorJob() => return remoteSparkJobMonitor.startMonitor()

该方法主要内容如下:

1. RemoteSparkJobMonitor循环获取Job执行状态:

   
   
   
   
  1. JobHandle.State state = sparkJobStatus.getRemoteJobState();

    
    
    
    
  1. public JobHandle.State getRemoteJobState() {
  2. return jobHandle.getState();
  3. }

2. Job共有六种状态(state):SENT,QUEUED,STARTED,SUCCEEDED,FAILED,CANCELED。根据不同状态做不同处理,其中SENT状态和QUEUED状态使用相同的处理逻辑:

    
    
    
    
  1. switch (state) {
  2. case SENT:
  3. case QUEUED:
  4. long timeCount = (System.currentTimeMillis() - startTime) / 1000;
  5. //monitorTimeoutInteval是配置文件中hive.spark.job.monitor.timeout的值
  6. if ((timeCount > monitorTimeoutInteval)) {
  7. console.printError("Job hasn\'t been submitted after " + timeCount + "s." +
  8. " Aborting it.\\nPossible reasons include network issues, " +
  9. "errors in remote driver or the cluster has no available resources, etc.\\n" +
  10. "Please check YARN or Spark driver\'s logs for further information.\\nReason1 from RemoteSparkJobMonitor");
  11. console.printError("Status: " + state);
  12. running = false;
  13. done = true;
  14. rc = 2;
  15. }
  16. break;

如果超过预设job提交时间,则修改return code = 2,提示timeCount时间后Job仍未提交,否则继续继续等待


如果state为STARTED,打印job信息和执行进度:

   
   
   
   
  1. case STARTED:
  2.          JobExecutionStatus sparkJobState = sparkJobStatus.getState();
  3.          if (sparkJobState == JobExecutionStatus.RUNNING) {
  4.            Map<String, SparkStageProgress> progressMap = sparkJobStatus.getSparkStageProgress();
  5.            if (!running) {
  6.              perfLogger.PerfLogEnd(CLASS_NAME, PerfLogger.SPARK_SUBMIT_TO_RUNNING);
  7.              printAppInfo();
  8.              // print job stages.
  9.              console.printInfo("\nQuery Hive on Spark job["
  10.                + sparkJobStatus.getJobId() + "] stages:");
  11.              for (int stageId : sparkJobStatus.getStageIds()) {
  12.                console.printInfo(Integer.toString(stageId));
  13.              }
  14.              console.printInfo("\nStatus: Running (Hive on Spark job["
  15.                + sparkJobStatus.getJobId() + "])");
  16.              running = true;
  17.              console.printInfo("Job Progress Format\nCurrentTime StageId_StageAttemptId: "
  18.                + "SucceededTasksCount(+RunningTasksCount-FailedTasksCount)/TotalTasksCount [StageCost]");
  19.            }
  20.            printStatus(progressMap, lastProgressMap);
  21.            lastProgressMap = progressMap;
  22.          }
  23.          break;

负责打印任务信息的方法printStatus继承自父类SparkJobMonitor,它会计算总的task数、正在执行的任务数、已完成的任务数和失败的任务数,并进行格式化输出。


其余情况类似,如果状态为SUCCEEDED,打印成功或失败信息及状态码(不修改,默认为0,表示执行成功);如果为FAILED,打印错误信息,修改return code = 3。

   
   
   
   
  1. case SUCCEEDED:
  2.          Map<String, SparkStageProgress> progressMap = sparkJobStatus.getSparkStageProgress();
  3.          printStatus(progressMap, lastProgressMap);
  4.          lastProgressMap = progressMap;
  5.          double duration = (System.currentTimeMillis() - startTime) / 1000.0;
  6.          console.printInfo("Status: Finished successfully in "
  7.            + String.format("%.2f seconds", duration));
  8.          running = false;
  9.          done = true;
  10.          break;
  11.        case FAILED:
  12.          console.printError("Status: Failed");
  13.          running = false;
  14.          done = true;
  15.          rc = 3;
  16.          break;
  17.        }


如果任务还没有结束,则等待checkInterval时间,然后再次获取任务状态:

    
    
    
    
  1. if (!done) {
  2. Thread.sleep(checkInterval);
  3. }

如果捕获到异常,输出错误信息,修改return code = 1,任务标记为完成:
     
     
     
     
  1. catch (Exception e) {
  2. String msg = " with exception \'" + Utilities.getNameMessage(e) + "\'";
  3. msg = "Failed to monitor Job[ " + sparkJobStatus.getJobId() + "]" + msg;
  4. // Has to use full name to make sure it does not conflict with
  5. // org.apache.commons.lang.StringUtils
  6. LOG.error(msg, e);
  7. console.printError(msg, "\\n" + org.apache.hadoop.util.StringUtils.stringifyException(e));
  8. rc = 1;
  9. done = true;
  10. } finally {
  11. if (done) {
  12. break;
  13. }
  14. }
  15. }
以上这就是RemoteSparkJobMonitor的内容。下面看一下JobHandle。


JobHandle可以认为是一个job的句柄,用来监控和控制一个正在运行的远程任务。我们首先看一下JobHandle接口中定义的两个特殊的结构,首先state:

    
    
    
    
  1. static enum State {
  2. SENT,
  3. QUEUED,
  4. STARTED,
  5. CANCELLED,
  6. FAILED,
  7. SUCCEEDED;
  8. }
State是一个枚举类型值,表示job的当前状态,具体类型我们已经在上面提过。从SENT到SUCCEEDED,ordinal依次增加。
另外一个是一个监听器的接口Listener,用来监控job的state,其中的各种回调函数会在相应的事件发生时被调用:
     
     
     
     
  1. static interface Listener<T extends Serializable> {
  2. void onJobQueued(JobHandle<T> job);
  3. void onJobStarted(JobHandle<T> job);
  4. void onJobCancelled(JobHandle<T> job);
  5. void onJobFailed(JobHandle<T> job, Throwable cause);
  6. void onJobSucceeded(JobHandle<T> job, T result);
  7. /**
  8. * Called when a monitored Spark job is started on the remote context. This callback
  9. * does not indicate a state change in the client job's status.
  10. */
  11. void onSparkJobStarted(JobHandle<T> job, int sparkJobId);
  12. }

接口中其他的方法我们直接到JobHandleImpl 中去看具体的实现。 首先是构造方法,注意到state在构造方法中初始化为SENT状态:

   
   
   
   
  1. JobHandleImpl(SparkClientImpl client, Promise<T> promise, String jobId) {
  2.    this.client = client;
  3.    this.jobId = jobId;
  4.    this.promise = promise;
  5.    this.listeners = Lists.newLinkedList();
  6.    this.metrics = new MetricsCollection();
  7.    this.sparkJobIds = new CopyOnWriteArrayList<Integer>();
  8.    this.state = State.SENT;
  9.    this.sparkCounters = null;
  10.  }


下面这些方法都是通过JobHandleImpl内部的promise对象,以delegate的方式进行是实现。
     
     
     
     
  1. /** Requests a running job to be cancelled. */
  2. @Override
  3. public boolean cancel(boolean mayInterrupt) {
  4. if (changeState(State.CANCELLED)) {
  5. client.cancel(jobId);
  6. promise.cancel(mayInterrupt);
  7. return true;
  8. }
  9. return false;
  10. }
  11. @Override
  12. public T get() throws ExecutionException, InterruptedException {
  13. return promise.get();
  14. }
  15. @Override
  16. public T get(long timeout, TimeUnit unit)
  17. throws ExecutionException, InterruptedException, TimeoutException {
  18. return promise.get(timeout, unit);
  19. }
  20. @Override
  21. public boolean isCancelled() {
  22. return promise.isCancelled();
  23. }
  24. @Override
  25. public boolean isDone() {
  26. return promise.isDone();
  27. }

一些简单的get方法,可以获取job的信息,直接返回类中的私有成员:
      
      
      
      
  1. @Override
  2. public MetricsCollection getMetrics() {
  3. return metrics;
  4. }
  5. @Override
  6. public List<Integer> getSparkJobIds() {
  7. return sparkJobIds;
  8. }
  9. @Override
  10. public SparkCounters getSparkCounters() {
  11. return sparkCounters;
  12. }
  13. @Override
  14. public State getState() {
  15. return state;
  16. }

然后是一个比较重要的方法changeState,通过这个方法对state进行修改:

   
   
   
   
  1. boolean changeState(State newState) {
  2.    synchronized (listeners) {
  3.      if (newState.ordinal() > state.ordinal() && state.ordinal() < State.CANCELLED.ordinal()) {
  4.        state = newState;
  5.        for (Listener l : listeners) {
  6.          fireStateChange(newState, l);
  7.        }
  8.        return true;
  9.      }
  10.      return false;
  11.    }
  12.  }
修改时需要判断新的状态的基数是否大于当前状态的基数,并且小于CANCELLED的基数,只有这样才能对将当前的state修改为新state。例如,如果当前state为SENT,newState为QUEUED,则可以进行修改;如果当前state为QUEUED ,newState为SENT,则不能修改。state修改后,通过fireStateChange触发已注册的listener中相应事件(state)对应的回调函数。

看一下fireStateChange,除了SENT状态外,其他状态的变化都会触发相应的回调函数:

     
     
     
     
  1. private void fireStateChange(State s, Listener l) {
  2. switch (s) {
  3. case SENT:
  4. break;
  5. case QUEUED:
  6. l.onJobQueued(this);
  7. LOG.debug("liban: onJobQueued in fireStateChange.");
  8. break;
  9. case STARTED:
  10. l.onJobStarted(this);
  11. LOG.debug("liban: onJobStarted in fireStateChange.");
  12. break;
  13. case CANCELLED:
  14. l.onJobCancelled(this);
  15. break;
  16. case FAILED:
  17. l.onJobFailed(this, promise.cause());
  18. break;
  19. case SUCCEEDED:
  20. try {
  21. l.onJobSucceeded(this, promise.get());
  22. } catch (Exception e) {
  23. // Shouldn\'t really happen.
  24. throw new IllegalStateException(e);
  25. }
  26. break;
  27. default:
  28. throw new IllegalStateException();
  29. }
  30. }
但是其实上面的几个回调函数,我在HOS源码中并没有找到具体实现,猜测可能是留给开发人员自己实现的接口?如果有同学了解的可以分享一下。

以上就是RemoteSparkJobMonitor与JobHandle的基本内容。
简单总结:JobHandle定义了与Job状态相关的接口,可以监控和控制job的状态,SparkJobMonitor则在一定时间阈值内循环获取job的状态,根据job的不同状态做不同处理,修改相应的返回码,标识任务结果状态。









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