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,这样就能找到事件对应的事件处理器了。 通过上面描述可知,事件+事件调度器+事件处理器构成了事件驱动模型。事件驱动模型流程图如下所示:
注意: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性能。