hadoop叢集System Cpu消耗過高問題分析--記憶體碎片整合問題
Hadoop叢集伺服器升級為rhel6核心後,System Cpu佔用非常高,有任務執行的時候經常到50%以上。對其中一臺機器一天的執行狀態取樣的資料:
idle: 76% sys:14% user: 9%
從取樣資料中,可以發現System Cpu比User Cpu還要高,這在Hadoop叢集環境中很不尋常。
先簡單地用strace看了一下佔用cpu高的java程式經常去調哪些系統呼叫,發現sched_yield呼叫頻率非常之高,莫非是鎖的問題?分析了下核心中的文件和程式碼,發現CFS排程下sched_yield的行為與以前的O(1)演算法略有出入——CFS下sched_yield返回非常快,對於一些藉助sched_yield實現鎖的應用來說,開銷會很大。核心提供了一個proc引數sched_compat_yield,設定該引數為1,就可以解決這個問題。於是設定了該引數,仍然沒有效果,分析程式碼後,竟然發現sched_compat_yield在rhel6核心中並沒有實現,只是留下了一個介面相容而已。於是乎將upstream中的相關部分的程式碼port到rhel6的核心中,sched_compact_yield終於能幹活了,但出乎意料的是,系統態cpu仍然非常高。
沒辦法了,上個大招:oprofile,結果如下:
samples % symbol name
2822865 71.2192 compact_zone
160729 4.0551 clear_page_c
156913 3.9588 compaction_alloc
47691 1.2032 copy_user_generic_string
一看到結果,一頭霧水。compact_zone為何物?為何cpu佔用如此之高?不懂了就看程式碼。
__alloc_pages_slowpath
__alloc_pages_direct_compact
try_to_compact_pages
compact_zone_order
compact_order
有點頭緒了,核心要分配一塊高階實體記憶體,buddy system中又沒有滿足條件的,似乎核心要在compact_zone中做些什麼事,來滿足對高階實體記憶體的分配。
下一步,快速驗證下是不是compact_zone的問題,修改config檔案,去掉CONFIG_COMPACTION,重新編譯,換核心,竟然真的OK了 。 那基本斷定是compact_zone的問題了,後面就得分析下程式碼,研究下其中的原理了。
經過幾天的艱苦奮戰,終於把compaction的基本原理搞明白了。
linux實體記憶體的管理採用的是經典的夥伴系統,當然也就存在夥伴系統的問題——記憶體碎片。當然,此處的記憶體碎片問題並不算大,因為夥伴系統是以頁為單位為管理記憶體的,碎片也是以“頁”為單位,4k的實體記憶體還算不上是“碎片”。對於使用者態的程式,幾乎不需要超過4k的連續空間。但是對核心來說,碎片永遠都不是好東西。某些硬體相關的操作會需要連續的實體記憶體,如果無法滿足,核心就只能panic。
另外,引入compaction的另一個重要因素就是使用THP(Transparent hugepages)。4k的頁面大小已經出現了很多年了,就像檔案系統上1k-4k的block_size一樣,都是適應二十年前硬體的容量與速度而出現的,對於現在的硬體來說它們都顯得太小了。使用更大的物理頁,可以帶來兩個好處:TLB快取命中率的提高和page_fault的次數降低。compaction正是為了支援THP而出現的。
在以前版本的核心中,要獲得連續的實體記憶體只有一個辦法:釋放掉一部分記憶體,一般是釋放page cache、髒頁,或者進行頁面swap。
而compaction提出了另外一個思路:重新組織記憶體。為此,提出了“可移動”頁面的概念。在核心中的實體記憶體,有一部分是“可移動”的,核心使用的反碎片技術的基本原理,就是根據頁的“可移動性”將頁面分組。
那哪些頁面是可以移動的呢? 非空閒的實體記憶體,當然要麼是使用者態程序在用,要麼核心本身在用。對於前者,程序在訪問實體記憶體的時候,實際上要通過頁表的對映來訪問。頁表是一個可以做文章的地方:如果把一個頁移動到另一個地方,如果可以同時修改頁表,那麼對應用程式就不會有影響。而對於核心訪問實體記憶體時,是通過簡單的常量偏移來做的。因此核心使用的物理頁面無法移動。
定義了“可移動”的頁面,具體到某一個頁面,核心怎樣知道它是否是可移動的?分配記憶體的函式,kmalloc,alloc_pages等在任何地方都可能被呼叫。核心又是怎樣知道在這些地方分配的頁面屬於哪種型別呢?看這幾個函式的原型
void *kmalloc(size_t size, gfp_t flags)
struct page * alloc_pages(gfp_t gfp_mask, unsigned int order)
核心自然不知道kmalloc分配的記憶體是作什麼用途的,但是kernel 開發者知道,一個頁面是否可移動,自然也是開發者們告訴核心的。gft_t中有個標誌位:GFP_MOVABLE,開發者需要根據相應的記憶體是否要移動來設定該位。
瞭解瞭如何識別“可移動”頁面,下面看看頁面移動的流程:
1. 鎖定頁,以避免在移動頁的過程中有程序修改頁面。頁面記為oldpage
2. 確保“writeback”已經完成
3. 刪除當前頁面的全部對映,並將指向該頁的頁表項標記MIGRATION
4. 查詢新頁,記為newpage
5. 獲取radix tree的鎖,以阻塞所有試圖通過radix tree來訪問頁面的程序。將radix tree中oldpage的指標指向newpage。釋放radix tree的鎖。
6. 舊頁的內容被拷到新頁面中,設定新頁面的各項標誌
7. 將所有頁表項指向新頁面
瞭解了compaction的目標和原理,那麼該怎樣檢視系統中當前的碎片情況呢?/proc/pagetypeinfo檔案提供了“可移動”和“不可移動”頁面的分佈資料, 一方面方便開發者除錯,另一方面可以讓系統管理員瞭解當前的系統執行狀態。
Compaction在hadoop上所帶來的效能問題,目前還不知道是在這種特定場景下才出現還是compaction本身就影響了效能。不過現在看來,在其它機器上還沒有發現這種情況。
Compaction的目的是減少記憶體碎片,主要和THP搭配使用,適合需要大量連續記憶體的應用,比如KVM,能提升TLB效率和減少page fault次數,從而提高應用程式的執行效率。因此,去掉Compaction的支援,會對此類應用的效能所有影響。