一,redis內(nèi)存管理介紹
? redis是一個(gè)基于內(nèi)存的key-value的數(shù)據(jù)庫(kù),其內(nèi)存管理是很重要的,為了屏蔽不同平臺(tái)之間的差異,以及統(tǒng)計(jì)內(nèi)存占用量等,redis對(duì)內(nèi)存分配函數(shù)進(jìn)行了一層封裝,程序中統(tǒng)一使用zmalloc,zfree一系列函數(shù),其相應(yīng)的源代碼在 src/zmalloc.h 和 src/zmalloc.c 兩個(gè)文件里,源代碼點(diǎn) 這里 。
二,redis內(nèi)存管理源代碼分析
redis封裝是為了屏蔽底層平臺(tái)的差異,同一時(shí)候方便自己實(shí)現(xiàn)相關(guān)的函數(shù),我們能夠通過 src/zmalloc.h 文件里的相關(guān)宏定義來分析redis是怎么實(shí)現(xiàn)底層平臺(tái)差異的屏蔽的, zmalloc.h 中相關(guān)宏聲明例如以下 :
#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_SIZE size_t zmalloc_size(void *ptr); #endif
通過上面的宏的預(yù)處理我們能夠發(fā)現(xiàn)redis為了屏蔽不同系統(tǒng)(庫(kù))的差異進(jìn)行了例如以下預(yù)處理:
A ,若系統(tǒng)中存在Google的TC_MALLOC庫(kù),則使用tc_malloc一族函數(shù)取代原本的malloc一族函數(shù)。
B ,若系統(tǒng)中存在FaceBook的JEMALLOC庫(kù),則使用je_malloc一族函數(shù)取代原本的malloc一族函數(shù)。 ??
C ,若當(dāng)前系統(tǒng)是Mac系統(tǒng),則使用<malloc/malloc.h>中的內(nèi)存分配函數(shù)。???
D ,其它情況,在每一段分配好的空間前頭,同一時(shí)候多分配一個(gè)定長(zhǎng)的字段,用來記錄分配的空間大小。?
tc_malloc是google開源處理的一套內(nèi)存管理庫(kù),是用C++實(shí)現(xiàn)的,主頁(yè)在 這里 。TCMalloc給每一個(gè)線程分配了一個(gè)線程局部緩存。小分配能夠直接由線程局部緩存來滿足。須要的話,會(huì)將對(duì)象從中央數(shù)據(jù)結(jié)構(gòu)移動(dòng)到線程局部緩存中,同一時(shí)候定期的垃圾收集將用于把內(nèi)存從線程局部緩存遷移回中央數(shù)據(jù)結(jié)構(gòu)中。這篇 文章 里對(duì)TCMalloc有個(gè)具體的介紹。
jemalloc 也是一個(gè)內(nèi)存創(chuàng)管理庫(kù),其創(chuàng)始人Jason Evans也是在FreeBSD非常有名的開發(fā)者 ,參見 這里 。Jemalloc聚集了malloc的使用過程中所驗(yàn)證的非常多技術(shù)。忽略細(xì)節(jié),從架構(gòu)著眼,最出色的部分仍是arena和thread cache。
讀者一定會(huì)有疑問系統(tǒng)不是有了malloc 嗎,為什么還有這種內(nèi)存管理庫(kù)?? 因?yàn)榻?jīng)典的libc的分配器碎片率為較高,能夠查看 這篇文章 的分析,關(guān)于內(nèi)存碎片不太了解的童鞋請(qǐng)參考 這里 ,?malloc 和free 怎么工作的參考 這里 。 關(guān)于ptmalloc,tcmalloc和jemalloc內(nèi)存分配策略的一篇總結(jié)不錯(cuò)的文章,請(qǐng)點(diǎn) 這里 。
以下介紹redis封裝的內(nèi)存管理相關(guān)函數(shù), src/zmalloc.h 有相關(guān)聲明。
void *zmalloc(size_t size);//malloc void *zcalloc(size_t size);//calloc void *zrealloc(void *ptr, size_t size);/realloc void zfree(void *ptr);//free char *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內(nèi)存分配函數(shù) void *zmalloc(size_t size),其相應(yīng)的聲明形式例如以下:
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 }
閱讀源代碼我們發(fā)現(xiàn)有個(gè)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
結(jié)合 src/zmalloc.h 有相關(guān)宏聲明,我們發(fā)現(xiàn),由于 tc_malloc 、je_malloc 和 Mac平臺(tái)下的 malloc 函數(shù)族提供了計(jì)算已分配空間大小的函數(shù)(各自是tc_malloc_size, je_malloc_usable_size和malloc_size),所以就不須要單獨(dú)分配一段空間記錄大小了。在linux和sun平臺(tái)則要記錄分配空間大小。對(duì)于linux,使用sizeof(size_t)定長(zhǎng)字段記錄;對(duì)于sun 系統(tǒng),使用sizeof(long long)定長(zhǎng)字段記錄,其相應(yīng)源代碼中的 PREFIX_SIZE 宏。
PREFIX_SIZE 有什么用呢?
為了統(tǒng)計(jì)當(dāng)前進(jìn)程究竟占用了多少內(nèi)存。在
zmalloc.c
中,有一個(gè)靜態(tài)變量:
static size_t used_memory = 0;這個(gè)變量它記錄了進(jìn)程當(dāng)前占用的內(nèi)存總數(shù)。每當(dāng)要分配內(nèi)存或是釋放內(nèi)存的時(shí)候,都要更新這個(gè)變量(當(dāng)然能夠是線程安全的)。由于分配內(nèi)存的時(shí)候,須要指定分配多少內(nèi)存。可是釋放內(nèi)存的時(shí)候,(對(duì)于未提供malloc_size函數(shù)的內(nèi)存庫(kù))通過指向要釋放內(nèi)存的指針是不能知道釋放的空間究竟有多大的。這時(shí)候,上面提到的PREFIX_SIZE就起作用了,能夠通過當(dāng)中記錄的內(nèi)容得到空間的大小。(只是在linux系統(tǒng)上也有對(duì)應(yīng)的函數(shù)獲得分配內(nèi)存空間的大小,參見 這里 )。
通過zmalloc的源代碼我們能夠發(fā)現(xiàn),其分配空間代碼為void *ptr = malloc(size+PREFIX_SIZE); 顯然其分配空間大小為:size+PREFIX_SIZE ,對(duì)于使用tc_malloc或je_malloc的情況或mac系統(tǒng),其 PREFIX_SIZE 為0。當(dāng)分配失敗時(shí)有對(duì)應(yīng)的出錯(cuò)處理 。
前面我們已經(jīng)說過redis通過使用used_memory 的變量來統(tǒng)計(jì)當(dāng)前進(jìn)程究竟占用了多少內(nèi)存,因此在分配和釋放內(nèi)存時(shí)我們須要緊接著更新used_memory 的相應(yīng)值,相應(yīng)到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上面的代碼有事宏預(yù)處理 #ifdef HAVE_MALLOC_SIZE 顯然是上面我們說過的利用的tc_malloc je_malloc Mac等提供malloc_size函數(shù)的情形,我們能夠非常easy得知分配內(nèi)存的大小通過統(tǒng)一化的malloc_size函數(shù)就可以。可是對(duì)于沒有提供malloc_size功能的函數(shù),redis是怎么處理的呢?看上面的源代碼 #else以下的代碼即是事實(shí)上現(xiàn),其相應(yīng)的內(nèi)存結(jié)構(gòu)例如以下:
prefix-size | memory size |
redis通過update_zmalloc_stat_alloc(__n,__size) 和 update_zmalloc_stat_free(__n) 這兩個(gè)宏負(fù)責(zé)在分配內(nèi)存或是釋放內(nèi)存的時(shí)候更新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把這個(gè)更新操作寫成宏的形式主要是處于效率的考慮。
上面的代碼中?
A,if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
? 主要是考慮對(duì)齊問題,保證新增的_n 是 sizeof(long)的倍數(shù)。
B, ? if (zmalloc_thread_safe) { \
??????? update_zmalloc_stat_add(_n); \
?????? }
假設(shè)進(jìn)程中有多個(gè)線程存在,并保證線程安全zmalloc_thread_safe,則在更新變量的時(shí)候要加鎖。? 通過宏HAVE_ATOMIC選擇對(duì)應(yīng)的同步機(jī)制。
zmalloc_calloc、zmalloc_free等的實(shí)現(xiàn)就不細(xì)致介紹了詳情參見 源代碼 。
最后解說下
zmalloc_get_rss()
函數(shù)。
?? 這個(gè)函數(shù)用來獲取進(jìn)程的RSS。神馬是RSS?全稱為Resident Set Size,指實(shí)際使用物理內(nèi)存(包括共享庫(kù)占用的內(nèi)存)。在linux系統(tǒng)中,能夠通過讀取/proc/pid/stat文件系統(tǒng)獲取,pid為當(dāng)前進(jìn)程的進(jìn)程號(hào)。讀取到的不是byte數(shù),而是內(nèi)存頁(yè)數(shù)。通過系統(tǒng)調(diào)用sysconf(_SC_PAGESIZE)能夠獲得當(dāng)前系統(tǒng)的內(nèi)存頁(yè)大小。 獲得進(jìn)程的RSS后,能夠計(jì)算眼下數(shù)據(jù)的內(nèi)存碎片大小,直接用rss除以u(píng)sed_memory。rss包括進(jìn)程的全部?jī)?nèi)存使用,包括代碼,共享庫(kù),堆棧等。 哪來的內(nèi)存碎片?上面我們已經(jīng)說明了通常考慮到效率,往往有內(nèi)存對(duì)齊等方面的考慮,所以,碎片就在這里產(chǎn)生了。相比傳統(tǒng)glibc中的malloc的內(nèi)存利用率不是非常高通常會(huì)使用別的內(nèi)存庫(kù)系統(tǒng)。在redis中默認(rèn)的已經(jīng)不使用簡(jiǎn)單的malloc了而是使用 jemalloc, 在源文件src/Makefile下有這樣一段代碼:
能夠知道在linux系統(tǒng)上默認(rèn)使用jemalloc, 在redis公布的源代碼中有相關(guān)的庫(kù) deps/jemalloc 。ifeq ($(uname_S),Linux)MALLOC = jemalloc
總的來說 redis則全然自主分配內(nèi)存,在請(qǐng)求到的時(shí)候?qū)崟r(shí)依據(jù)內(nèi)建的算法分配內(nèi)存,全然自主控制內(nèi)存的管理。簡(jiǎn)單即是美吧,只是功能確實(shí)強(qiáng)大。
參考:
http://blog.ddup.us/?p=136
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
