源碼閱讀——hadoop yarn之AsyncDispatcher

最近看了讀到了關於hadoop中yarn的編程模型的文章 http://dongxicheng.org/mapreduce-nextgen/programming-model/,想深入瞭解下它的事件機制是怎麼實現的,就看了看其中的AsyncDispatcher類的源碼,幸好該類涉及到的其它類不多,讀起來也不算吃力

我們先來看其繼承關係,AsyncDispatcher繼承了AbstractService類,實現了Dispatcher接口,其中Dispatcher接口主要是定義了兩個方法register以及getEventHandler,以及一個用於配置事件循環是否在錯誤後推出的配置,不用太關注這個。

重點看下AbstractService,AbstractService類實現了Service接口,AbstractService中我們主要關注幾個方法:

服務生命週期部分:init,start,stop,serviceInit,serviceStart,serviceStop

服務監聽器部分:registerServiceListener,unregisterServiceListener,registerGlobalListener,unregisterGlobalListener

其中init,start,stop是繼承自service接口的,是暴露給用戶用於初始化,啓動和停止服務的接口,而serviceInit,serviceStart,serviceStop是空方法,在init,start,stop中被調用,用於子類繼承重寫,從而自定義自己的初始化,啓動以及停止操作。

那麼爲什麼不直接讓子類重寫init,start,stop方法呢,因爲init,start,stop除了調用你們自定義的啓動過程,還做了一些其它的事,就是改變service的狀態以及通知其它監聽了該服務的回掉,其中registerServiceListener,unregisterServiceListener,registerGlobalListener,unregisterGlobalListener就是用於註冊服務狀態回掉的接口,注意,此處的的回掉不是事件回掉,而是服務狀態改變時的回掉,每個服務都有該功能,不要和後文的AbstractService的事件回掉弄混了。

大致瞭解了AbstractService的功能是提供自定義的服務初始化,啓動和停止的接口以及維持服務狀態+狀態改變後通知接聽者,我們接下來就可以開始看AbstractService了,對於AbstractService的大致流程我們大致可以在腦中想一下,不外乎內部有一個事件隊列,同時有一個線程不斷的從事件隊列裏取事件,然後根據註冊情況進行分發,大致思路就是這樣的。

我們重點是看看具體的實現,首先我們看創建取事件的線程的

Runnable createThread() {
    return new Runnable() {
      @Override
      public void run() {
//        首先定義兩個循環終止條件,stopped變量以及線程被Interrupt,其中stopped是volatile,保證可以及時的看到改變
        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就是一個Object的實例,用於做線程間通信而已
                waitForDrained.notify();
              }
            }
          }
          Event event;
          try {
//            用阻塞的模式從事件隊列的取事件
            event = eventQueue.take();
          } catch(InterruptedException ie) {
//            如果take被中斷,且不是service被停止導致的,那麼log打印一句警告
            if (!stopped) {
              LOG.warn("AsyncDispatcher thread interrupted", ie);
            }
            return;
          }
          if (event != null) {
//            分發事件
            dispatch(event);
          }
        }
      }
    };
  }

大致就是做了take事件,然後dispatch事件的操作,其它操作則是爲了保證線程優雅推出,以及推出前完成當前事件隊列裏事件的分發

接下來就是dispatch方法了

protected void dispatch(Event event) {
    //all events go thru this loop
    if (LOG.isDebugEnabled()) {
      LOG.debug("Dispatching the event " + event.getClass().getName() + "."
          + event.toString());
    }

//    獲取事件類型
    Class<? extends Enum> type = event.getType().getDeclaringClass();

    try{
//      獲取相應的handler,EventHandler是個接口
      EventHandler handler = eventDispatchers.get(type);
      if(handler != null) {
//        調用handler的handle方法,
        handler.handle(event);
      } else {
        throw new Exception("No handler for registered for " + type);
      }
    } catch (Throwable t) {
      //TODO Maybe log the state of the queue
      LOG.fatal("Error in dispatcher thread", t);
      // If serviceStop is called, we should exit this thread gracefully.
      if (exitOnDispatchException
          && (ShutdownHookManager.get().isShutdownInProgress()) == false
          && stopped == false) {
        Thread shutDownThread = new Thread(createShutDownThread());
        shutDownThread.setName("AsyncDispatcher ShutDown handler");
        shutDownThread.start();
      }
    }
  }

這個沒啥好說的,注意一點就是,dispatch方法是直接調用了handler的handle方法的,所以如果handle裏會出現要處理長時間的任務,務必記得新開一個線程執行,不然會影響eventloop的

接下來看一下用於註冊事件的接口

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);
    }
  }

這個純粹看英文註釋就知道註冊事件時碰到的三種情況(事件未註冊過,事件已註冊過但事件的handler不是符合handler,事件已註冊過且事件的handler是複合handler)下進行的操作了,提一句MultiListenerHandler是實現了EventHandler接口的類,代表符合handler,裏面維持了一個handler的list

接下來看看怎麼觸發事件,正常來說就是將事件推入事件隊列即可,但是hadoop這裏的實現讓我有點不太明白爲什麼要這麼做,希望有人可以指導一下

首先我們看一下項目裏別人是怎麼觸發事件的

dispatcher.getEventHandler().handle(event);

然後我們看一下getEventHandler方法

@Override
public EventHandler getEventHandler() {
  return handlerInstance;
}

handlerInstance則是

private final EventHandler handlerInstance = new GenericEventHandler();

而GenericEventHandler則是實現了EventHandler接口的一個內部類

class GenericEventHandler implements EventHandler<Event> {
    public void handle(Event event) {
      if (blockNewEvents) {
        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);
      }
    };
  }

沒錯,我們終於在GenericEventHandler的handle方法裏看到了將事件推入事件隊列的操作了,不太理解爲什麼要繞這麼一個大彎,望有人解答。handle裏還做了檢查事件隊列剩餘大小的工作。

最後我們看一下AsyncDispatcher的停止服務的方法

@Override
  protected void serviceStop() throws Exception {
//    用一個標準位表示是否要處理完事件隊列裏的事件,該標誌位是有接口可以更改的
    if (drainEventsOnStop) {
//      將blockNewEvents設爲true,禁止再向事件隊列裏添加事件了
      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());
        }
      }
    }
//    設置服務停止標誌位爲true
    stopped = true;
    if (eventHandlingThread != null) {
//      中斷事件循環線程
      eventHandlingThread.interrupt();
      try {
//        等待線程結束
        eventHandlingThread.join();
      } catch (InterruptedException ie) {
        LOG.warn("Interrupted Exception while stopping", ie);
      }
    }

    // stop all the components
    super.serviceStop();
  }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章