排查內存使用(泄露、耗盡)問題的一個的技巧是,區分「批發商」和「零售商」這兩類不一樣的內存管理機制。這裏的「批發商」,指的是按頁面管理並分配內存的機制。而「零售商」,則是指從「批發商」那裏批量獲取資源,並以字節爲單位,管理和分配內存的機制。服務器
「零售商」和「批發商」的區分很重要。這是由於經過「零售商」分配出去的內存資源,在「批發商」那裏或多或少都有統計。可是從「批發商」那邊分配出去的內存資源,「零售商」幾乎一無所知。凡是一些詭異的,「個人內存去哪裏了」的問題,每每都跟這個有點有關係。數據結構
「批發商」「零售商」的例子不少,好比Windows上的一對接口VirtualAlloc和HeapAlloc。而在Linux內核中,buddy system毋庸置疑是最大內存的「批發商」,而slab則是最經常使用的「零售商」。今天這篇文章,我跟你們分享一個和「零售商」slab有關的真實案例。工具
今天的案例,咱們從雲監控提及。雲監控做爲一個監控工具,給客戶提供了很是豐富的功能。阿里雲的客戶可使用雲監控來隨時瞭解雲服務的狀態,並且在雲服務出現異常的時候,客戶能夠及時地收到來自雲監控的報警信息。阿里雲
作爲阿里雲技術支持的同窗,能夠說咱們對雲監控有着一種很是「特殊」的感情。這是由於,不少時候客戶提給咱們的問題,會以一個雲監控的截圖開始,看到雲監控的截圖,那基本就等於咱們有新的問題須要處理了。spa
以上的截圖是雲監控經過釘釘消息發送給客戶的報警信息。從這個截圖裏來看,客戶的一臺ECS實例內存使用率超過了90%,並且持續了1分鐘時間。這對ECS服務器來講,是至關不健康的狀態。操作系統
處理內存使用問題,有一些基本套路。首先,咱們可使用free或top命令看一下系統整體內存使用狀況,如物理內存大小,有多少剩餘內存,有多少被cache(這裏的cache和slab機制的cache是兩回事)和buff用掉了;其次,咱們可使用ps命令看看每一個進程內存的使用狀況;若是到這一步尚未定位到問題,進一步,咱們能夠經過查看/proc/meminfo文件,在更細的粒度上了解內存資源的分佈與分配狀況。按照這個套路打下來,通常咱們都會獲得一個初步的排查結果。以今天這個問題爲例,系統32G內存幾乎被用光了,可是cache和buff並不大,進程使用的內存也很少。最終在meminfo文件中,我發現有大量內存被slab用掉了。3d
Slab應該算是Linux內存管理機制中,最著名的「零售商」機制。寫slab的文章很是多,我這裏只作簡單的介紹。對象
作爲「零售商」,slab確定須要解決兩個問題,一個是怎麼樣從「批發商」buddy system那裏「批發」內存,另一個是怎麼樣管理並把這些內存「散賣」出去。爲了回答這兩個問題,咱們首先理解一下slab機制的數據結構。Slab機制包括三個層次的數據結構:cache,slab和object。以下圖,一個slab通常是一個4K大小的內存頁面。當咱們把slab頁面,按照固定大小切分紅可以被「零售」的小塊的時候,咱們就獲得了不少的object。當把包含相同大小object的slab用「箭頭」串起來的時候,就造成了cache。這裏slab和object是比較容易理解的:由於把4K的頁面分配給幾個字節的對象使用太浪費內存了,因此就須要切開來「散賣」。而cache的存在乎義實際上是比較隱晦的。Cache存在的意義,實際上是爲了動態的管理slab,讓slab能夠動態的增長和減小。在object需求量大的時候,能夠從「批發商」那裏拿到不少的slab放進cache裏,而在用完以後,又能夠動態地把slab退回去。blog
基於cache, slab和object數據結構,slab機制要解決上邊那兩個問題就變得很容易了:一方面,slab機制從「批發商」buddy system那裏,以slab(頁面)爲單位獲取內存;另一方面,slab機制以object爲基本管理單元,把內存資源「零售」給系統中其餘模塊。接口
Slabtop是一個能夠用來查看slab內存使用狀況的工具。下邊這個截圖來自客戶問題現場。咱們能夠根據cache size這一列,快速地定位到問題。很明顯cache size最大的一行對應的對象是dentry。
注意:爲了解釋簡單,這裏我假設slab是一個頁面,也就是4K大小。這個假設意味着,全部被管理的對象都是小於4K的。但在實際狀況中,爲了提供大於4K的內存塊,一個slab其實能夠是多個連續的物理頁面。
目錄項dentry算是一個很是小的內核數據結構。從上邊的截圖咱們能夠看到,dentry其中只有0.19K。雖然dentry很小,可是這個結構是內核中最常使用的數據結構。以下圖,當進程打開文件的時候,通常都會有對應的dentry被分配出來。在Linux這樣的「一切皆文件」的操做系統裏,dentry結構被使用頻率是不言自明的。
由於已經定位到了內核數據結構dentry,因此我選擇抓一個dump來慢慢分析這個問題。抓了dump以後讓客戶重啓機器釋放內存,以避免致使更嚴重的後果,影響業務。
使用core dump排查問題有兩個優點:一是在生產環境中,客戶有時候真的很難給咱們機會去使用一些trace和工具一步一步的排查問題,而抓dump對業務的影響較小,抓完以後能夠慢慢分析。二是core dump裏包含了系統中全部正常、不正常的狀態。數據量大且完整,這不是通常的數據收集方法能夠比的。下邊我演示一遍怎麼用core dump來排查slab泄露問題。
使用sys命令,咱們能夠快速看一下客戶這臺機器的基本信息。如機器名,內核版本,內存大小等。
內存概要
由於這個問題是內存使用問題,咱們能夠用kmem -i命令在dump裏看一下內存的使用狀況。咱們能夠看到,這個系統有32G內存,其中slab用掉27G。
Cache
接下來咱們看一下slab的使用狀況。命令kmem -s能夠幫咱們列出內核中全部的slab cache。以下圖,每一行對應一個cache,每一個cache負責管理一個對象,第二列是對象的名字。
Slab
既然已經發現了問題是由dentry對象引發的,那麼,咱們能夠到slab結構內部去查看泄露的的dentry。命令kmem -S dentry能夠幫咱們列出包含dentry的全部slab。能夠對照「零售商slab」一節第一個插圖來看下邊的截圖。前兩行給出了cache的一些基本信息,如地址,object名字,object大小等。從第三行開始,這個命令分塊輸出cache中全部的slab結構。每一個slab裏包含一列小對象,這些對象都在一個頁面內部,從他們連續的地址能夠看出這點。實際上這個系統分配了7百萬slab頁面給dentry,下圖只截了前兩個。
這個時候問題來了。這個cache裏有7百萬slab,有1億4千萬dentry項,咱們怎麼去分析這些項呢?其實有一個很簡單的技巧,就是隨機挑選的一些dentry,而後看看這些dentry項裏,有沒有什麼共同的特徵,好比相同的字符串,或者共同引用的地址等。對這個問題來講,我是很幸運的。我發如今隨機挑選的dentry結構裏,d_iname這個字符串都包含一個子串「dOeSnotExist_.db」。
使用dOeSnotExist_.db這個子串,咱們能夠在公網上垂手可得地定位到下邊這個問題:這是nss-softokn軟件包的一個Bug。
技術支持工做的一個創新點,也是這個工種的一大樂趣,是能夠利用各類組合技,利用不一樣的方法,巧妙地組合並找出問題的答案。以今天這個問題爲例,咱們開始使用了一些基礎的排查工具對問題進行了初步的定位;進而咱們使用了core dump這種看起來不那麼易用的工具,從內存中挖出了一個字符串做爲線索;最後結合公網上的信息,咱們拼出了這個問題的答案。
本文做者:聲東
閱讀原文本文爲雲棲社區原創內容,未經容許不得轉載。