YARN 状态机的原理和使用---Hadoop中央异步调度器AsyncDispatcher代码和设计解析

本文深入探讨了Hadoop中的AsyncDispatcher设计和实现,它是Hadoop的核心调度器,负责处理各种事件并进行状态转换。AsyncDispatcher通过事件队列和事件处理器管理事件,保证了系统的高效运行。文章详细分析了其内部工作机制,包括事件注册、调度处理、服务启动与停止等关键环节,展示了其如何优雅地实现异步调度和状态管理。
摘要由CSDN通过智能技术生成

以Yarn、HDFS和MapReduce为主要组成的Hadoop,涉及到大量复杂的、交互的事件处理、状态转换,同时,这些事件调度和状态转换又对实时性和效率提出了极高的要求。可以想见,没有一个规整的、通用型良好的调度器,Hadoop代码无论是对读者,还是对开发者,都将变成一场灾难,同时,Hadoop的运行效率也会变得无法忍受。统一的、设计良好的、通用的和共用的调度器,对于Hadoop不同组件的开发者来说是一种解脱,大大降低了Hadoop在事件调度、状态转换的底层出错的可能性,提高了代码稳定性和可读性。这篇文章主要介绍了Hadoop的核心调度器AsyncDispatcher的设计和实现。同Hadoop状态机一样,这个通用调度器设计得十分通用,完美可扩展可重用,我们在自己的项目中完全可以使用Hadoop的调度器实现我们自己的事件调度逻辑。 
先抛开代码本身,我们来看一个基于事件的调度器的工作方式: 


 public AsyncDispatcher() {
    this(new LinkedBlockingQueue<Event>());
  }

  public AsyncDispatcher(BlockingQueue<Event> eventQueue) {
    super("Dispatcher");
    this.eventQueue = eventQueue;
    this.eventDispatchers = new HashMap<Class<? extends Enum>, EventHandler>();
  }

在它的构造方法中,有两个核心变量,eventQueue是一个队列,用来存放事件,同时,还构造了一个eventDispatchers,这是一个map对象,用来管理事件和事件处理器之间的关系。AsyncDispatcher同样遵循我一直提到的Yarn的服务化设计,即AsyncDispatcher被抽象为一个service,在完成对象构造以后,会进行服务的初始化和启动:

  @Override
  protected void serviceInit(Configuration conf) throws Exception {
    this.exitOnDispatchException =
       conf.getBoolean(Dispatcher.DISPATCHER_EXIT_ON_ERROR_KEY,
          Dispatcher.DEFAULT_DISPATCHER_EXIT_ON_ERROR);
    super.serviceInit(conf);
  }

初始化操作并没有做太多的事情,只是调用父类的serviceInit()方法,用来将配置文件设置进来。

  @Override
  protected void serviceStart() throws Exception {
    //start all the components
    super.serviceStart();
    eventHandlingThread = new Thread(createThread());
    eventHandlingThread.setName("AsyncDispatcher event handler");
    eventHandlingThread.start();
  }
serviceStart()方法的主要任务是创建了一个名字叫做AsyncDispatcher event handler的独立线程,我们一起来看这个线程的代码:

Runnable createThread() {
    return new Runnable() {
      @Override
      public void run() {
        while (!stopped && !Thread.currentThread().isInterrupted()) {
          drained = eventQueue.isEmpty();
          // blockNewEvents is only set when dispatcher is draining to stop,
          // adding this check is to avoid the overhead of acquiring the lock
          // and calling notify every time in the normal run of the loop.
          if (blockNewEvents) {
            synchronized (waitForDrained) {
              if (drained) {
                waitForDrained.notify();
              }
            }
          }
          Event event;
          try {
            event = eventQueue.take();
          } catch(InterruptedException ie) {
            if (!stopped) {
              LOG.warn("AsyncDispatcher thread interrupted", ie);
            }
            return;
          }
          if (event != null) {
            dispatch(event);
          }
        }
      }
    };
  }
线程的大致功能,是不断循环检测事件队列eventQueue中是否有新的事件到来,如果有,则取出这个事件的处理器,一个EventHandler接口的实现类,调用dispatch()方法,对该事件进行处理。 
那么问题来了,这个异步调度器会管理多个事件和事件调度器,那么它什么时候开始知道某个事件发生的时候改用什么调度器对象对这个事件进行处理呢?这就是register方法:

/**
   * 注册事件分派器
   */
  public void register(Class<? extends Enum> eventType,
      EventHandler handler) {
    /* check to see if we have a listener registered */
    EventHandler<Event> registeredHandler = (EventHandler<Event>)
    eventDispatchers.get(eventType);
    LOG.info("Registering " + eventType + " for " + handler.getClass());
    if (registeredHandler == null) {
      eventDispatchers.put(eventType, handler);
    } else if (!(registeredHandler instanceof MultiListenerHandler)){
      /* for multiple listeners of an event add the multiple listener handler */
      MultiListenerHandler multiHandler = new MultiListenerHandler();
      multiHandler.addHandler(registeredHandler);
      multiHandler.addHandler(handler);
      eventDispatchers.put(eventType, multiHandler);
    } else {
      /* already a multilistener, just add to it */
      MultiListenerHandler multiHandler
      = (MultiListenerHandler) registeredHandler;
      multiHandler.addHandler(handler);
    }
  }
还有一个问题,不同的事件,是怎么放入eventQueue中的呢?这是通过AsyncDispatcher.GenericEventHandler.handle()方法进行的:

 class GenericEventHandler implements EventHandler<Event> {
    public void handle(Event event) {
      if (blockNewEvents) { //如果该标记为置位,说明可能服务正在进行stop操作,无法处理新的请求
        return;
      }
      drained = false;
      /* all this method does is enqueue all the events onto the queue */
      int qSize = eventQueue.size();
      if (qSize != 0 && qSize % 1000 == 0
          && lastEventQueueSizeLogged != qSize) {
        lastEventQueueSizeLogged = qSize;
        LOG.info("Size of event-queue is " + qSize);
      }
      int remCapacity = eventQueue.remainingCapacity();
      if (remCapacity < 1000) {
        LOG.warn("Very low remaining capacity in the event-queue: "
            + remCapacity);
      }
      try {
        eventQueue.put(event);
      } catch (InterruptedException e) {
        if (!stopped) {
          LOG.warn("AsyncDispatcher thread interrupted", e);
        }
        // Need to reset drained flag to true if event queue is empty,
        // otherwise dispatcher will hang on stop.
        drained = eventQueue.isEmpty();
        throw new YarnRuntimeException(e);
      }
    };
  }
AsyncDispatcher内部维护了一个GenericEventHandler handlerInstance变量,通过z这个handlerInstance.handle()来将新的事件放入到eventQueue中。这个方法名字虽然叫做handle(),但是由于我们的AysncDispatcher是异步调度器,因此并没有立刻进行处理,而是放入队列,由上面提到的eventHandlingThread线程进行处理。

先看AsyncDispatcher.serviceStop()方法:

protected void serviceStop() throws Exception {
    if (drainEventsOnStop) {
      blockNewEvents = true; //首先阻止新任务的分派,试图优雅停掉当前线程的工作
      LOG.info("AsyncDispatcher is draining to stop, igonring any new events.");
      long endTime = System.currentTimeMillis() + getConfig()
          .getLong(YarnConfiguration.DISPATCHER_DRAIN_EVENTS_TIMEOUT,
              YarnConfiguration.DEFAULT_DISPATCHER_DRAIN_EVENTS_TIMEOUT);

      synchronized (waitForDrained) {
        while (!drained && eventHandlingThread != null
            && eventHandlingThread.isAlive()
            && System.currentTimeMillis() < endTime) {
          waitForDrained.wait(1000);
          LOG.info("Waiting for AsyncDispatcher to drain. Thread state is :" +
              eventHandlingThread.getState());
        }
      }
    }
    stopped = true;
    if (eventHandlingThread != null) {
      eventHandlingThread.interrupt();//防止线程正在处理一个耗时任务导致线程依然没有退出
      try {
        eventHandlingThread.join();//等待eventHandlingThread执行完毕
      } catch (InterruptedException ie) {
        LOG.warn("Interrupted Exception while stopping", ie);
      }
    }
serviceStop()调用一开始,就将blockNewEvents=true置位,这意味着将不再接受新的event提交,然后的主要工作,就是需要等待所有的serviceStop()调用前提交的事件都被执行完毕再真正停止服务,否则会造成任务丢失。waitForDrained.wait(1000);代表着服务关闭的时候会每1s检查我们的eventQueue中的所有事件是否处理完毕,如果没有处理完毕,则继续等待。虽然1s已经非常短暂,但是对于Yarn这样的高并发系统,也必须进行优化。 
为了能够在eventQueue清空的时候及时通知serviceStop()所在线程,我们看这段代码:

if (blockNewEvents) {
            synchronized (waitForDrained) {
              if (drained) {
                waitForDrained.notify();
              }
            }
}
如果blockNewEvents=true,代表该调度器目前正在进行关闭操作,因此,事件处理线程有必要在发现事件队列已经清空的情况下(drained=true),通过waitForDrained锁及时通知到服务关闭线程(waitForDrained.notify())。服务关闭线程收到该通知就能立刻从waitForDrained.wait()中直接唤醒,从而及时完成关闭操作,而不再进行不必要的wait操作。 
注意,wait操作与sleep()不同,wait()操作虽然必须在synchronized代码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值