在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框架内事件的流转和处理,有很大的好处。