很多朋友在剛開(kāi)始搭建和使用 YARN 集群的時(shí)候,很容易就被紛繁復(fù)雜的配置參數(shù)搞暈了:參數(shù)名稱相近、新老命名摻雜、文檔說(shuō)明模糊 。特別是那幾個(gè)關(guān)于內(nèi)存的配置參數(shù),即使看好幾遍文檔也不能完全弄懂含義不說(shuō),配置時(shí)一不小心就會(huì)張冠李戴,犯錯(cuò)誤。
如果你同樣遇到了上面的問(wèn)題,沒(méi)有關(guān)系,在這篇文章中,我就為大家梳理一下 YARN 的幾個(gè)不易理解的內(nèi)存配置參數(shù),并結(jié)合源碼闡述它們的作用和原理,讓大家徹底清楚這些參數(shù)的含義。
一、YARN 的基本架構(gòu)
介紹 YARN 框架的介紹文章網(wǎng)上隨處都可以找到,我這里就不做詳細(xì)闡述了。之前我的文章“YARN環(huán)境中應(yīng)用程序JAR包沖突問(wèn)題的分析及解決”中也對(duì) YARN 的一些知識(shí)點(diǎn)做了總結(jié),大家可以在TheFortyTwo 后臺(tái)回復(fù)編號(hào) 0x0002 獲得這篇文章的推送。下面附上一張 YARN 框架圖,方便引入我們的后續(xù)內(nèi)容:
圖 1: YARN 架構(gòu)圖
二、內(nèi)存相關(guān)參數(shù)梳理
YARN 中關(guān)于內(nèi)存配置的參數(shù)呢,乍一看有很多,其實(shí)主要也就是那么幾個(gè)(如果你感覺(jué)實(shí)際接觸到的比這更多更混亂,是因?yàn)榇蟛糠值呐渲脜?shù)都有新命名和舊命名,我后面會(huì)分別解釋),我已經(jīng)整理出來(lái)列在了下表中。大家先看一下,對(duì)于表中各列的意義,我會(huì)在本節(jié)后面詳細(xì)說(shuō)明;而對(duì)于每個(gè)參數(shù)的意義,我會(huì)放在下節(jié)進(jìn)行詳細(xì)解釋。
圖 2: 內(nèi)存參數(shù)整理圖
下面我們解釋一下表中的各列:
配置對(duì)象:指參數(shù)是針對(duì)何種組件起作用;
參數(shù)名稱:這個(gè)不用解釋,大家都明白;
舊參數(shù)名稱:大家都知道,MapReduce 在大版本上,經(jīng)歷了 MR1 和 MR on YARN;而小版本則迭代了不計(jì)其數(shù)次。版本的演進(jìn)過(guò)程中,開(kāi)發(fā)人員發(fā)現(xiàn)很多參數(shù)的命名不夠標(biāo)準(zhǔn),就對(duì)參數(shù)名稱做了修改;但是為了保證程序的前后兼容,仍然保留了舊參數(shù)名稱的功能。這樣等于是實(shí)現(xiàn)同一個(gè)功能的參數(shù),就有了新舊兩種不同的名稱。比如 mapreduce.map.java.opts 和 mapred.map.child.java.opts 兩個(gè)參數(shù),其實(shí)是等價(jià)的。那如果新舊兩個(gè)參數(shù)都設(shè)置了情況下,哪個(gè)參數(shù)會(huì)實(shí)際生效呢?Hadoop 的規(guī)則是,新參數(shù)設(shè)置了的話,會(huì)使用新參數(shù),否則才會(huì)使用舊參數(shù)設(shè)置的值,而與你設(shè)置參數(shù)的順序無(wú)關(guān);
缺省值:如果沒(méi)有設(shè)置參數(shù)的話,Hadoop 使用的默認(rèn)值。需要注意的是,并非所有參數(shù)的默認(rèn)值都是寫(xiě)在配置文件(如 mapred-default.xml)中的,比如 mapreduce.map.java.opts 這個(gè)參數(shù),它的取值是在創(chuàng)建 Map Task 前,通過(guò)下面代碼獲得的:
if (isMapTask) {
userClasspath = jobConf.get(“mapreduce.map.java.opts”,
jobConf.get( “mapred.child.java.opts”, “-Xmx200m"));
…
}
可以看到,這個(gè)參數(shù)的取值優(yōu)先級(jí)是:
mapreduce.map.java.opts > mapred.child.java.opts > -Xmx200m
所在配置文件:指明了如果你想靜態(tài)配置這個(gè)參數(shù)(而非在程序中調(diào)用 API 動(dòng)態(tài)設(shè)置參數(shù)),應(yīng)該在哪個(gè)配置文件中進(jìn)行設(shè)置比較合適;
三、各參數(shù)終極解釋
下面我們分別來(lái)講解每個(gè)參數(shù)的功能和意義。
mapreduce.map.java.opts 和 mapreduce.map.memory.mb
我反復(fù)斟酌了一下,覺(jué)得這兩個(gè)參數(shù)還是要放在一起講才容易讓大家理解,否則割裂開(kāi)會(huì)讓大家困惑更大。這兩個(gè)參數(shù)的功能如下:
mapreduce.map.java.opts: 運(yùn)行 Map 任務(wù)的 JVM 參數(shù),例如 -Xmx 指定大內(nèi)存大小;
mapreduce.map.memory.mb: Container 這個(gè)進(jìn)程的大可用內(nèi)存大小。
這兩個(gè)參數(shù)是怎樣一種聯(lián)系呢?首先大家要了解 Container 是一個(gè)什么樣的進(jìn)程(想詳細(xì)了解的話,就真的需要大家去看我的另一篇文章“YARN環(huán)境中應(yīng)用程序JAR包沖突問(wèn)題的分析及解決”,回復(fù)編號(hào)0x0002)。簡(jiǎn)單地說(shuō),Container 其實(shí)就是在執(zhí)行一個(gè)腳本文件(launch_container.sh),而腳本文件中,會(huì)執(zhí)行一個(gè) Java 的子進(jìn)程,這個(gè)子進(jìn)程就是真正的 Map Task。
圖 3: Container 和 Map Task 的關(guān)系圖
理解了這一點(diǎn)大家就明白了,mapreduce.map.java.opts 其實(shí)就是啟動(dòng) JVM 虛擬機(jī)時(shí),傳遞給虛擬機(jī)的啟動(dòng)參數(shù),而默認(rèn)值 -Xmx200m 表示這個(gè) Java 程序可以使用的大堆內(nèi)存數(shù),一旦超過(guò)這個(gè)大小,JVM 就會(huì)拋出 Out of Memory 異常,并終止進(jìn)程。而 mapreduce.map.memory.mb 設(shè)置的是 Container 的內(nèi)存上限,這個(gè)參數(shù)由 NodeManager 讀取并進(jìn)行控制,當(dāng) Container 的內(nèi)存大小超過(guò)了這個(gè)參數(shù)值,NodeManager 會(huì)負(fù)責(zé) kill 掉 Container。在后面分析 yarn.nodemanager.vmem-pmem-ratio 這個(gè)參數(shù)的時(shí)候,會(huì)講解 NodeManager 監(jiān)控 Container 內(nèi)存(包括虛擬內(nèi)存和物理內(nèi)存)及 kill 掉 Container 的過(guò)程。
緊接著,一些深入思考的讀者可能就會(huì)提出這些問(wèn)題了:
Q: 上面說(shuō)過(guò),Container 只是一個(gè)簡(jiǎn)單的腳本程序,且里面僅運(yùn)行了一個(gè) JVM 程序,那么為何還需要分別設(shè)置這兩個(gè)參數(shù),而不能簡(jiǎn)單的設(shè)置 JVM 的內(nèi)存大小就是 Container的大?。?/p>
A: YARN 作為一個(gè)通用的計(jì)算平臺(tái),設(shè)計(jì)之初就考慮了各種語(yǔ)言的程序運(yùn)行于這個(gè)平臺(tái)之上,而非僅適用 Java 及 JVM。所以 Container 被設(shè)計(jì)成一個(gè)抽象的計(jì)算單元,于是它就有了自己的內(nèi)存配置參數(shù)。
Q: JVM 是作為 Container 的獨(dú)立子進(jìn)程運(yùn)行的,與 Container 是兩個(gè)不同的進(jìn)程。那么 JVM 使用的內(nèi)存大小是否受限于 Container 的內(nèi)存大小限制?也就是說(shuō),mapreduce.map.java.opts 參數(shù)值是否可以大于 mapreduce.map.memory.mb 的參數(shù)值?
A: 這就需要了解 NodeManager 是如何管理 Container 內(nèi)存的了。NodeManager 專門(mén)有一個(gè) monitor 線程,時(shí)刻監(jiān)控所有 Container 的物理內(nèi)存和虛擬內(nèi)存的使用情況,看每個(gè) Container 是否超過(guò)了其預(yù)設(shè)的內(nèi)存大小。而計(jì)算 Container 內(nèi)存大小的方式,是計(jì)算 Container 的所有子進(jìn)程所用內(nèi)存的和。上面說(shuō)過(guò)了,JVM 是 Container 的子進(jìn)程,那么 JVM 進(jìn)程使用的內(nèi)存大小,當(dāng)然就算到了 Container 的使用內(nèi)存量之中。一旦某個(gè) Container 使用的內(nèi)存量超過(guò)了其預(yù)設(shè)的內(nèi)存量,則 NodeManager 就會(huì)無(wú)情地 kill 掉它。
mapreduce.reduce.java.opts 和 mapred.job.reduce.memory.mb
和上面介紹的參數(shù)類(lèi)似,區(qū)別就是這兩個(gè)參數(shù)是針對(duì) Reducer 的。
mapred.child.java.opts
這個(gè)參數(shù)也已經(jīng)是一個(gè)舊的參數(shù)了。在老版本的 MR 中,Map Task 和 Reduce Task 的 JVM 內(nèi)存配置參數(shù)不是分開(kāi)的,由這個(gè)參數(shù)統(tǒng)一指定。也就是說(shuō),這個(gè)參數(shù)其實(shí)已經(jīng)分成了 mapreduce.map.java.opts 和 mapreduce.reduce.java.opts 兩個(gè),分別控制 Map Task 和 Reduce Task。但是為了前后兼容,這個(gè)參數(shù)在 Hadoop 源代碼中仍然被使用,使用的地方上面章節(jié)已經(jīng)講述過(guò)了,這里再把優(yōu)先級(jí)列一下:
mapreduce.map.java.opts > mapred.child.java.opts > -Xmx200m
yarn.nodemanager.resource.memory-mb
從這個(gè)參數(shù)開(kāi)始,我們來(lái)看 NodeManager 的配置項(xiàng)。
這個(gè)參數(shù)其實(shí)是設(shè)置 NodeManager 預(yù)備從本機(jī)申請(qǐng)多少內(nèi)存量的,用于所有 Container 的分配及計(jì)算。這個(gè)參數(shù)相當(dāng)于一個(gè)閾值,限制了 NodeManager 能夠使用的服務(wù)器的大內(nèi)存量,以防止 NodeManager 過(guò)度消耗系統(tǒng)內(nèi)存,導(dǎo)致最終服務(wù)器宕機(jī)。這個(gè)值可以根據(jù)實(shí)際服務(wù)器的配置及使用,適度調(diào)整大小。例如我們的服務(wù)器是 96GB 的內(nèi)存配置,上面部署了 NodeManager 和 HBase,我們?yōu)?NodeManager 分配了 52GB 的內(nèi)存。
yarn.nodemanager.vmem-pmem-ratio 和 yarn.nodemanager.vmem-check-enabled
yarn.nodemanager.vmem-pmem-ratio 這個(gè)參數(shù)估計(jì)是最讓人困惑的了。網(wǎng)上搜出的資料大都出自官方文檔的解釋,不夠清晰明徹。下面我結(jié)合源代碼和大家解釋一下這個(gè)參數(shù)到底在控制什么。
首先,NodeManager 接收到 AppMaster 傳遞過(guò)來(lái)的 Container 后,會(huì)用 Container 的物理內(nèi)存大小 (pmem) * yarn.nodemanager.vmem-pmem-ratio 得到 Container 的虛擬內(nèi)存大小的限制,即為 vmemLimit:
long pmemBytes = container.getResource().getMemory() * 1024 * 1024L;
float pmemRatio = container.daemonConf.getFloat(YarnConfiguration.NM_VMEM_PMEM_RATIO, YarnConfiguration.DEFAULT_NM_VMEM_PMEM_RATIO);
long vmemBytes = (long) (pmemRatio * pmemBytes);
然后,NodeManager 在 monitor 線程中監(jiān)控 Container 的 pmem(物理內(nèi)存)和 vmem(虛擬內(nèi)存)的使用情況。如果當(dāng)前 vmem 大于 vmemLimit 的限制,或者 olderThanAge(與 JVM 內(nèi)存分代相關(guān))的內(nèi)存大于限制,則 kill 掉進(jìn)程:
if (currentMemUsage > (2 * vmemLimit)) {
isOverLimit = true;
} else if (curMemUsageOfAgedProcesses > vmemLimit) {
isOverLimit = true;
}
kill 進(jìn)程的代碼如下:
if (isMemoryOverLimit) {
// kill the container
eventDispatcher.getEventHandler().handle(new ContainerKillEvent(containerId, msg));
}
上述控制是針對(duì)虛擬內(nèi)存的,針對(duì)物理內(nèi)存的使用 YARN 也有類(lèi)似的監(jiān)控,讀者可以自行從源碼中進(jìn)行探索。yarn.nodemanager.vmem-check-enabled 參數(shù)則十分簡(jiǎn)單,就是上述監(jiān)控的開(kāi)關(guān)。
上面的介紹提到了 vmemLimit,也許大家會(huì)有個(gè)疑問(wèn):這里的 vmem 究竟是否是 OS 層面的虛擬內(nèi)存概念呢?我們來(lái)看一下源碼是怎么做的。
ContainerMontor 就是上述所說(shuō)的 NodeManager 中監(jiān)控每個(gè) Container 內(nèi)存使用情況的 monitor,它是一個(gè)獨(dú)立線程。ContainerMonitor 獲得單個(gè) Container 內(nèi)存(包括物理內(nèi)存和虛擬內(nèi)存)使用情況的邏輯如下:
Monitor 每隔 3 秒鐘就更新一次每個(gè) Container 的使用情況;更新的方式是:
查看 /proc/pid/stat 目錄下的所有文件,從中獲得每個(gè)進(jìn)程的所有信息;
根據(jù)當(dāng)前 Container 的 pid 找出其所有的子進(jìn)程,并返回這個(gè) Container 為根節(jié)點(diǎn),子進(jìn)程為葉節(jié)點(diǎn)的進(jìn)程樹(shù);在 Linux 系統(tǒng)下,這個(gè)進(jìn)程樹(shù)保存在 ProcfsBasedProcessTree 類(lèi)對(duì)象中;
然后從 ProcfsBasedProcessTree 類(lèi)對(duì)象中獲得當(dāng)前進(jìn)程 (Container) 總虛擬內(nèi)存量和物理內(nèi)存量。
由此大家應(yīng)該立馬知道了,內(nèi)存量是通過(guò) /proc/pid/stat 文件獲得的,且獲得的是該進(jìn)程及其所有子進(jìn)程的內(nèi)存量。所以,這里的 vmem 就是 OS 層面的虛擬內(nèi)存概念。
圖 4: 內(nèi)存參數(shù)的組合示意圖
四、結(jié)語(yǔ)
本文帶大家深入剖析了 YARN 中幾個(gè)容易混淆的內(nèi)存參數(shù),大家可以見(jiàn)微知著,從文章分析問(wèn)題的角度找出同類(lèi)問(wèn)題的分析方法,文檔與源碼相結(jié)合,更深入了解隱藏在框架之下的秘密。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。