當(dāng)我們發(fā)布了一個(gè)appMaster時(shí),通常是appMaster來(lái)停止某個(gè)container,這個(gè)過(guò)程是調(diào)用NMClientImpl的stopContainer方法實(shí)現(xiàn)的。
客戶端請(qǐng)求實(shí)現(xiàn):

核心方法如下

首先hadoop內(nèi)部是使用基于GOOGLE的protocol buffers實(shí)現(xiàn)客戶端與服務(wù)端的RPC通信的。停止contianer的過(guò)程也是如此,客戶端會(huì)通過(guò)代理,向NodeManager發(fā)送rpc請(qǐng)求,然后服務(wù)端返回結(jié)果。下面我們來(lái)看服務(wù)端接受請(qǐng)求后的處理。
服務(wù)端接受請(qǐng)求實(shí)現(xiàn):
通過(guò)斷點(diǎn),我們找到了服務(wù)端接受rpc消息的地方來(lái)做stopContainers處理,之后,我們一步一步跟斷點(diǎn)。


啟動(dòng)dispatcher.getEventHandler().handle()是這個(gè)方法的核心。我們可以看到,nodeManager封裝了一個(gè)ContainerKill事件,然后由對(duì)應(yīng)的handler來(lái)進(jìn)行處理。
說(shuō)到這里,我們可以順便看一下所有的container事件實(shí)現(xiàn)。

我們知道yarn使用container來(lái)表示資源,由上圖可知,yarn對(duì)于container的申請(qǐng),初始化,銷毀,成功完成退出都是基于這種事件模型由相關(guān)的handler處理的。
我們接著跟進(jìn)斷點(diǎn):

我們發(fā)現(xiàn),最后是將這個(gè)事件放到了一個(gè)事件隊(duì)列中,這是一個(gè)異步處理,這里沒有真正的殺死container,然后我們來(lái)看哪個(gè)任務(wù)會(huì)從中取出這個(gè)事件,來(lái)真正的殺死container。
執(zhí)行found usage:

找到相關(guān)代碼,標(biāo)記斷點(diǎn):

我們可以看到這里有一個(gè)循環(huán)取事件并處理的方法,其中的dispatch(event)是這個(gè)方法的核心。
這個(gè)方法是這里啟動(dòng)的。

我們跟進(jìn)斷點(diǎn),查看dispatch內(nèi)的內(nèi)容

這里根據(jù)事件類型,在一個(gè)類似于事件調(diào)度注冊(cè)中心中,找到了對(duì)應(yīng)的handler,然后進(jìn)行handle。繼續(xù)跟進(jìn)斷點(diǎn),查看handle內(nèi)的內(nèi)容


最后我們看到,這里是更改了contianer的狀態(tài),hadoop-yarn的狀態(tài)機(jī)模式很復(fù)雜,這里不過(guò)多說(shuō)明,不過(guò)此時(shí)雖然更改了contianer的狀態(tài),還是沒有真正執(zhí)行container清理工作。
我們繼續(xù)跟進(jìn)斷點(diǎn)。

這里,從事件隊(duì)列中由取到了一個(gè)事件,這個(gè)事件類型像是執(zhí)行清理工作。

我們可以看到這里取得的handler為ContainersLauncher,之前修改狀態(tài)的為ContainerManagerImpl.他們都是EventHandler的實(shí)現(xiàn)類。我們繼續(xù)跟進(jìn)斷點(diǎn):

進(jìn)入handle方法,最后執(zhí)行l(wèi)aucher.cleanupContainer()

這個(gè)方法過(guò)長(zhǎng),不過(guò)上面的截圖已經(jīng)找到了清除container的核心,ContainerLauch的cleanupConatiner()方法,其中核心步驟為這一段:

其中sleepDelayBeforeSigKill默認(rèn)是250,這個(gè)參數(shù)可以在yarn-site.xml中配置,

我們跟進(jìn)斷點(diǎn),來(lái)看下exec.signalContainer(user, processId, signal)

進(jìn)入killContainer

不斷的斷點(diǎn)后,進(jìn)入了如下方法:

最后我們得出結(jié)論,nodeManager的事件處理模型,收到containerKill事件后,會(huì)對(duì)相關(guān)container進(jìn)行狀態(tài)修改操作,狀態(tài)修改成功后,會(huì)發(fā)送一個(gè)cleanContainer事件,之后便是真正的對(duì)contianer進(jìn)行清理操作,清理的方式說(shuō)到底就是java本地運(yùn)行shell通過(guò)ProcessBuilder。nodeManager是先找到container對(duì)應(yīng)的pid,然后執(zhí)行kill -15 pid過(guò)了sleepDelayBeforeSigKill毫秒后,再執(zhí)行kill -9 pid,現(xiàn)在我們清楚container是如何被停止的了。
最后說(shuō)一下,kill -15 pid會(huì)嘗試殺掉此進(jìn)程跟與其相關(guān)的所有子進(jìn)程,而kill-9只會(huì)殺掉此進(jìn)程,不會(huì)殺掉其子進(jìn)程,所以,這里先執(zhí)行kill -15是合理的。如果直接執(zhí)行kill -9會(huì)導(dǎo)致子進(jìn)程不會(huì)被回收,最后成為僵尸進(jìn)程,操作資源無(wú)法回收。不過(guò)kill -15不能完全保證子進(jìn)程一定被殺掉。所以,穩(wěn)妥起見,開發(fā)者最好還是有自己的監(jiān)控策略,來(lái)保證相關(guān)資源被回收。