1. 前言

上一篇文章介绍了Yarn Service框架分析,了解到Yarn通过Service->AbstractService->CompositeService类的完善,实现了从简单到复杂的服务设计。实现了所有场景下服务的初始化/启动/停止流程。本文介绍Yarn的另一个重要设计思想:事件驱动模型。

2. 什么是事件驱动模型

2.1 什么是事件

在了解事件驱动模型前,需要了解什么是事件。例如:ApplicationFinishEvent表示应用结束的事件、ContainerLauncherEvent表示容器启动的事件、RMNodeStartedEvent表示RM节点启动的事件。在Yarn中,所有事件都是通过实现Event接口表示一种行为状态,这种行为就是一种事件。Event接口如下所示:

public interface Event<TYPE extends Enum<TYPE>> {
  TYPE getType();
  long getTimestamp();
  String toString();
}

Event#getType是非常重要的方法,它用于获取事件的状态。同一种事件,可能有多种状态。以ApplicationFinishEvent为例,ApplicationFinishEvent继承ApplicationEvent类、ApplicationEvent继承AbstractEvent<ApplicationEventType>类、AbstractEvent类实现Event接口。层次关系如下所示:

public class ApplicationFinishEvent extends ApplicationEvent{...}
  public class ApplicationEvent extends AbstractEvent<ApplicationEventType>{...}
    public abstract class AbstractEvent<TYPE extends Enum<TYPE>> implements Event<TYPE>{...}

从上述层次关系可以看到,由ApplicationEventType类表示ApplicationFinishEvent类型的事件的具体类型。ApplicationEventType是枚举类型,其定义如下:

public enum ApplicationEventType {

  // Source: ContainerManager
  INIT_APPLICATION,
  INIT_CONTAINER,
  FINISH_APPLICATION, // Source: LogAggregationService if init fails

  // Source: ResourceLocalizationService
  APPLICATION_INITED,
  APPLICATION_RESOURCES_CLEANEDUP,

  // Source: Container
  APPLICATION_CONTAINER_FINISHED,

  // Source: Log Handler
  APPLICATION_LOG_HANDLING_INITED,
  APPLICATION_LOG_HANDLING_FINISHED,
  APPLICATION_LOG_HANDLING_FAILED
}

因此,ApplicationFinishEvent事件有9种事件类型。例如,当ContainerManager初始化应用时,会发送INIT_APPLICATION类型的ApplicationFinishEvent事件,请求处理器处理事件。

2.2 如何处理事件

Yarn设计了事件驱动模型,用于处理各种各样的事件。事件驱动模型基于生产者-消费者模式,使用Event Queue存放事件,生产者服务往Event Queue中存放事件数据,消费者负责处理Event Queue中的事件数据。Yarn设计各种各样的事件处理器EventHandler用于处理特定的事件,EventHandler通过handle方法处理事件,其接口定义如下:

public interface EventHandler<T extends Event> {
  void handle(T event);
}

为了快速找到不同类型事件Event对应的事件EventHandler。Yarn设计了事件调度器,使用恰当的EventHandler处理对应的Event。Dispatcher接口定义如下:

public interface Dispatcher {
  EventHandler<Event> getEventHandler();
  void register(Class<? extends Enum> eventType, EventHandler handler);
}

Dispatcher接口中Dispatcher#register方法非常重要。它负责注册不同类型事件Event对应的事件处理器EventHandler,这样就能找到事件对应的事件处理器了。 通过上面描述可知,事件+事件调度器+事件处理器构成了事件驱动模型。事件驱动模型流程图如下所示: image.png

注意:EventHandler处理完事件后,依然可以产生新的事件,加入Event Queue中等待被处理。上图中,Event Handler处理完事件后,又产生了EventType4类型的事件加入了Event Queue。

3. 为什么要设计事件驱动模型

事件驱动模型以事件为核心,将事件存放在事件队列中,通过生产者/消费者模式向队列中生产/消费数据。这一套模型高效完成事件处理整个流程。

4. 异步事件调度器:AsyncDispatcher

在事件驱动模型处理事件过程中,如果按照同步处理的方式,即生产者产生一个事件,消费者就处理这个事件,生产者等待消费者将事件处理完再生产下一个事件。如果消费者处理事件的速度慢,会导致生产发生延迟。为了解决同步事件调度的弊端。Yarn设计了异步事件调度器AsyncDispatcher,通过队列缓存的方式保存没有及时被消费的事件,提升了生产效率。

++AsyncDispatcher继承了AbstractService,它也是一种服务,会调用init/start/stop方法进行初始化/启动/停止++。其重要成员如下:

public class AsyncDispatcher extends AbstractService implements Dispatcher {
  private final BlockingQueue<Event> eventQueue;
  private final EventHandler<Event> handlerInstance = new GenericEventHandler();

  private Thread eventHandlingThread;
  protected final Map<Class<? extends Enum>, EventHandler> eventDispatchers;
}

其中,eventQueue用于存放事件的队列;handlerInstance是通用的事件处理器,用于生产事件;eventHandlingThread线程用于分发并处理事件;eventDispatchers用于存放不同类型事件对应的事件处理器,一个事件类型可以对应多个事件处理器。

4.1 AsyncDispatcher服务创建过程

事件调度器AsyncDispatcher服务属于ResourceManager的子服务,在ResourceManager#serviceInit初始化时,会创建AsyncDispatcher类型的rmDispatcher成员,并将AsyncDispatcher对象加入到ResourceManager的子服务列表中:

public class ResourceManager extends CompositeService implements Recoverable, ResourceManagerMXBean {
    private Dispatcher rmDispatcher;
    protected void serviceInit(Configuration conf) throws Exception {
        //省略
        // register the handlers for all AlwaysOn services using setupDispatcher().
        rmDispatcher = setupDispatcher();
        addIfService(rmDispatcher);
        rmContext.setDispatcher(rmDispatcher);
        //省略
        super.serviceInit(this.conf);
    }
}

ResourceManager#serviceInit依靠ResourceManager#setupDispatcher创建Dispatcher对象,同时还自动为RMFatalEventType类型的事件注册了对应的事件处理器ResourceManager$RMFatalEventDispatcher:

  private Dispatcher setupDispatcher() {
    Dispatcher dispatcher = createDispatcher();
    dispatcher.register(RMFatalEventType.class, new ResourceManager.RMFatalEventDispatcher());
    return dispatcher;
  }

ResourceManager#createDispatcher创建了AsyncDispatcher事件调度处理器,并初始化eventQueue为LinkedBlockingQueue类型:

  protected Dispatcher createDispatcher() {
    return new AsyncDispatcher("RM Event dispatcher");
  }

AsyncDispatcher#register负责注册事件类型对应的事件处理器,使用eventDispatchers成员存储type->handler的映射关系。如果事件类型对应的处理器有多个,将处理器类型转化为MultiListenerHandler,向MultiListenerHandler添加多个处理器即可:

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

4.2 AsyncDispatcher服务初始化过程

ResourceManager#serviceInit方法最后,即在ResourceManager服务自身初始化完后,会通过super.serviceInit(conf)调用CompositeService#serviceInit方法,初始化所有子服务。其子服务AsyncDispatcher初始化过程如下:

  protected void serviceInit(Configuration conf) throws Exception{
    super.serviceInit(conf);
    //每当eventQueue大小达到5000个,打印每种事件的数量
    this.detailsInterval = getConfig().getInt(YarnConfiguration.YARN_DISPATCHER_PRINT_EVENTS_INFO_THRESHOLD, YarnConfiguration.DEFAULT_YARN_DISPATCHER_PRINT_EVENTS_INFO_THRESHOLD);
  }

AsyncDispatcher服务初始化不重要,只用于获取配置信息。

4.3 AsyncDispatcher服务启动过程

ResourceManager主线程在调用执行AsyncDispatcher#serviceStart启动服务时,创建并启动了eventHandlingThread线程后,就继续执行其它服务的启动流程了。AsyncDispatcher#serviceStart方法如下:

  protected void serviceStart() throws Exception {
    //start all the components
    super.serviceStart();
    eventHandlingThread = new Thread(createThread());
    eventHandlingThread.setName(dispatcherThreadName);
    eventHandlingThread.start();
  }

eventHandlingThread对象是一个匿名内部线程类的实例,服务消费事件队列,并负责分发事件。其定义如下:

Runnable createThread() {
  return new Runnable() {
    @Override
    public void run() {
      while (!stopped && !Thread.currentThread().isInterrupted()) {
        //省略
        //从AsyncDispatcher#eventQueue中取出一个事件。
        Event event = eventQueue.take();
        //省略
        //将事件event派发给对应的事件处理器EventHandler处理
        dispatch(event);
        //省略
    }
  };
}

在子线程eventHandlingThread中,AsyncDispatcher#dispatch负责分发事件。其中,首先获取到要处理的事件的类型,找到事件类型对应的处理器,通过执行器Handler执行对应的处理方法:

  protected void dispatch(Event event) {
    //省略
    Class<? extends Enum> type = event.getType().getDeclaringClass();
    //省略
    EventHandler handler = eventDispatchers.get(type);
    //省略
    handler.handle(event);
    //省略
  }

4.3 事件生产过程

从4.2节可知,eventHandlingThread线程主要负责分发事件队列中的事件,并调用Handler实现类#handle处理事件,处理完事件后,再依次消费事件队列中后续的事件。 对于异步调度器AsyncDispatcher,生产事件到事件队列中也是由Handler实现类AsyncDispatcher$GenericEventHandler处理,如下所示:

public class AsyncDispatcher extends AbstractService implements Dispatcher {
  class GenericEventHandler implements EventHandler<Event> {
    public void handle(Event event) {
      //省略
      eventQueue.put(event);
      //省略
    }
  }
}

虽然GenericEventHandler也是一种Handler,但是并没有设计对应的事件类型eventType,将GenericEventHandler注册到对应的事件类型中。这里推断是因为如果将事件生产封装成一个事件,并注册事件对应的GenericEventHandler处理器。那么生产事件在eventHandlingThread队列中必须等前面的事件消费完,才能将最新的事件放到eventQueue中,这无疑会造成生产延迟。 实际上,在生产事件时,直接调用GenericEventHandler#handle。有两种生产事件的场景,分别是直接生产事件、消费时生产事件。

4.3.1 直接生产事件

在其它服务中,在处理处理到达了一定阶段,会直接调用GenericEventHandler#handle方法生产事件。例如,当容器恢复服务时,会调用GenericEventHandler#handle生产对应的事件:

public class ContainerLaunch implements Callable<Integer> {
  public void resumeContainer() throws IOException {
    ContainerId containerId = container.getContainerId();
    String containerIdStr = containerId.toString();
    exec.resumeContainer(container);
    dispatcher.getEventHandler().handle(new ContainerEvent(containerId, ContainerEventType.CONTAINER_RESUMED)); 
  }
}

4.3.2 消费时生产事件

在Handler处理事件时,可能会产生新的事件待处理。例如,在处理应用调度时会产生应用调度事件:

  private static final class AddApplicationToSchedulerTransition extends RMAppTransition {
    @Override
    public void transition(RMAppImpl app, RMAppEvent event) {
      // app.handler就是RMAppImpl的handler成员变量,其实就是GenericEventHandler对象
      app.handler.handle(new AppAddedSchedulerEvent(app.user, app.submissionContext, false, app.applicationPriority, app.placementContext));
      // send the ATS create Event
      app.sendATSCreateEvent();
    }
  }

4.4 异步调度器总结

  • 异步调度器采用生产者-消费者模式,使用事件队列存放未处理的事件,提升生产效率。
  • 不同类型的事件处理方式不同,通过注册Handler实现,一个Handler类处理一种事件。
  • 消费事件队列和处理事件在同一线程eventHandlingThread中。

5. 总结

事件驱动模型用于处理Yarn中的事件。它使用事件队列存储事件,基于GenericEventHandler#handle生产事件;消费事件前,会注册事件类型对应的Handler处理器,在消费时,找到事件类型的Handler处理,进行事件处理。

6. 思考

消费事件时,所有事件在一个线程中进行处理,可能造成消费延迟,影响Yarn性能。