Cgroup - Linux 內存資源管理

jopen 8年前發布 | 15K 次閱讀 Linux

Cgroup - Linux 內存資源管理

Zorro ] icon

Hi ,我是 Zorro 。這是我的微博地址,我會不定期在這里更新文章,如果你有興趣,可以來關注我呦。

另外,我的其他聯系方式:

Email: mini.jerry@gmail.com

QQ: 30007147

本文PDF

在聊 cgroup 的內存限制之前,我們有必要先來講解一下:

Linux 內存管理基礎知識

free 命令

無論從任何角度看, Linux 的內存管理都是一坨麻煩的事情,當然我們也可以用一堆、一片、一塊、一筐來形容這個事情,但是毫無疑問,用一坨來形容它簡直恰當無比。在理解它之前,我甚至不會相信精妙的和惡心可以同時形容同一件事情,是的,在我看來它就是這樣的。其實我只是做個鋪墊,讓大家明白,我們下面要講的內容,絕不是一個成體系的知識,所以,學習起來也確實很麻煩。甚至,我寫這個技術文章之前一度考慮了很久該怎么寫?從哪里開始寫?思考了半天,還是不能免俗,我們無奈,仍然先從 free 命令說起:

[root@zorrozou-pc ~]# free
             total       used       free     shared    buffers     cached
Mem:     131904480    6681612  125222868          0     478428    4965180
-/+ buffers/cache:    1238004  130666476
Swap:      2088956          0    2088956

這個命令幾乎是每一個使用過 Linux 的人必會的命令,但越是這樣的命令,似乎真正明白的人越少(我是說比例越少)。一般情況下,對此命令的理解可以分這幾個階段:

  1. 我擦,內存用了好多, 6 個多 G ,可是我什么都沒有運行啊?為什么會這樣? Linux 好占內存。
  2. 嗯,根據我專業的眼光看出來,內存才用了 1G 多點,還有很多剩余內存可用。 buffers/cache 占用的較多,說明系統中有進程曾經讀寫過文件,但是不要緊,這部分內存是當空閑來用的。
  3. free 顯示的是這樣,好吧我知道了。神馬?你問我這些內存夠不夠,我當然不知道啦!我特么怎么知道你程序怎么寫的?

如果你的認識在第一種階段,那么請你繼續補充關于 Linux 的 buffers / cache 的知識。如果你處在第二階段,好吧,你已經是個老手了,但是需要提醒的是,上帝給你關上一扇門的同時,肯定都會給你放一條狗的。是的, Linux 的策略是:內存是用來用的,而不是用來看的。但是,只要是用了,就不是沒有成本的。有什么成本,憑你對 buffer/cache 的理解,應該可以想的出來。一般我比較認同第三種情況,一般光憑一個 free 命令的顯示,是無法判斷出任何有價值的信息的,我們需要結合業務的場景以及其他輸出綜合判斷目前遇到的問題。當然也可能這種人給人的第一感覺是他很外行,或者他真的是外行。

無論如何, free 命令確實給我門透露了一些有用的信息,比如內存總量,剩余多少,多少用在了 buffers / cache 上, Swap 用了多少,如果你用了其它參數還能看到一些其它內容,這里不做一一列舉。那么這里又引申出另一些概念,什么是 buffer ?什么是 cache ?什么是 swap ?由此我們就直接引出另一個命令:

[root@zorrozou-pc ~]# cat /proc/meminfo
MemTotal:       131904480 kB
MemFree:        125226660 kB
Buffers:          478504 kB
Cached:          4966796 kB
SwapCached:            0 kB
Active:          1774428 kB
Inactive:        3770380 kB
Active(anon):     116500 kB
Inactive(anon):     3404 kB
Active(file):    1657928 kB
Inactive(file):  3766976 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:       2088956 kB
SwapFree:        2088956 kB
Dirty:               336 kB
Writeback:             0 kB
AnonPages:         99504 kB
Mapped:            20760 kB
Shmem:             20604 kB
Slab:             301292 kB
SReclaimable:     229852 kB
SUnreclaim:        71440 kB
KernelStack:        3272 kB
PageTables:         3320 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:    68041196 kB
Committed_AS:     352412 kB
VmallocTotal:   34359738367 kB
VmallocUsed:      493196 kB
VmallocChunk:   34291062284 kB
HardwareCorrupted:     0 kB
AnonHugePages:     49152 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:      194816 kB
DirectMap2M:     3872768 kB
DirectMap1G:    132120576 kB

以上顯示的內容都是些什么鬼?

其實這個問題的答案也是另一個問題的答案,即: Linux 是如何使用內存的?了解清楚這個問題是很有必要的,因為只有先知道了 Linux 如何使用內存,我們在能知道內存可以如何限制,以及,做了限制之后會有什么問題?我們在此先例舉出幾個常用概念的意義:

內存,作為一種相對比較有限的資源,內核在考慮其管理時,無非應該主要從以下出發點考慮:

  1. 內存夠用時怎么辦?
  2. 內存不夠用時怎么辦?

在內存夠用時,內核的思路是,如何盡量提高資源的利用效率,以加快系統整體響應速度和吞吐量?于是內存作為一個 CPU 和 I / O 之間的大 buffer 的功能就呼之欲出了。為此,內核設計了以下系統來做這個功能:

Buffers / Cached

buffer 和 cache 是兩個在計算機技術中被用濫的名詞,放在不通語境下會有不同的意義。在內存管理中,我們需要特別澄清一下,這里的 buffer 指 Linux 內存的: Buffer cache 。這里的 cache 指 Linux 內存中的: Page cache 。翻譯成中文可以叫做緩沖區緩存和頁面緩存。在歷史上,它們一個( buffer )被用來當成對 io 設備寫的緩存,而另一個( cache )被用來當作對 io 設備的讀緩存,這里的 io 設備,主要指的是塊設備文件和文件系統上的普通文件。但是現在,它們的意義已經不一樣了。在當前的內核中, page cache 顧名思義就是針對內存頁的緩存,說白了就是,如果有內存是以 page 進行分配管理的,都可以使用 page cache 作為其緩存來使用。當然,不是所有的內存都是以頁( page )進行管理的,也有很多是針對塊( block )進行管理的,這部分內存使用如果要用到 cache 功能,則都集中到 buffer cache 中來使用。(從這個角度出發,是不是 buffer cache 改名叫做 block cache 更好?)然而,也不是所有塊( block )都有固定長度,系統上塊的長度主要是根據所使用的塊設備決定的,而頁長度在 X86 上無論是 32 位還是 64 位都是 4k 。

而明白了這兩套緩存系統的區別,也就基本可以理解它們究竟都可以用來做什么了。

什么是 page cache

Page cache 主要用來作為文件系統上的文件數據的緩存來用,尤其是針對當進程對文件有 read / write 操作的時候。如果你仔細想想的話,作為可以映射文件到內存的系統調用: mmap 是不是很自然的也應該用到 page cache ?如果你再仔細想想的話, malloc 會不會用到 page cache ?

以上提出的問題都請自己思考,本文檔不會給出標準答案。

在當前的實現里, page cache 也被作為其它文件類型的緩存設備來用,所以事實上 page cache 也負責了大部分的塊設備文件的緩存工作。

什么是 buffer cache

Buffer cache 則主要是設計用來在系統對塊設備進行讀寫的時候,對塊進行數據緩存的系統來使用。但是由于 page cache 也負責塊設備文件讀寫的緩存工作,于是,當前的 buffer cache 實際上要負責的工作比較少。這意味著某些對塊的操作會使用 buffer cache 進行緩存,比如我們在格式化文件系統的時候。

一般情況下兩個緩存系統是一起配合使用的,比如當我們對一個文件進行寫操作的時候, page cache 的內容會被改變,而 buffer cache 則可以用來將 page 標記為不同的緩沖區,并記錄是哪一個緩沖區被修改了。這樣,內核在后續執行臟數據的回寫( writeback )時,就不用將整個 page 寫回,而只需要寫回修改的部分即可。

有搞大型系統經驗的人都知道,緩存就像萬金油,只要哪里有速度差異產生的瓶頸,就可以在哪里抹。但是其成本之一就是,需要維護數據的一致性。內存緩存也不例外,內核需要維持其一致性,在臟數據產生較快或數據量較大的時候,緩存系統整體的效率一樣會下降,因為畢竟臟數據寫回也是要消耗 IO 的。這個現象也會表現在這樣一種情況下,就是當你發現 free 的時候,內存使用量較大,但是去掉了 buffer / cache 的使用之后剩余確很多。以一般的理解,都會認為此時進程如果申請內存,內核會將 buffer / cache 占用的內存當成空閑的內存分給進程,這是沒錯的。但是其成本是,在分配這部分已經被 buffer / cache 占用的內存的時候,內核會先對其上面的臟數據進行寫回操作,保證數據一致后才會清空并分給進程使用。如果此時你的進程是突然申請大量內存,而且你的業務是一直在產生很多臟數據(比如日志),并且系統沒有及時寫回的時候,此時系統給進程分配內存的效率會很慢,系統 IO 也會很高。那么此時你還以為 buffer / cache 可以當空閑內存使用么?

思考題: Linux 什么時候會將臟數據寫回到外部設備上?這個過程如何進行人為干預?

這足可以證明一點,以內存管理的復雜度,我們必須結合系統上的應用狀態來評估系統監控命令所給出的數據,才是做評估的正確途徑。如果你不這樣做,那么你就可以輕而易舉的得出“ Linux 系統好爛啊!“這樣的結論。也許此時,其實是你在這個系統上跑的應用很爛的緣故導致的問題。

接下來,當內存不夠用的時候怎么辦?

我們好像已經分析了一種內存不夠用的狀態,就是上述的大量 buffer / cache 把內存幾乎占滿的情況。但是基于 Linux 對內存的使用原則,這不算是不夠用,但是這種狀態導致 IO 變高了。我們進一步思考,假設系統已經清理了足夠多的 buffer / cache 分給了內存,而進程還在嚷嚷著要內存咋辦?

此時內核就要啟動一系列手段來讓進程盡量在此時能夠正常的運行下去。

請注意我在這說的是一種異常狀態!我之所以要這樣強調是因為,很多人把內存用滿了當稱一種正常狀態。他們認為,當我的業務進程在內存使用到壓力邊界的情況下,系統仍然需要保證讓業務進程有正常的狀態!這種想法顯然是緣木求魚了。另外我還要強調一點,系統提供的是內存管理的機制和手段,而內存用的好不好,主要是業務進程的事情,責任不能本末倒置。

誰該 SWAP ?

首先是 Swap 機制。 Swap 是交換技術,這種技術是指,當內存不夠用的時候,我們可以選擇性的將一塊磁盤、分區或者一個文件當成交換空間,將內存上一些臨時用不到的數據放到交換空間上,以釋放內存資源給急用的進程。

哪些數據可能會被交換出去呢?從概念上判斷,如果一段內存中的數據被經常訪問,那么就不應該被交換到外部設備上,因為這樣的數據如果交換出去的話會導致系統響應速度嚴重下降。內存管理需要將內存區分為活躍的( Active )和不活躍的( Inactive ),再加上一個進程使用的用戶空間內存映射包括文件影射( file )和匿名影射( anon ),所以就包括了 Active ( anon )、 Inactive ( anon )、 Active ( file )和 Inactive ( file )。你說神馬?啥是文件影射( file )和匿名影射( anon )?好吧,我們可以這樣簡單的理解,匿名影射主要是諸如進程使用 malloc 和 mmap 的 MAP_ANONYMOUS 的方式申請的內存,而文件影射就是使用 mmap 影射的文件系統上的文件,這種文件系統上的文件既包括普通的文件,也包括臨時文件系統( tmpfs )。這意味著, Sys V 的 IPC 和 POSIX 的 IPC ( IPC 是進程間通信機制,在這里主要指共享內存,信號量數組和消息隊列)都是通過文件影射方式體現在用戶空間內存中的。這兩種影射的內存都會被算成進程的 RSS ,但是也一樣會被顯示在 cache 的內存計數中,在相關 cgroup 的另一項統計中,共享內存的使用和文件緩存( file cache )也都會被算成是 cgroup 中的 cache 使用的總量。這個統計顯示的方法是:

[root@zorrozou-pc ~]# cat /cgroup/memory/memory.stat
cache 94429184
rss 102973440
rss_huge 50331648
mapped_file 21512192
swap 0
pgpgin 656572990
pgpgout 663474908
pgfault 2871515381
pgmajfault 1187
inactive_anon 3497984
active_anon 120524800
inactive_file 39059456
active_file 34484224
unevictable 0
hierarchical_memory_limit 9223372036854775807
hierarchical_memsw_limit 9223372036854775807
total_cache 94429184
total_rss 102969344
total_rss_huge 50331648
total_mapped_file 21520384
total_swap 0
total_pgpgin 656572990
total_pgpgout 663474908
total_pgfault 2871515388
total_pgmajfault 1187
total_inactive_anon 3497984
total_active_anon 120524800
total_inactive_file 39059456
total_active_file 34484224
total_unevictable 0

好吧,說了這么半天終于聯系到一個 cgroup 的內存限制相關的文件了。在這需要說明的是,你之所以看見我廢話這么多,是因為我們必須先基本理清楚 Linux 系統的內存管理方式,才能進一步對 cgroup 中的內存限制做規劃使用,否則同樣的名詞會有很多的歧義。就比如我們在觀察某一個 cgroup 中的 cache 占用數據的時候,我們究竟該怎么理解它?真的把它當成空閑空間來看么?

我們撤的有點遠,回過頭來說說這些跟 Swap 有什么關系?還是剛才的問題,什么內容該被從內存中交換出去呢?文件 cache 是一定不需要的,因為既然是 cache ,就意味著它本身就是硬盤上的文件(當然你現在應該知道了,它也不僅僅只有文件),那么如果是硬盤上的文件,就不用 swap 交換出去,只要寫回臟數據,保持數據一致之后清除就可以了,這就是剛才說過的緩存清楚機制。但是我們同時也要知道,并不是所有被標記為 cache 的空間都能被寫回硬盤的(是的,比如共享內存)。那么能交換出去內存應該主要包括有 Inactive ( anon )這部分內存。主要注意的是,內核也將共享內存作為計數統計近了 Inactive ( anon )中去了(是的,共享內存也可以被 Swap )。還要補充一點,如果內存被 mlock 標記加鎖了,則也不會交換,這是對內存加 mlock 鎖的唯一作用。剛才我們討論的這些計數,很可能會隨著 Linux 內核的版本改變而產生變化,但是在比較長的一段時間內,我們可以這樣理解。

我們基本搞清了 swap 這個機制的作用效果,那么既然 swap 是內部設備和外部設備的數據拷貝,那么加一個緩存就顯得很有必要,這個緩存就是 swapcache ,在 memory.stat 文件中, swapcache 是跟 anon page 被一起記錄到 rss 中的,但是并不包含共享內存。另外再說明一下, HugePages 也是不會交換的。顯然,當前的 swap 空間用了多少,總共多少,這些我們也可以在相關的數據中找到答案。

以上概念中還有一些名詞大家可能并不清楚其含義,比如 RSS 或 HugePages 。請自行查資料補上這些知識。為了讓大家真的理解什么是 RSS ,請思考 ps aux 命令中顯示的 VSZ , RSS 和 cat /proc/pid/smaps 中顯示的: PSS 這三個進程占用內存指標的差別?

何時 SWAP ?

搞清楚了誰該 swap ,那么還要知道什么時候該 swap 。這看起來比較簡單,內存耗盡而且 cache 也沒什么可以回收的時候就應該觸發 swap 。其實現實情況也沒這么簡單,實際上系統在內存壓力可能不大的情況下也會 swap ,這種情況并不是我們今天要討論的范圍。

思考題:除了內存被耗盡的時候要 swap ,還有什么時候會 swap ?如何調整內核 swap 的行為?如何查看當前系統的 swap 空間有哪些?都是什么類型?什么是 swap 權重? swap 權重有什么意義?

其實絕大多數場景下,什么時候 swap 并不重要,而 swap 之后的事情相對卻更重要。大多數的內存不夠用,只是臨時不夠用,比如并發突增等突發情況,這種情況的特點是時間持續短,此時 swap 機制作為一種臨時的中轉措施,可以起到對業務進程的保護作用。因為如果沒有 swap ,內存耗盡的結果一般都是觸發 oom killer ,會殺掉此時積分比較高的進程。如果更嚴重的話,內存不夠用還會觸發進程 D 狀態死鎖,這一般發生在多個進程同時要申請內存的時候,此時 oom killer 機制也可能會失效,因為需要被干掉的積分比較高的進程很可能就是需要申請內存的進程,而這個進程本身因為正在爭搶內存而導致陷入 D 狀態,那么此時 kill 就可能是對它無效的。

但是 swap 也不是任何時候都有很好的保護效果。如果內存申請是長期并大量的,那么交換出去的數據就會因為長時間駐留在外部設備上,導致進程調用這段內存的幾率大大增加,當進程很頻繁的使用它已經被交換出去的內存時,就會讓整個系統處在 io 繁忙的狀態,此時進程的響應速度會嚴重下降,導致整個系統夯死。對于系統管理員來說,這種情況是完全不能接受的,因為故障之后的第一要務是趕緊恢復服務,但是 swap 頻繁使用的 IO 繁忙狀態會導致系統除了斷電重啟之外,沒有其它可靠手段可以讓系統從這種狀態中恢復回來,所以這種情況是要盡力避免的。此時,如果有必要,我們甚至可以考慮不用 swap ,哪怕內存過量使用被 oom ,或者進程 D 狀態都是比 swap 導致系統卡死的情況更好處理的狀態。如果你的環境需求是這樣的,那么可以考慮關閉 swap 。

進程申請內存的時候究竟會發生什么?

剛才我們從系統宏觀的角度簡要說明了一下什么是 buffer / cache 以及 swap 。下面我們從一個更加微觀的角度來把一個內存申請的過程以及相關機制什么時候觸發給串聯起來。本文描述的過程是基于 Linux 3.10 內核版本的, Linux 4.1 基本過程變化不大。如果你想確認在你的系統上究竟是什么樣子,請自行翻閱相關內核代碼。

進程申請內存可能用到很多種方法,最常見的就是 malloc 和 mmap 。但是這對于我們并不重要,因為無論是 malloc 還是 mmap ,或是其他的申請內存的方法,都不會真正的讓內核去給進程分配一個實際的物理內存空間。真正會觸發分配物理內存的行為是缺頁異常

缺頁異常就是我們可以在 memory.stat 中看到的 total_pgfault ,這種異常一般分兩種,一種叫 major fault ,另一種叫 minor fault 。這兩種異常的主要區別是,進程所請求的內存數據是否會引發磁盤 io ?如果會引發,就是一個 majfault ,如果不引發,那就是 minfault 。就是說如果產生了 major fault ,這個數據基本上就意味著已經被交換到了 swap 空間上。

缺頁異常的處理過程大概可以整理為以下幾個路徑:

首先檢查要訪問的虛擬地址是否合法,如果合法則繼續查找和分配一個物理頁,步驟如下:

  1. 檢查發生異常的虛擬地址是不是在物理頁表中不存在?如果是,并且是匿名影射,則申請置 0 的匿名影射內存,此時也有可能是影射了某種虛擬文件系統,比如共享內存,那么就去影射相關的內存區,或者發生 COW 寫時復制申請新內存。如果是文件影射,則有兩種可能,一種是這個影射區是一個 page cache ,直接將相關 page cache 區影射過來即可,或者 COW 新內存存放需要影射的文件內容。如果 page cache 中不存在,則說明這個區域已經被交換到 swap 空間上,應該去處理 swap 。
  2. 如果頁表中已經存在需要影射的內存,則檢查是否要對內存進行寫操作,如果不寫,那就直接復用,如果要寫,就發生 COW 寫時復制,此時的 COW 跟上面的處理過程不完全相同,在內核中,這里主要是通過 do_wp_page 方法實現的。

如果需要申請新內存,則都會通過 allocpage_vma 申請新內存,而這個函數的核心方法是_alloc_pages_nodemask ,也就是 Linux 內核著名的內存管理系統伙伴系統的實現。

分配過程先會檢查空閑頁表中有沒有頁可以申請,實現方法是: getpage_from_freelist ,我們并不關心正常情況,分到了當然一切 ok 。更重要的是異常處理,如果空閑中沒有,則會進入_alloc_pages_slowpath 方法進行處理。這個處理過程的主邏輯大概這樣:

  1. 喚醒 kswapd 進程,把能換出的內存換出,讓系統有內存可用。
  2. 繼續檢查看看空閑中是否有內存。有了就 ok ,沒有繼續下一步:
  3. 嘗試清理 page cache ,清理的時候會將進程置為 D 狀態。如果還申請不到內存則:
  4. 啟動 oom killer 干掉一些進程釋放內存,如果這樣還不行則:
  5. 回到步驟 1 再來一次!

當然以上邏輯要符合一些條件,但是這一般都是系統默認的狀態,比如,你必須啟用 oom killer 機制等。另外這個邏輯中有很多其它狀態與本文無關,比如檢查內存水印、檢查是否是高優先級內存申請等等,當然還有關于 numa 節點狀態的判斷處理,我沒有一一列出。另外,以上邏輯中,不僅僅只有清理 cache 的時候會使進程進入 D 狀態,還有其它邏輯也會這樣做。這就是為什么在內存不夠用的情況下, oom killer 有時也不生效,因為可能要干掉的進程正好陷入這個邏輯中的 D 狀態了。

以上就是內存申請中,大概會發生什么的過程。當然,我們這次主要是真對本文的重點 cgroup 內存限制進行說明,當我們處理限制的時候,更多需要關心的是當內存超限了會發生什么?對邊界條件的處理才是我們這次的主題,所以我并沒有對正常申請到的情況做細節說明,也沒有對用戶態使用 malloc 什么時候使用 sbrk 還是 mmap 來申請內存做出細節說明,畢竟那是程序正常狀態的時候的事情,后續可以另寫一個內存優化的文章主要講解那部分。

下面我們該進入正題了:

Cgroup 內存限制的配置

當限制內存時,我們最好先想清楚如果內存超限了會發生什么?該怎么處理?業務是否可以接受這樣的狀態?這就是為什么我們在講如何限制之前說了這么多基礎知識的“廢話”。其實最簡單的莫過于如何進行限制了,我們的系統環境還是沿用上一次講解 CPU 內存隔離的環境,使用 cgconfig 和 cgred 服務進行 cgroup 的配置管理。還是創建一個 zorro 用戶,對這個用戶產生的進程進行內存限制。基礎配置方法不再多說,如果不知道的請參考這個文檔

環境配置好之后,我們就可以來檢查相關文件了。內存限制的相關目錄根據 cgconfig.config 的配置放在了 /cgroup/memory 目錄中,如果你跟我做了一樣的配置,那么這個目錄下的內容應該是這樣的:

[root@zorrozou-pc ~]# ls /cgroup/memory/
cgroup.clone_children  memory.failcnt                  memory.kmem.slabinfo                memory.kmem.usage_in_bytes  memory.memsw.limit_in_bytes      memory.oom_control          memory.usage_in_bytes  shrek
cgroup.event_control   memory.force_empty              memory.kmem.tcp.failcnt             memory.limit_in_bytes       memory.memsw.max_usage_in_bytes  memory.pressure_level       memory.use_hierarchy   tasks
cgroup.procs           memory.kmem.failcnt             memory.kmem.tcp.limit_in_bytes      memory.max_usage_in_bytes   memory.memsw.usage_in_bytes      memory.soft_limit_in_bytes           zorro
cgroup.sane_behavior   memory.kmem.limit_in_bytes      memory.kmem.tcp.max_usage_in_bytes  memory.meminfo              memory.move_charge_at_immigrate  memory.stat                 notify_on_release
jerry                  memory.kmem.max_usage_in_bytes  memory.kmem.tcp.usage_in_bytes      memory.memsw.failcnt        memory.numa_stat                 memory.swappiness           release_agent

其中, zorro 、 jerry 、 shrek 都是目錄概念跟 cpu 隔離的目錄樹結構類似。相關配置文件內容:

[root@zorrozou-pc ~]# cat /etc/cgconfig.conf    mount {
    cpu = /cgroup/cpu;
    cpuset  = /cgroup/cpuset;
    cpuacct = /cgroup/cpuacct;
    memory  = /cgroup/memory;
    devices = /cgroup/devices;
    freezer = /cgroup/freezer;
    net_cls = /cgroup/net_cls;
    blkio   = /cgroup/blkio;
}

group zorro {
    cpu {
        cpu.shares = 6000;
#       cpu.cfs_quota_us = "600000";
    }
    cpuset {
#       cpuset.cpus = "0-7,12-19";
#       cpuset.mems = "0-1";
    }
    memory {
    }
}

配置中添加了一個真對 memory 的空配置項,我們稍等下再給里面添加配置。

[root@zorrozou-pc ~]# cat /etc/cgrules.conf 
zorro       cpu,cpuset,cpuacct,memory   zorro
jerry       cpu,cpuset,cpuacct,memory   jerry
shrek       cpu,cpuset,cpuacct,memory   shrek

文件修改完之后記得重啟相關服務:

[root@zorrozou-pc ~]# service cgconfig restart
[root@zorrozou-pc ~]# service cgred restart

讓我們繼續來看看真對內存都有哪些配置參數:

[root@zorrozou-pc ~]# ls /cgroup/memory/zorro/
cgroup.clone_children  memory.kmem.failcnt             memory.kmem.tcp.limit_in_bytes      memory.max_usage_in_bytes        memory.memsw.usage_in_bytes      memory.soft_limit_in_bytes  
cgroup.event_control   memory.kmem.limit_in_bytes      memory.kmem.tcp.max_usage_in_bytes  memory.meminfo                   memory.move_charge_at_immigrate  memory.stat                 notify_on_release
cgroup.procs           memory.kmem.max_usage_in_bytes  memory.kmem.tcp.usage_in_bytes      memory.memsw.failcnt             memory.numa_stat                 memory.swappiness           tasks
memory.failcnt         memory.kmem.slabinfo            memory.kmem.usage_in_bytes          memory.memsw.limit_in_bytes      memory.oom_control               memory.usage_in_bytes
memory.force_empty     memory.kmem.tcp.failcnt         memory.limit_in_bytes               memory.memsw.max_usage_in_bytes  memory.pressure_level            memory.use_hierarchy

首先我們已經認識了 memory.stat 文件了,這個文件內容不能修改,它實際上是輸出當前 cgroup 相關內存使用信息的。常見的數據及其含義我們剛才也已經說過了,在此不再復述。

cgroup 內存限制

memory.memsw.limit_in_bytes:內存+ swap 空間使用的總量限制。

memory.limit_in_bytes:內存使用量限制。

這兩項的意義很清楚了,如果你決定在你的 cgroup 中關閉 swap 功能,可以把兩個文件的內容設置為同樣的值即可。至于為什么相信大家都能想清楚。

OOM 控制

memory.oom_control:內存超限之后的 oom 行為控制。
這個文件中有兩個值:

oom_kill_disable 0

默認為 0 表示打開 oom killer ,就是說當內存超限時會觸發干掉進程。如果設置為 1 表示關閉 oom killer ,此時內存超限不會觸發內核殺掉進程。而是將進程夯住( hang / sleep ),實際上內核中就是將進程設置為 D 狀態,并且將相關進程放到一個叫做 OOM-waitqueue 的隊列中。這時的進程可以 kill 殺掉。如果你想繼續讓這些進程執行,可以選擇這樣幾個方法:

  1. 增加內存,讓進程有內存可以繼續申請。
  2. 殺掉一些進程,讓本組內有內存可用。
  3. 把一些進程移到別的 cgroup 中,讓本 cgroup 內有內存可用。
  4. 刪除一些 tmpfs 的文件,就是占用內存的文件,比如共享內存或者其它會占用內存的文件。

說白了就是,此時只有當 cgroup 中有更多內存可以用了,在 OOM-waitqueue 隊列中被掛起的進程就可以繼續運行了。

under_oom 0

這個值只是用來看的,它表示當前的 cgroup 的狀態是不是已經 oom 了,如果是,這個值將顯示為 1 。我們就是通過設置和監測這個文件中的這兩個值來管理 cgroup 內存超限之后的行為的。在默認場景下,如果你使用了 swap ,那么你的 cgroup 限制內存之后最常見的異常效果是 IO 變高,如果業務不能接受,我們一般的做法是關閉 swap ,那么 cgroup 內存 oom 之后都會觸發 kill 掉進程,如果我們用的是 LXC 或者 Docker 這樣的容器,那么還可能干掉整個容器。當然也經常會因為 kill 進程的時候因為進程處在 D 狀態,而導致整個 Docker 或者 LXC 容器根本無法被殺掉。至于原因,在前面已經說的很清楚了。當我們遇到這樣的困境時該怎么辦?一個好的辦法是,關閉 oom killer ,讓內存超限之后,進程掛起,畢竟這樣的方式相對可控。此時我們可以檢查 under_oom 的值,去看容器是否處在超限狀態,然后根據業務的特點決定如何處理業務。我推薦的方法是關閉部分進程或者重啟掉整個容器,因為可以想像,容器技術所承載的服務應該是在整體軟件架構上有容錯的業務,典型的場景是 web 服務。容器技術的特點就是生存周期短,在這樣的場景下,殺掉幾個進程或者幾個容器,都應該對整體服務的穩定性影響不大,而且容器的啟動速度是很快的,實際上我們應該認為,容器的啟動速度應該是跟進程啟動速度可以相媲美的。你的業務會因為死掉幾個進程而表現不穩定么?如果不會,請放心的干掉它們吧,大不了很快再啟動起來就是了。但是如果你的業務不是這樣,那么請根據自己的情況來制定后續處理的策略。

當我們進行了內存限制之后,內存超限的發生頻率要比使用實體機更多了,因為限制的內存量一般都是小于實際物理內存的。所以,使用基于內存限制的容器技術的服務應該多考慮自己內存使用的情況,尤其是內存超限之后的業務異常處理應該如何讓服務受影響的程度降到更低。在系統層次和應用層次一起努力,才能使內存隔離的效果達到最好。

內存資源審計

memory.memsw.usage_in_bytes:當前 cgroup 的內存+ swap 的使用量。

memory.usage_in_bytes:當前 cgroup 的內存使用量。

memory.max_usage_in_bytes:cgroup 最大的內存+ swap 的使用量。

memory.memsw.max_usage_in_bytes:cgroup 的最大內存使用量。

最后

Linux 的內存限制要說的就是這么多了,當我們限制了內存之后,相對于使用實體機,實際上對于應用來說可用內存更少了,所以業務會相對更經常地暴露在內存資源緊張的狀態下。相對于虛擬機( kvm , xen ),多個 cgroup 之間是共享內核的,我們可以從內存限制的角度思考一些關于“容器”技術相對于虛擬機和實體機的很多特點:

  1. 內存更緊張,應用的內存泄漏會導致相對更嚴重的問題。
  2. 容器的生存周期時間更短,如果實體機的開機運行時間是以年計算的,那么虛擬機則是以月計算的,而容器應該跟進程的生存周期差不多,頂多以天為單位。所以,容器里面要跑的應用應該可以被經常重啟。
  3. 當有多個 cgroup (容器)同時運行時,我們不能再以實體機或者虛擬機對資源的使用的理解來規劃整體運營方式,我們需要更細節的理解什么是 cache ,什么是 swap ,什么是共享內存,它們會被統計到哪些資源計數中?在內核并不沖突的環境,這些資源都是獨立給某一個業務使用的,在理解上即使不是很清晰,也不會造成歧義。但是在 cgroup 中,我們需要徹底理解這些細節,才能對遇到的情況進行預判,并規劃不同的處理策略。

也許我們還可以從中得到更多的理解,大家一起來想嘍?

由于字數限制,本文內容并不完整。完整內容請見 PDF 。

來自: http://v2ex.com/t/249798

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!