关于Yarn源码那些事-番外-Yarn的异步调度

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


猜你喜欢

转载自blog.csdn.net/u013384984/article/details/80324375
今日推荐