天天看點

redis 源碼分析(一) 記憶體管理

  redis是一個基于記憶體的key-value的資料庫,其記憶體管理是非常重要的,為了屏蔽不同平台之間的差異,以及統計記憶體占用量等,redis對記憶體配置設定函數進行了一層封裝,程式中統一使用zmalloc,zfree一系列函數,其對應的源碼在src/zmalloc.h和src/zmalloc.c兩個檔案中,源碼點。

redis封裝是為了屏蔽底層平台的差異,同時友善自己實作相關的函數,我們可以通過src/zmalloc.h 檔案中的相關宏定義來分析redis是怎麼實作底層平台差異的屏蔽的,zmalloc.h 中相關宏聲明如下:

通過上面的宏的預處理我們可以發現redis為了屏蔽不同系統(庫)的差異進行了如下預處理:

A,若系統中存在Google的TC_MALLOC庫,則使用tc_malloc一族函數代替原本的malloc一族函數。

B,若系統中存在FaceBook的JEMALLOC庫,則使用je_malloc一族函數代替原本的malloc一族函數。   

C,若目前系統是Mac系統,則使用<malloc/malloc.h>中的記憶體配置設定函數。   

D,其他情況,在每一段配置設定好的空間前頭,同時多配置設定一個定長的字段,用來記錄配置設定的空間大小。 

tc_malloc是google開源處理的一套記憶體管理庫,是用C++實作的,首頁在。TCMalloc給每個線程配置設定了一個線程局部緩存。小配置設定可以直接由線程局部緩存來滿足。需要的話,會将對象從中央資料結構移動到線程局部緩存中,同時定期的垃圾收集将用于把記憶體從線程局部緩存遷移回中央資料結構中。這篇裡對TCMalloc有個詳細的介紹。

jemalloc

也是一個記憶體創管理庫,其創始人Jason Evans也是在FreeBSD很有名的開發人員,參見。Jemalloc聚集了malloc的使用過程中所驗證的很多技術。忽略細節,從架構着眼,最出色的部分仍是arena和thread cache。

讀者一定會有疑問系統不是有了malloc 嗎,為什麼還有這樣的記憶體管理庫?? 由于經典的libc的配置設定器碎片率為較高,可以檢視的分析,關于記憶體碎片不太了解的童鞋請參考, malloc

和free 怎麼工作的參考。 關于ptmalloc,tcmalloc和jemalloc記憶體配置設定政策的一篇總結不錯的文章,請點。

下面介紹redis封裝的記憶體管理相關函數,src/zmalloc.h有相關聲明。

現在主要介紹下redis記憶體配置設定函數 void *zmalloc(size_t size),其對應的聲明形式如下:

閱讀源碼我們發現有個PREFIX_SIZE 宏,其宏定義形式如下:

結合src/zmalloc.h有相關宏聲明,我們發現,因為 tc_malloc 、je_malloc 和 Mac平台下的 malloc 函數族提供了計算已配置設定空間大小的函數(分别是tc_malloc_size, je_malloc_usable_size和malloc_size),是以就不需要單獨配置設定一段空間記錄大小了。在linux和sun平台則要記錄配置設定空間大小。對于linux,使用sizeof(size_t)定長字段記錄;對于sun 系統,使用sizeof(long

long)定長字段記錄,其對應源碼中的 PREFIX_SIZE 宏。

PREFIX_SIZE 有什麼用呢?

為了統計目前程序到底占用了多少記憶體。在 zmalloc.c 中,有一個靜态變量:

這個變量它記錄了程序目前占用的記憶體總數。每當要配置設定記憶體或是釋放記憶體的時候,都要更新這個變量(當然可以是線程安全的)。因為配置設定記憶體的時候,需要指定配置設定多少記憶體。但是釋放記憶體的時候,(對于未提供malloc_size函數的記憶體庫)通過指向要釋放記憶體的指針是不能知道釋放的空間到底有多大的。這時候,上面提到的PREFIX_SIZE就起作用了,可以通過其中記錄的内容得到空間的大小。(不過在linux系統上也有相應的函數獲得配置設定記憶體空間的大小,參見)。

通過zmalloc的源碼我們可以發現,其配置設定空間代碼為void *ptr = malloc(size+PREFIX_SIZE); 顯然其配置設定空間大小為:size+PREFIX_SIZE ,對于使用tc_malloc或je_malloc的情況或mac系統,其 PREFIX_SIZE 為0。當配置設定失敗時有相應的出錯處理 。

前面我們已經說過redis通過使用used_memory 的變量來統計目前程序到底占用了多少記憶體,是以在配置設定和釋放記憶體時我們需要緊接着更新used_memory 的相應值,對應到redis源碼中為:

上面的代碼有事宏預處理 #ifdef HAVE_MALLOC_SIZE 顯然是上面我們說過的利用的tc_malloc je_malloc Mac等提供malloc_size函數的情形,我們可以很容易得知配置設定記憶體的大小通過統一化的malloc_size函數即可。但是對于沒有提供malloc_size功能的函數,redis是怎麼處理的呢?看上面的源碼 #else下面的代碼即是其實作,其對應的記憶體結構如下:

prefix-size

memory size

配置設定的記憶體前加一個固定大小的prefis-size空間,用于記錄該段記憶體的大小,size所占據的記憶體大小是已知的,為size_t類型的長度,是以通過*((size_t*)ptr) = size; 即可對目前記憶體塊大小進行指定。每次配置設定記憶體後,傳回的實際位址指針為指向memorysize的位址( (char*)ptr+PREFIX_SIZE; ),通過該指針,可以很容易的計算出實際記憶體的頭位址,進而釋放記憶體。

redis通過update_zmalloc_stat_alloc(__n,__size) 和 update_zmalloc_stat_free(__n) 這兩個宏負責在配置設定記憶體或是釋放記憶體的時候更新used_memory變量。update_zmalloc_stat_alloc定義如下:

redis把這個更新操作寫成宏的形式主要是處于效率的考慮。

上面的代碼中 

A,if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));

  主要是考慮對齊問題,保證新增的_n 是 sizeof(long)的倍數。

B,   if (zmalloc_thread_safe) { \

        update_zmalloc_stat_add(_n); \

       }

如果程序中有多個線程存在,并保證線程安全zmalloc_thread_safe,則在更新變量的時候要加鎖。  通過宏HAVE_ATOMIC選擇相應的同步機制。

zmalloc_calloc、zmalloc_free等的實作就不仔細介紹了詳情參見。

最後講解下 zmalloc_get_rss()函數。

   這個函數用來擷取程序的RSS。神馬是RSS?全稱為Resident Set Size,指實際使用實體記憶體(包含共享庫占用的記憶體)。在linux系統中,可以通過讀取/proc/pid/stat檔案系統擷取,pid為目前程序的程序号。讀取到的不是byte數,而是記憶體頁數。通過系統調用sysconf(_SC_PAGESIZE)可以獲得目前系統的記憶體頁大小。 獲得程序的RSS後,可以計算目前資料的記憶體碎片大小,直接用rss除以used_memory。rss包含程序的所有記憶體使用,包括代碼,共享庫,堆棧等。 哪來的記憶體碎片?上面我們已經說明了通常考慮到效率,往往有記憶體對齊等方面的考慮,是以,碎片就在這裡産生了。相比傳統glibc中的malloc的記憶體使用率不是很高一般會使用别的記憶體庫系統。在redis中預設的已經不使用簡單的malloc了而是使用

jemalloc, 在源檔案src/Makefile下有這樣一段代碼:

可以知道在linux系統上預設使用jemalloc, 在redis釋出的源碼中有相關的庫 deps/jemalloc 。

總的來說 redis則完全自主配置設定記憶體,在請求到的時候實時根據内建的算法配置設定記憶體,完全自主要制記憶體的管理。簡單即是沒吧,不過功能确實強大。

參考:

http://blog.ddup.us/?p=136

繼續閱讀