上篇 Yarn源碼分析之集羣啓動流程 我們介紹了Yarn的啓動過程,捎帶介紹了
AsyncDispatcher
,但感覺有必要單獨詳細分析下(源碼版本:hadoop2.7.1)…
事件模型設計原理
爲了更好的應對併發,Yarn採用了基於事件的異步模型。所謂基於事件的模型,就是通過事件隊列緩存住各種事件,然後通過事件分發器裏的常駐線程不斷的從事件隊列裏取事件並將該事件交給相應的事件處理handler(EventHandler)進行處理,是一個典型的生產者-消費者模型。其原理如下圖:
在Yarn中,Dispatcher 接口的實現有三種,不過在絕大多數場景下是使用它的一種實現— AsyncDispatcher ,下面我們也是重點分析該類的。
register()
方法負責註冊事件處理器,getEventHandler()
方法獲得事件處理器以派發事件。
AsyncDispatcher的基本結構
AsyncDispatcher 內部是 有一個 阻塞的事件隊列,有一個一直運行的執行線程,當阻塞隊列中有事件被放入,執行線程會把事件取出來,並獲取事件的類型,從事件註冊器 Map<Class<? extends Enum>, EventHandler> 中獲取到對應的 EventHandler 對象,並調用該對象的 dispatch 方法分發事件。
AsyncDispatcher的初始化
由之前的文章《學習筆記之Yarn啓動過程.md》可知,Yarn啓動的入口類是 ResourceManager,而在serviceInit
函數中初始化事件驅動器並賦值給全局變量,由此可知 AsyncDispatcher作爲Yarn的常駐服務之一 ,代碼如下圖:
AsyncDispatcher的啓動
同樣由之前的文章《學習筆記之Yarn啓動過程.md》可知,在 ResourceManager.serviceStart
-> CompositeService.serviceStart -> AsyncDispatcher.serviceStart
函數中啓動,查看 AsyncDispatcher 代碼,摘關鍵代碼:
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);
}
}
}
};
}
@Override
protected void serviceStart() throws Exception {
//start all the components
super.serviceStart();
eventHandlingThread = new Thread(createThread());
eventHandlingThread.setName("AsyncDispatcher event handler");
eventHandlingThread.start();
}
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);
} 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();
}
}
}
上文說Yarn啓動後,AsyncDispatcher作爲Yarn的常駐服務之一,而在AsyncDispatcher啓動後會開闢一個線程,循環讀取阻塞隊列,然後分發事件,此處相當於”消費者“。其中分發機制下面繼續講解…
註冊事件處理器
查看上面 Dispatcher
接口發現,register
方法是核心方法之一,該方法是將各種事件對應的事件處理器保存下來,用於判斷哪個handler對應哪個event。下面我們首先看 AsyncDispatcher
中該方法的實現如下:
可以看到該方法比較簡單,僅僅做判斷並put到eventDispatchers中,下面我們看何時調用該方法:
在ResourceManager 組件中註冊過程,舉例如下:
註冊完後,AsyncDispatcher在消費端就能按照事件類型分發到具體的事件處理器了。
下面列舉註冊的事件處理器便於參閱,如下:
格式:<事件類型,事件處理器>
ResourceManager:
<NodesListManagerEventType, NodesListManager>
<SchedulerEventType, SchedulerEventDispatcher>
<RMAppEventType, ApplicationEventDispatcher>
<RMAppAttemptEventType, ApplicationAttemptEventDispatcher>
<RMNodeEventType, NodeEventDispatcher>
<RMAppManagerEventType, RMAppManager>
<AMLauncherEventType, ApplicationMasterLauncher>
<ContainerPreemptEventType, RMContainerPreemptEventDispatcher>
<RMFatalEventType, ResourceManager.RMFatalEventDispatcher>
ContainerManagerImpl:
<ContainerEventType, ContainerEventDispatcher>
<ApplicationEventType, ApplicationEventDispatcher>
<LocalizationEventType, ResourceLocalizationService>
<AuxServicesEventType, AuxServices>
<ContainersMonitorEventType,ContainersMonitorImpl>
<ContainersLauncherEventType, ContainersLauncher>
<LogHandlerEventType, LogAggregationService|NonAggregatingLogHandler>
<SharedCacheUploadEventType, ContainerManagerImpl>
NodeManager:
<ContainerManagerEventType, ContainerManagerImpl>
<NodeManagerEventType, NodeManager>
ResourceLocalizationService:
<LocalizerEventType, LocalizerTracker>
CommonNodeLabelsManager:
<NodeLabelsStoreEventType, ForwardingEventHandler>
MRAppMaster:
<JobEventType, NoopEventHandler>
<ContainerAllocator.EventType, ContainerAllocator>
<org.apache.hadoop.mapreduce.jobhistory.EventType, JobHistoryEventHandler|NoopEventHandler>
<org.apache.hadoop.mapreduce.jobhistory.EventType, NoopEventHandler>
<JobEventType, JobEventDispatcher>
<TaskEventType, TaskEventDispatcher>
<TaskAttemptEventType, TaskAttemptEventDispatcher>
<CommitterEventType, CommitterEventHandler>
<Speculator.EventType, SpeculatorEventDispatcher>
<ContainerAllocator.EventType, ContainerAllocatorRouter>
<ContainerLauncher.EventType, ContainerLauncherRouter>
<JobFinishEvent.Type, JobFinishEventHandler>
RMApplicationHistoryWriter:
<WritingHistoryEventType, ForwardingEventHandler>
RMStateStore:
<RMStateStoreEventType, ForwardingEventHandler>
SystemMetricsPublisher:
<SystemMetricsEventType, ForwardingEventHandler>
分發事件處理器
事件處理器就像一個簡單的函數,只有一個 handle()
方法,傳入的是一個封裝好的事件Event:
這裏給出一個例子:
ApplicationEventDispatcher實現了EventHandler接口,用於接收Application的啓動命令,源碼位於 ResourceManager 中
調用事件過程
繼續查看上面 Dispatcher
接口發現,getEventHandler
方法是另一個核心方法,調用該方法主要是爲了“接收事件,放入中央異步調度器”。查看AsyncDispatcher.getEventHandler
代碼:
查看代碼可知,其核心代碼 eventQueue.put(event)
,相當於生產者,生產一個事件後能夠立即返回。下面舉例一個調用代碼(即上文分發事件處理器的調用代碼),源碼位於 RMAppManager
參考
Yarn的事件驅動模型與狀態機:https://monkeysayhi.github.io/2018/11/20/源碼|Yarn的事件驅動模型與狀態機