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

來源:互聯網
上載者:User

標籤:style   blog   http   color   使用   strong   檔案   資料   

一,redis記憶體管理介紹

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

二,redis記憶體管理原始碼分析

redis封裝是為了屏蔽底層平台的差異,同一時候方便自己實現相關的函數,我們能夠通過src/zmalloc.h 檔案裡的相關宏定義來分析redis是怎麼實現底層平台差異的屏蔽的,zmalloc.h 中相關宏聲明例如以下:

#if defined(USE_TCMALLOC)#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))#include <google/tcmalloc.h>#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)#define HAVE_MALLOC_SIZE 1#define zmalloc_size(p) tc_malloc_size(p)#else#error "Newer version of tcmalloc required"#endif#elif defined(USE_JEMALLOC)#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))#include <jemalloc/jemalloc.h>#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)#define HAVE_MALLOC_SIZE 1#define zmalloc_size(p) je_malloc_usable_size(p)#else#error "Newer version of jemalloc required"#endif#elif defined(__APPLE__)#include <malloc/malloc.h>#define HAVE_MALLOC_SIZE 1#define zmalloc_size(p) malloc_size(p)#endif#ifndef ZMALLOC_LIB#define ZMALLOC_LIB "libc"#endif...#ifndef HAVE_MALLOC_SIZEsize_t zmalloc_size(void *ptr);#endif


通過上面的宏的預先處理我們能夠發現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有相關聲明。

void *zmalloc(size_t size);//mallocvoid *zcalloc(size_t size);//callocvoid *zrealloc(void *ptr, size_t size);/reallocvoid zfree(void *ptr);//freechar *zstrdup(const char *s);size_t zmalloc_used_memory(void);void zmalloc_enable_thread_safeness(void);void zmalloc_set_oom_handler(void (*oom_handler)(size_t));float zmalloc_get_fragmentation_ratio(void);size_t zmalloc_get_rss(void);size_t zmalloc_get_private_dirty(void);void zlibc_free(void *ptr);

如今主要介紹下redis記憶體配置函數 void *zmalloc(size_t size),其相應的聲明形式例如以下:

void *zmalloc(size_t size) {    void *ptr = malloc(size+PREFIX_SIZE);    if (!ptr) zmalloc_oom_handler(size);#ifdef HAVE_MALLOC_SIZE    update_zmalloc_stat_alloc(zmalloc_size(ptr));    return ptr;#else    *((size_t*)ptr) = size;    update_zmalloc_stat_alloc(size+PREFIX_SIZE);    return (char*)ptr+PREFIX_SIZE;#endif}

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

/* zmalloc.c */#ifdef HAVE_MALLOC_SIZE#define PREFIX_SIZE (0)       #else  #if defined(__sun)#define PREFIX_SIZE (sizeof(long long))#else                         #define PREFIX_SIZE (sizeof(size_t))#endif#endif

結合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 中,有一個靜態變數:

static size_t used_memory = 0;
這個變數它記錄了進程當前佔用的記憶體總數。每當要分配記憶體或是釋放記憶體的時候,都要更新這個變數(當然能夠是安全執行緒的)。由於分配記憶體的時候,須要指定分配多少記憶體。可是釋放記憶體的時候,(對於未提供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    update_zmalloc_stat_alloc(zmalloc_size(ptr));    return ptr;#else    *((size_t*)ptr) = size;    update_zmalloc_stat_alloc(size+PREFIX_SIZE);    return (char*)ptr+PREFIX_SIZE;#endif
上面的代碼有事宏預先處理 #ifdef HAVE_MALLOC_SIZE 顯然是上面我們說過的利用的tc_malloc je_malloc Mac等提供malloc_size函數的情形,我們能夠非常easy得知分配記憶體的大小通過統一化的malloc_size函數就可以。可是對於沒有提供malloc_size功能的函數,redis是怎麼處理的呢?看上面的原始碼 #else以下的代碼即是事實上現,其相應的記憶體結構例如以下:
prefix-size memory size
分配的記憶體前加一個固定大小的prefis-size空間,用於記錄該段記憶體的大小,size所佔領的記憶體大小是已知的,為size_t類型的長度,因此通過*((size_t*)ptr) = size; 就可以對當前記憶體塊大小進行指定。每次分配記憶體後,返回的實際地址指標為指向memorysize的地址( (char*)ptr+PREFIX_SIZE; ),通過該指標,能夠非常easy的計算出實際記憶體的頭地址,從而釋放記憶體。

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

#define update_zmalloc_stat_alloc(__n) do {     size_t _n = (__n);     if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \     if (zmalloc_thread_safe) {         update_zmalloc_stat_add(_n);     } else {         used_memory += _n;     } } while(0)
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下有這樣一段代碼:

ifeq ($(uname_S),Linux)MALLOC=jemalloc
能夠知道在linux系統上預設使用jemalloc, 在redis公布的原始碼中有相關的庫 deps/jemalloc 。


總的來說 redis則全然自主分配記憶體,在請求到的時候即時依據內建的演算法分配記憶體,全然自主控制記憶體的管理。簡單即是沒吧,只是功能確實強大。


參考:

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.