一台伺服器警示了,記憶體佔用過高,奇怪的是叢集裡其它的伺服器都沒問題。不過從以往的經驗來看:每一個匪夷所思的問題背後,都隱藏著一個啼笑皆非的答案。
首先通過「free -m」確認一下記憶體情況,發現用掉了 6893M,還剩 976M:
free
然後通過「top」查看一下哪些進程佔用記憶體多,通過「shift + m」按記憶體排序:
top
雖然通過 free 命令我們能確認系統可用記憶體不足,但是通過 top 命令我們卻沒有發現有記憶體佔用大戶的進程,那麼記憶體到底都去哪裡了呢?
開頭我們提到過,叢集裡只有一台伺服器有問題,其它伺服器皆正常,於是我們比較了一下問題伺服器和正常伺服器的進程列表,結果發現問題伺服器多了幾個進程:
/usr/local/bin/php artisan queue:listen
/usr/local/bin/php artisan queue:work
經過確認,它們是 Laravel 隊列,雖然直覺告訴我們問題與其有關聯,但是進程本身並沒有佔用多少記憶體,在不能立刻確診原因的情況下,我們用排除法把隊列換到另外一台正常的伺服器上看看會不會重現問題,過了一會,果然再次出現同樣問題。
既然 free,top 之類的命令不能確認記憶體的去向,那麼我們不妨看看「meminfo」:
meminfo
如所示,大量記憶體被 Slab 消耗了,更進一步講是被 SReclaimable 消耗了,也就是說記憶體被一些可回收的 Slab 消耗了,更進一步的資訊可以通過「slabtop」擷取:
slabtop
大量記憶體被 dentry 消耗了,如果你也跟我一樣,搞不清楚它意味著什麼,搜尋吧,能翻牆用 Google,不能翻牆用 AOL,反正別用百度,我找到如下介紹:
簡而言之,記憶體 dentry 裡緩衝了最近訪問過的檔案資訊,如果頻繁的操作大量檔案,那麼 dentry 就會不斷的增加,於是問題就變為確認 Laravel 隊列有沒有類似問題。
前面提到過,Laravel 隊列有一個 listen 進程,還有一個 work 進程,從名字我們就能判斷出來,前者是主進程,後者是子進程,子進程是幹活的進程,可是當我直接 strace 跟蹤子進程的時候,卻提示我子進程不存在,進一步調試發現,原來子進程會不斷重啟!
既然我們不好直接跟蹤子進程,那麼不妨從父進程入手跟蹤子進程的檔案操作:
shell> strace -f -e trace=open,close,stat,unlink -p $( ps aux | grep "[q]ueue:listen" | awk '{print $2}')
可惜 Laravel 本身號稱是巨匠架構,依賴一坨一坨的檔案,所以跟蹤結果裡充斥著大量架構檔案本身正常的 open,close,stat 操作,改為只跟蹤 unlik 操作試試:
shell> strace -f -e trace=unlink -p $( ps aux | grep "[q]ueue:listen" | awk '{print $2}')
發現 Laravel 隊列頻繁的執行刪除檔案操作,每重啟一次子進程就執行一次刪除:
unlink(“/tmp/.ZendSem.aXaa3Z”)
unlink(“/tmp/.ZendSem.teQG0Y”)
unlink(“/tmp/.ZendSem.Bn3ien”)
unlink(“/tmp/.ZendSem.V4s8RX”)
unlink(“/tmp/.ZendSem.PnNuTN”)
因為臨時檔案的名字各不相同,所以消耗了大量的 dentry 緩衝。查閱 Laravel 隊列的 文檔 ,發現 Laravel 隊列實際上也提供了不重啟的守護進程模式,這樣就不會頻繁建立大量臨時檔案,進而也就不會消耗大量的 dentry 緩衝,推薦使用。
如果頻繁建立大量臨時檔案的情況無法避免,那麼按照 Linux 文檔 的描述,我們可以通過設定 drop_caches 為 2 來刪除可回收的 slab(包括 dentries 和 inodes),較粗野:
shell> echo 2 > /proc/sys/vm/drop_caches
此外還可以通過設定 vfs_cache_pressure 大於 100 來增加回收機率,較溫柔:
shell> echo 1000 > /proc/sys/vm/vfs_cache_pressure
從測試結果看,vfs_cache_pressure 的作用有限,當然也可能是我姿勢不對。還有一些資料說 min_free_kbytes 也可以解決此類問題,不過鑒於這個參數有一定危險性,我建議大家別亂改,這裡就不多說了,有興趣的可以自行查閱相關資料。