我的內(nèi)存去哪了?
本文來自博客園,作者:T-BARBARIANS,博文嚴(yán)禁轉(zhuǎn)載,轉(zhuǎn)載必究!
一、前言
近幾年開發(fā)了一些大型的應(yīng)用程序,在程序性能調(diào)優(yōu)或者解決一些疑難雜癥問題的過程中,遇到最多的還是與內(nèi)存相關(guān)的一些問題。例如glibc內(nèi)存分配器ptmalloc,google的內(nèi)存分配器tcmalloc都存在“內(nèi)存泄漏”,即內(nèi)存不歸還操作系統(tǒng)的問題;ptmalloc內(nèi)存分配性能低下的問題;隨著系統(tǒng)長時(shí)間運(yùn)行,buffer/cache被某些應(yīng)用大量使用,幾乎完整占用系統(tǒng)內(nèi)存,導(dǎo)致其他應(yīng)用程序內(nèi)存申請(qǐng)失敗等等問題。
之所以內(nèi)存相關(guān)的問題層出不窮,關(guān)鍵還是它的地位太重要了。這次還是與內(nèi)存相關(guān),分享的是追蹤buffer/cache占用的內(nèi)存到底被誰(哪些應(yīng)用程序)偷吃了!
有關(guān)buffer/cache的文章,大多提及的是如何釋放并歸還到系統(tǒng)的方法,但是分析buffer/cache內(nèi)存消耗背后原因的相關(guān)文章卻鳳毛麟角。buffer/cache為什么會(huì)增長,它到底被哪些程序使用了,我相信這也是很多同行的疑惑,因此想通過本篇文章分享一些buffer/cache內(nèi)存消耗問題的跟蹤方法,為類似問題的優(yōu)化和解決提供一些參考。
二、問題描述
如下圖1和圖2所示,buffer/cache已經(jīng)占用了46GB的內(nèi)存,達(dá)到了整個(gè)系統(tǒng)內(nèi)存的37%,這個(gè)占比已經(jīng)非常高了。buffer/cache長期占用不釋放,同時(shí)供系統(tǒng)上其它進(jìn)程使用的可用內(nèi)存幾乎快沒了。

圖1

圖2
長此以往,會(huì)出現(xiàn)什么問題呢?最直接的問題就是其他進(jìn)程沒法玩了,比如大一點(diǎn)的內(nèi)存塊就無法申請(qǐng)。之前分享過一次相關(guān)問題的定位,參見鏈接:http://www.rzrgm.cn/t-bar/p/16626951.html,我在這篇文章里也詳細(xì)介紹了buffer/cache的釋放方法,解決了當(dāng)時(shí)的燃眉之急。
為啥最近又開始與buffer/cache糾纏上了呢?
“echo 1 > /proc/sys/vm/drop_caches”釋放的是所有cache,這些cache是當(dāng)前系統(tǒng)上所有程序在運(yùn)行過程中加載到內(nèi)存的一些文件信息,這些信息被當(dāng)做緩存用,好處是CPU下次讀取某個(gè)文件時(shí)就會(huì)比第一次從磁盤讀取快多了。drop_caches執(zhí)行時(shí)會(huì)清空所有cache,這樣會(huì)帶來一個(gè)問題:當(dāng)某些程序需要讀取之前加載到cache的信息時(shí)就需要重新從磁盤讀取,這就會(huì)產(chǎn)生IO等待,或者IO競爭,從而拖累程序性能。在某些平臺(tái)上,我們已經(jīng)發(fā)現(xiàn)有高性能程序因?yàn)閏ache的粗暴清空產(chǎn)生了性能抖動(dòng)。因此,我們就沒法像以前一樣回避buffer/cache到底被誰使用的問題,并且直接粗暴釋放的策略在某些平臺(tái)上也就失效了。
根據(jù)上面的描述,我們當(dāng)前面臨的問題就是:究竟是誰占用了buffer/cache,以及弄清是誰占用后,是否可以規(guī)避它對(duì)buffer/cache的大量使用。面對(duì)這個(gè)問題,老板最近又上火了。

三、buffer/cache使用跟蹤
開始介紹一下調(diào)查buffer/cache占用的跟蹤思路吧。
1、hcache
網(wǎng)上有一些帖子分享了hcache可以查看哪些文件使用了cache,那hache真的可以幫助我們對(duì)buffer/cache進(jìn)行全面調(diào)查嗎?我們一起來看看。
根據(jù)前面的問題描述,當(dāng)前buffer/cache已經(jīng)占用了46GB的內(nèi)存。先使用hcache查看一下top100的cache占用,如下圖所示(截取了Cached靠前的一部分)。

圖3
top100,即使top200的Size統(tǒng)計(jì)之和,也只有幾個(gè)GB,離46GB相差甚遠(yuǎn),結(jié)果說明hcache遺漏了很多cache的使用統(tǒng)計(jì)。
hcache還有一個(gè)能力,查看某個(gè)進(jìn)程當(dāng)前使用的cache。我們看看clickhouse的cache使用,結(jié)果如下圖所示。

圖4
正在運(yùn)行的clickhouse,居然只能看到程序可執(zhí)行文件本身當(dāng)前的cache占用,程序運(yùn)行過程中已打開的cache文件卻沒統(tǒng)計(jì)。不過這里有個(gè)小收獲:程序加載進(jìn)內(nèi)存后,程序的可執(zhí)行文件,依賴的庫文件使用的內(nèi)存都是在buffer/cache里。

圖5
從上面的結(jié)果發(fā)現(xiàn)hcahce有很多缺點(diǎn),只能粗略的看到一些可執(zhí)行程序文件,或者一些庫文件使用的cache大小,沒有統(tǒng)計(jì)各程序運(yùn)行態(tài)的cache使用,因此對(duì)cache占用問題的排查作用非常有限。
2、top + lsof + fincore
找了很多資料,除了hcache確實(shí)沒有其他方法可以統(tǒng)計(jì)當(dāng)前運(yùn)行程序消耗的cache大小了,但是hcache本身不可靠。沒有直接的辦法,那就只有另辟蹊徑了,這也是buffer/cache分布情況不便跟蹤調(diào)查的原因。
該從哪里入手呢?當(dāng)然是top命令給方向,哪些程序cpu使用率高,且使用了一定的內(nèi)存,那就查它。因?yàn)橹挥兴鼈儾庞锌赡茉诓粩嗟氖褂胏ache,調(diào)查大方向有了。

圖6
下一步呢?buffer/cache的使用肯定跟文件相關(guān)啊,還是那句話:linux一切皆文件。那有沒有可以實(shí)時(shí)查看某個(gè)進(jìn)程當(dāng)前已打開的文件方法?lsof命令可以!我們用lsof查一下clickhouse,某時(shí)刻,clickhouse打開的文件如下圖7圖8所示,篇幅太長,圖7只截取了前面部分。

圖7
圖8只截取了類型TYPE=REG(REG表示文件類型為普通,還有DIR為目錄等等等),即截取了clickhouse當(dāng)前打開,且正在使用的一部分類型為普通的文件。

圖8
不斷的執(zhí)行:lsof -p $(pidof clickhouse-server),發(fā)現(xiàn)每次查看到的文件名都不一樣。好了,這說明clickhouse會(huì)在運(yùn)行過程中不斷的大量打開,讀寫和關(guān)閉文件。嫌疑很重了。
下一步呢?有沒有辦法可以實(shí)時(shí)查看當(dāng)前這些文件是不是使用了cache,以及各自使用cache的大???還真有,fincore可以查看某個(gè)文件使用的cache大小,鏈接:https://github.com/david415/linux-ftools。輪子就是齊全啊,要啥有啥。
命令行:lsof -p $(pidof clickhouse-server) | grep 'REG' | awk '{print $9}' | xargs ./fincore --pages=false --summarize --only-cached *
截圖較大,點(diǎn)開看會(huì)清晰一點(diǎn)。

圖9
fincore統(tǒng)計(jì)了命令行執(zhí)行時(shí),clickhouse當(dāng)前打開的文件使用的cache之和為1.2GB左右。到這里,當(dāng)前的探索結(jié)果與前文提到的問題:究竟是誰占用了buffer/cache,越來越接近了。
通過top + lsof,發(fā)現(xiàn)了一個(gè)非常重要的線索,就是clickhouse在目錄/opt/runtime/esch/ch/store下頻繁的打開了很多文件,那這個(gè)目錄下面到底都是一些什么文件?有沒有都使用了cache呢?clickhouse是不是cache的消耗大戶呢?解決這些疑惑就產(chǎn)生了另外一個(gè)需求,需要一種可以統(tǒng)計(jì)指定目錄的cache大小的工具。這次fincore也不行了,fincore有一個(gè)致命弱點(diǎn),即只能獲得某個(gè)指定文件的cache占用大小,不能獲取指定目錄使用的cache大小,更別指望統(tǒng)計(jì)嵌套目錄的cache大小。因此,是時(shí)候該請(qǐng)vmtouch出場(chǎng)了,鏈接:https://github.com/hoytech/vmtouch,還是這句話:輪子就是齊全啊,要啥有啥。
3、vmtouch
vmtouch可以統(tǒng)計(jì)指定目錄的cache占用大小,即使是嵌套目錄。
迫不及待的直接奔主題,看看clickhouse目錄/opt/runtime/esch/ch/store下是什么,以及使用了多少cache。截取了該目錄下的部分文件,內(nèi)容如下圖所示。

圖10
直接統(tǒng)計(jì)一下/opt/runtime/esch/ch/store目錄占用的cache規(guī)模吧,結(jié)果如下圖所示。

圖11
shit,居然吃了我42GB的內(nèi)存??!地主家的余糧也不多啊---老板哭著說。
激動(dòng)之余,我還要確認(rèn)一下42GB cache的使用者是不是它!如何證明呢,還是使用“echo 1 > /proc/sys/vm/drop_caches”,看看釋放完畢之后,free可用內(nèi)存的大小是否會(huì)增長42GB左右。
執(zhí)行前的內(nèi)存分布情況:

圖12

圖13
執(zhí)行后的內(nèi)存分布情況:

圖14

圖15
執(zhí)行cache釋放后,free從2GB變?yōu)榱?5GB,擴(kuò)大了43GB;buffer/cache從46GB變?yōu)榱?GB,減小了43GB。從cache釋放了clickhouse的42GB+1GB其它程序占用的cache,說明我們環(huán)境上,clickhouse就是cache的消耗大戶!老板沸不沸騰我不知道,反正我是沸騰了。
四、clickhouse cache耗費(fèi)
為什么clickhouse對(duì)buffer/cache的消耗如此巨大?在好奇心的驅(qū)使下,又開始了新的調(diào)查。此時(shí)此景想到了一句歌詞:一波還未平息,一波又來侵襲,茫茫人海,狂風(fēng)暴雨。。。
1、clickhouse cache耗費(fèi)原因
從哪開始調(diào)查呢?想起了lsof命令的執(zhí)行結(jié)果,如前文圖9所示的重要線索,clickhouse有大量時(shí)間都在打開目錄:/opt/runtime/esch/ch/store/032/03216cf6-357f-477f-bc9b-5eedb07a5d07,判斷該目錄下面肯定有大量消耗cache的文件。直接進(jìn)入該目錄,繼續(xù)使用vmtouch統(tǒng)計(jì),不出所料,結(jié)果如下圖所示,032目錄就吃了24GB內(nèi)存,心好痛啊。

圖16
clickhouse的什么機(jī)制會(huì)如此瘋狂的消耗cache呢?我們?cè)倏纯茨夸浵掠行┦裁搭愋偷奈募?,截取了部分文件?/span>

圖17
發(fā)現(xiàn)目錄下主要是很多以日期編號(hào)開頭的目錄文件,有純數(shù)字組成的,也有帶有merge字符的目錄。隨便打開一個(gè)5月17日當(dāng)天的目錄文件:20230517_563264_565136_5

圖18
20230517_563264_565136_5 目錄就占用了2GB cache,驚不驚喜意不意外?而且上面的所有文件,都完全加載到了cache中,比如在磁盤中占用743MB的文件cuid.bin,同樣在cache中占用了742MB。

圖19
查閱clickhouse資料后才發(fā)現(xiàn),數(shù)字編號(hào)的目錄都是clickhouse的很多分區(qū)part,clickhouse服務(wù)會(huì)根據(jù)相關(guān)策略自動(dòng)的在后臺(tái)合并這些分區(qū)。想想,如果在每一次合并分區(qū)時(shí),才將上一次的某兩個(gè)分區(qū)從磁盤進(jìn)行IO讀取,那將帶來多大的性能開銷。因此,clickhouse的開發(fā)者會(huì)將上一次的分區(qū)合并結(jié)果保存在cache里,下一次該分區(qū)與其它分區(qū)再次進(jìn)行合并時(shí),直接從內(nèi)存里讀取數(shù)據(jù)就好了。這就是為什么clickhouse消耗如此巨大cache的原因。當(dāng)然,clickhouse對(duì)cache的消耗與您當(dāng)前環(huán)境的數(shù)據(jù)存儲(chǔ)規(guī)模呈正相關(guān)。
再來看一個(gè)問題,那昨天的所有分區(qū),加載的數(shù)據(jù)還會(huì)保留在cache里嗎?
我找了一個(gè)昨天的分區(qū),可以發(fā)現(xiàn)昨天的分區(qū)目錄里的文件是不再占用cache的。上一天的分區(qū),clickhouse認(rèn)為是合并完成的分區(qū),已經(jīng)不需要再進(jìn)行合并了,自然就clear了cache的占用,開發(fā)者也是想到了的。

圖20
2、clickhouse cache耗費(fèi)調(diào)整
可是當(dāng)天分區(qū)消耗的cache,以及merge過程中使用的cache就讓我沒法玩了,尤其是在clickhouse服務(wù)并未獨(dú)立部署的場(chǎng)景。那clickhouse自身可以支持改變這一機(jī)制嗎?帶著疑問又開始了一探究竟,完全沒法停下來。
后來在clickhouse社區(qū)找到了一個(gè)可以節(jié)省cache使用的相關(guān)問題。鏈接:https://github.com/ClickHouse/ClickHouse/issues/1566,有配置min_part_size_for_direct_merge,意思是超過min_part_size時(shí),啟用direct_io。也就是此時(shí)clickhouse會(huì)通過direct_io的方式讀寫merge的源文件和目的文件,而不是使用cache緩存,通過這種方式減少cache的使用。

圖21
我們的clickhouse版本比較高,看社區(qū)記錄,clickhouse官方將之前的min_part_size_for_direct_merge改成為min_merge_bytes_to_use_direct_io,Minimal amount of bytes to enable O_DIRECT in merge (0 - disabled)。默認(rèn)超過10GB時(shí)會(huì)使用direct_io的方式進(jìn)行merge。

圖22
那我將min_merge_bytes_to_use_direct_io設(shè)置足夠小,甚至是1byte,是不是就可以完全避免對(duì)cache的使用了?答案是否定的,原因是:min_merge_bytes_to_use_direct_io只是讀寫表數(shù)據(jù)時(shí)使用了direct_io,替換了常用的buffer_io。也就是說只是在數(shù)據(jù)傳輸過程不使用cache,節(jié)省的是這個(gè)環(huán)節(jié)的cache內(nèi)存消耗。merge完成后,先通過direct_io將數(shù)據(jù)寫入到磁盤,同時(shí)會(huì)繼續(xù)使用cache緩存merge完成后的數(shù)據(jù),方便為下一次與其它分區(qū)進(jìn)行快速merge做準(zhǔn)備。因?yàn)槊看蝝erge都是merge舊數(shù)據(jù)與新數(shù)據(jù),因此新合成的分區(qū)所使用的cache只會(huì)比merge前的更大。direct_io與buffer_io的區(qū)別如下圖所示。

圖23
需要注意的是,設(shè)置min_merge_bytes_to_use_direct_io還有一個(gè)副作用,當(dāng)發(fā)生merge行為時(shí)會(huì)導(dǎo)致磁盤IO急劇拉高。因?yàn)閐irect_io是對(duì)磁盤進(jìn)行直接操作,這種IO方式與buffer_io(使用了page cache做緩沖層)相比,帶給磁盤的沖擊更大。但是如果鈔票比較多,可以做磁盤raid,或者增加了SSD,磁盤能夠扛住direct_io沖擊的同時(shí),還能支持前端的絲滑查詢,那就另當(dāng)別論啦!

圖24
另外還設(shè)置過相關(guān)參數(shù):max_bytes_to_merge_at_max_space_in_pool,用處不大,就不繼續(xù)介紹了,讀者可以自行驗(yàn)證。只能說clickhouse當(dāng)前沒有提供待merge分區(qū)文件所占用cache的清理機(jī)制。
五、cache清理
一頓操作猛如虎,定睛一看原地杵。clickhouse自身無法限制cahce消耗;“echo 1 > /proc/sys/vm/drop_caches”又太粗暴,會(huì)清理掉其它進(jìn)程加載到cache中的內(nèi)容。只想搞掉clickhouse占用的大量cache,該怎么辦?
有時(shí)候你不得不相信車到山前必有路,船到橋頭自然直。再次請(qǐng)出vmtouch!
vmtouch的help中有一個(gè)“-e”的選項(xiàng),即“evict pages from memory”,顧名思義將page cache從內(nèi)存中驅(qū)逐出去。既然vmtouch可以統(tǒng)計(jì)指定文件或目錄占用的cache,那自然就可以實(shí)現(xiàn)對(duì)指定文件或目錄的cahce清理!

圖25
先來看看執(zhí)行效果。執(zhí)行前有內(nèi)存分布,以及某個(gè)目錄下cache的使用情況:

圖26

圖27
執(zhí)行:vmtouch -e ./* 后,內(nèi)存分布如下圖28所示。驚喜嗎?剛好減少了某個(gè)目錄下占用的30GB cache,同時(shí)free內(nèi)存增加了30GB,實(shí)現(xiàn)了對(duì)指定目錄cache消耗的定點(diǎn)清理!

圖28
很好奇vmtouch實(shí)現(xiàn)指定目錄內(nèi)存清理的方法,去看了看源碼,簡單貼兩張圖就大致明白了。
1、傳遞指定path;
2、如果該path下無目錄,則通過vmtouch_file函數(shù)執(zhí)行cache的釋放操作;如果該目錄下存在其他文件(包括子目錄),則遍歷該目錄下的所有文件,并通過vmtouch_crawl實(shí)現(xiàn)遞歸調(diào)用,回到第1步。

圖29
那vmtouch_file函數(shù)如何實(shí)現(xiàn)cache的釋放操作呢?答案就是通過系統(tǒng)調(diào)用:posix_fadvise,并使用POSIX_FADV_DONTNEED宏。定義如下:
1 int posix_fadvise(int fd, off_t offset, off_t len, int advice); 2 3 advice: 4 POSIX_FADV_DONTNEED,指定的數(shù)據(jù)在未來的一段時(shí)間內(nèi)不會(huì)被訪問,丟棄page cache中的數(shù)據(jù)
當(dāng)advice為POSIX_FADV_DONTNEED時(shí),內(nèi)核會(huì)先將臟頁刷盤,再清除相關(guān)page cache,從而達(dá)到cache釋放的目的!

圖30
但是查閱資料,發(fā)現(xiàn) posix_fadvise 實(shí)現(xiàn)臟頁刷盤使用的參數(shù)是:WB_SYNC_NONE,即posix_fadvise 不會(huì)同步等待其它進(jìn)程發(fā)起的臟頁刷盤行為,這可能會(huì)帶來一個(gè)問題:不能完全釋放指定路徑下的cache,因此可以在執(zhí)行cache釋放前先使用 fsync,將所有的臟頁進(jìn)行回寫,完成后再調(diào)用 posix_fadvise,實(shí)現(xiàn)指定path下所有cache的釋放。如果不關(guān)心臟頁使用的cache是否可以被全被釋放,那直接使用posix_fadvise 就好了。
寫到這里,可能有些讀者會(huì)產(chǎn)生一個(gè)問題,清理cache就真的很好嗎?答案是肯定的。為什么這么說呢,我想可以概括為以下兩點(diǎn):
一是有些服務(wù)使用完cache后,這部分cache再也不會(huì)被訪問,這一類cache消耗當(dāng)然是需要干掉的;
二是類似我們環(huán)境clickhouse這一類應(yīng)用,它們會(huì)再次使用之前使用過的cache。這種情形確實(shí)不好辦,但是如果不辦它,最后就是大家都別玩了,魚和熊掌不可兼得。因此,對(duì)這一類應(yīng)用,定期清理cache是必要的,通過一個(gè)折中的辦法讓大家共存下來。對(duì)依賴cache這一類應(yīng)用,cache清理后就只有辛苦下磁盤了(人在家中坐,鍋從天上來)。
六、結(jié)語
摸索cache的過程曲折且漫長,還好最后找到了一個(gè)大家都可以接受的辦法。文章篇幅比較長,簡單總結(jié)一下全文吧,概括起來可歸納為以下四點(diǎn):
1、驗(yàn)證了 hcache 無法統(tǒng)計(jì)cache的全面消耗情況,推薦了一種通過 lsof+fincore 探測(cè)進(jìn)程中活躍目錄以及文件的方法,并以此作為cache消耗的關(guān)鍵調(diào)查線索。這種方法適用于多種場(chǎng)景下的cache消耗調(diào)查,不僅僅是clickhosue。
2、通過vmtouch可以統(tǒng)計(jì)出某個(gè)文件、目錄,甚至是嵌套目錄下各文件真正使用cache的大小,從而明確cache消耗的分布情況。
3、分析了clickhouse大量消耗cache的原因,探索了clickhouse自身是否具備減少cache使用的能力和機(jī)制。
4、提供了清理各文件,或者各指定目錄所占用cache的通用方法,屬于定點(diǎn)清除的騷操作。
技術(shù)是不斷實(shí)踐積累的,在此分享出來與大家一起共勉!
如果文章對(duì)你有些許幫助,還請(qǐng)各位技術(shù)愛好者登錄點(diǎn)贊呀,非常感謝!
本文來自博客園,作者:T-BARBARIANS,博文嚴(yán)禁轉(zhuǎn)載,轉(zhuǎn)載必究!

浙公網(wǎng)安備 33010602011771號(hào)