在Yarn源码的研究过程中,有些概念比较重要,譬如Yarn的调度机制,事件机制和服务库等,这些概念有些含混,我们还是从源码角度上,来对这些概念一点点进行深入了解,对于了解Yarn的运行机制,有很大的好处。
本文,从源码层面来说一下Yarn的异步调度,这个词可能不太合适,直接看源码吧(想到哪儿,说到哪儿):
从ResourceManager说起(这里是基于2.6.5版本的Hadoop):
1:RM中的调度器
在我的其他博客中介绍过,RM中有两个方法非常重要,即serviceInit和serviceStart方法,一个是内部成员变量的初始化,一个是其内部嵌入服务的初始化,本文聚焦于其中的dispatcher:
// register the handlers for all AlwaysOn services using
// setupDispatcher().
rmDispatcher = setupDispatcher();
我们看到了rmDispatcher,而且从命名来说,其是RM内部核心的调度器,管理所有RM层面的事件调度。
这种说法很重要,因为RM内部的很多服务也有自己的调度器,完成它们内部事件的调度,而它们彼此之间的交互,就是把时间提交给这个全局调度器,好让别的服务来处理:
/**
* Register the handlers for alwaysOn services
*/
private Dispatcher setupDispatcher() {
Dispatcher dispatcher = createDispatcher();
dispatcher.register(RMFatalEventType.class,
new ResourceManager.RMFatalEventDispatcher());
return dispatcher;
}
我们把这两句简单的代码看下:
protected Dispatcher createDispatcher() {
return new AsyncDispatcher();
}
首先,这是个异步调度器,反映了Yarn内部是异步调度的冰山一角;我们看看其初始化方法:
protected final Map<Class<? extends Enum>, EventHandler> eventDispatchers;
public AsyncDispatcher() {
this(new LinkedBlockingQueue<Event>());
}
public AsyncDispatcher(BlockingQueue<Event> eventQueue) {
super("Dispatcher");
this.eventQueue = eventQueue;
this.eventDispatchers = new HashMap<Class<? extends Enum>, EventHandler>();
}
异步调度器内部封装了一个阻塞队列,其用于缓存所有交给这个调度器的事件,统一扔到这个队列里,同时,新建了一个HashMap类型的eventDispatchers。
接着,我们看看dispatcher内部的register方法:
@SuppressWarnings("unchecked")
@Override
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);
}
}
这里,我们看到了eventDispatchers的用途,其key是各种各样的时间类型,而value则是对应的事件处理器;我们认真分析这个方法,发现对应的value全是MultiListenerHandler:
/**
* Multiplexing an event. Sending it to different handlers that are
* interested in the event.
*
* @param <T>
* the type of event these multiple handlers are interested in.
*/
static class MultiListenerHandler implements EventHandler<Event>
这个用意在于,每一个事件可能需要很多的事件调度和处理类来处理,我们使用MultiListenerHandler处理较为方便;在其内部封装了一个ArrayList类型的EventHandler,在调度的时候,每一个Handler都会对该事件进行一次处理:
为了方便理解,我们看看register方法的实际应用:
// Register event handler for RmAppEvents
rmDispatcher.register(RMAppEventType.class,
new ApplicationEventDispatcher(rmContext));
捡个现成的例子,比如这句话,注释很清晰明了,rmDispatcher能够处理RMAppEventType.class类型的事件,这里并不是它自身去处理,而是从自己的eventDispatchers内找到对应的Handler或者dispatcher,交给其来处理:
/**
* Interface for handling events of type T
*
* @param <T>
* parameterized event of type T
*/
@SuppressWarnings("rawtypes")
@Public
@Evolving
public interface EventHandler<T extends Event> {
void handle(T event);
}
EventHandler作为value,其子类众多,RM内部和NM内部能够处理和调度事件的类,都是其子类,这里就不一一列举了,我们看下rmDispatcher中注册的那些事件即可:
nodesListManager = new NodesListManager(rmContext);
rmDispatcher.register(NodesListManagerEventType.class,
nodesListManager);
rmDispatcher
.register(SchedulerEventType.class, schedulerDispatcher);
rmDispatcher.register(RMAppEventType.class,
new ApplicationEventDispatcher(rmContext));
rmDispatcher.register(RMAppAttemptEventType.class,
new ApplicationAttemptEventDispatcher(rmContext));
rmDispatcher.register(RMNodeEventType.class,
new NodeEventDispatcher(rmContext));
rmDispatcher.register(ContainerPreemptEventType.class,
new RMContainerPreemptEventDispatcher(
(PreemptableResourceScheduler) scheduler));
rmDispatcher.register(RMAppManagerEventType.class, rmAppManager);
rmDispatcher.register(AMLauncherEventType.class,
applicationMasterLauncher);
我们必须认真注意到rmDispatcher中的eventDispatchers,研究源码过程中,如果遇到这些事件,我们就要想到可能与RM的全局调度器有关,更能够清晰把握住全盘的脉络。
另外需要注意的是,上面这些EventHandler,实际上都是与rmContext即RM的大管家有关系,因为这些处理过程,基本都要从rmContext中获取数据,简单来说,rmContext中有所有提交的App的管理信息,所有与Application有关的事件处理都要从其中找到Application相应的上下文,所依据的key就是第一次提交任务时候分配的ApplicationId。
dispatcher.register(RMFatalEventType.class,
new ResourceManager.RMFatalEventDispatcher());
这个事件的等级非常高,从名称就能看出来,如果出现此类事件,系统会直接中断:
@Private
public static class RMFatalEventDispatcher implements
EventHandler<RMFatalEvent> {
@Override
public void handle(RMFatalEvent event) {
LOG.fatal("Received a " + RMFatalEvent.class.getName()
+ " of type " + event.getType().name() + ". Cause:\n"
+ event.getCause());
ExitUtil.terminate(1, event.getCause());
}
}
RM在服务初始化的时候,新建了一个rmDispatcher,那么,该rmDispatcher如何发挥作用的呢,我们要看看rmDispatcher中serviceStart中的代码:
@Override
protected void serviceStart() throws Exception {
// start all the components
super.serviceStart();
eventHandlingThread = new Thread(createThread());
eventHandlingThread.setName("AsyncDispatcher event handler");
eventHandlingThread.start();
}
其中使用了createThread方法:
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内部取出事件,然后进行调度,这里,默认的blockNewEvents为false,而整个服务系统中,所有的dispatcher原理大致类似。
我们接着看其中的dispatcher方法:
@SuppressWarnings("unchecked")
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 {
EventHandler handler = eventDispatchers.get(type);
if (handler != null) {
handler.handle(event);//寻找对应的Handler中的handle逻辑来处理事件
} 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();
}
}
}
这里,默认的exitOnDispatchException是false的,就是调度错误并不会停止,否则,按照判断条件,系统终止,看下这个shutdown的run方法:
Runnable createShutDownThread() {
return new Runnable() {
@Override
public void run() {
LOG.info("Exiting, bbye..");
System.exit(-1);
}
};
}
Yarn内部,就是通过这种异步的调度机制,大幅度提高了系统的整体运行速度:
这里插播一句,其实这种消息队列的方式,比共享内存加锁的性能要高一些。
2:NM的调度器
说完了RM的调度器,我们来看看NM的调度器,看看其中注册了哪些事件:
dispatcher.register(ContainerManagerEventType.class, containerManager);
dispatcher.register(NodeManagerEventType.class, this);
在NM的dispatcher,所需要管理的事件就比较少了,只有这两大类,而且NM中的dispatcher也是一个异步调度器。
分析完这最重要的RM和NM的调度器,看看其他使用到的调度器:
3:ContainerManagerImpl的调度器
dispatcher.register(LogHandlerEventType.class, logHandler);
dispatcher.register(ContainerEventType.class,
new ContainerEventDispatcher());
dispatcher.register(ApplicationEventType.class,
new ApplicationEventDispatcher());
dispatcher.register(LocalizationEventType.class, rsrcLocalizationSrvc);
dispatcher.register(AuxServicesEventType.class, auxiliaryServices);
dispatcher
.register(ContainersMonitorEventType.class, containersMonitor);
dispatcher.register(ContainersLauncherEventType.class,
containersLauncher);
这里罗列出所有跟ContainerManagerImpl调度器有关的事件。
4:RMStateStore
dispatcher.register(RMStateStoreEventType.class,
new ForwardingEventHandler());
RMStateStore顾名思义,其实就是存储RM状态的,在我们的系统第一次启动的时候,其默认是个NullStateStore,内部主要是用来存储Application和相应信息的。
不仅如此,其内部同时拥有一个rmDispatcher,因为其也会把事件传递给外部的RM的调度器进行全局的调度:
private Dispatcher rmDispatcher;
/**
* Dispatcher used to send state operation completion events to
* ResourceManager services
*/
public void setRMDispatcher(Dispatcher dispatcher) {
this.rmDispatcher = dispatcher;
}
基本上,在Yarn内部拥有调度器的就是这四大类,其他的类通常都是调用这些类的调度器来使用:
比如我们看下RMContainerImpl的类,其中的eventHandler的来源,就是:
this.eventHandler = rmContext.getDispatcher().getEventHandler();
这实际上是RM内的调度器,当调用这个调度器的时候,其实就相当于把内部处理完毕的事件,交给了外面的全局调度器去处理。
又比如RMAppAttemptImpl类:
this.eventHandler = rmContext.getDispatcher().getEventHandler();
同样的作用不予赘述:
Yarn内部的大多数类,都使用到了调度器,无论是内部调度器,还是调用全局的调度器(如RM的调度器和NM的调度器),这些调度基本全是异步的,提现了Yarn内部的异步机制。
综合来说,对于一个类内部的函数执行,牵涉到的更多是状态机的转换,但是,认真了解这些调度器的存在,对于我们把握Yarn框架内事件的流转和处理,有很大的好处。