
作者:京東零售 吳佳
redis,對于一個(gè)java開發(fā)工程師來講,其實(shí)算不得什么復(fù)雜新奇的技術(shù),但可能也很少人去深入了解學(xué)習(xí)它的底層的一些東西。下面將通過對內(nèi)存統(tǒng)計(jì)、內(nèi)存劃分、存儲(chǔ)細(xì)節(jié)、對象類型&內(nèi)部編碼這四個(gè)模塊來學(xué)習(xí)學(xué)習(xí)redis的內(nèi)存模型,手字筆錄,潛心修行。
(資料圖片)
info memory 命令查看內(nèi)存使用情況:服務(wù)器基本信息、CPU、內(nèi)存、持久化、客戶端連接信息等等,如下圖: ?
??
used_memory:Redis分配器分配的內(nèi)存總量 + 虛擬內(nèi)存(磁盤)
used_memory_rss:Redis進(jìn)程占據(jù)操作系統(tǒng)的內(nèi)存 + 進(jìn)程運(yùn)行本身需要的內(nèi)存 + 內(nèi)存碎片等 (*:注意 used_memory_rss 不包括虛擬內(nèi)存)
兩者區(qū)別:
①面向角度:used_memory: Redis角度 used_memory_rss:操作系統(tǒng)角度
②大小不一定是后者大于前者:內(nèi)存碎片和Redis進(jìn)程運(yùn)行需要占用內(nèi)存,使得前者可能比后者小,另一方面虛擬內(nèi)存的存在,使得前者可能比后者大
內(nèi)存碎片比率, 等于 used_memory_rss / used_memory
mem_fragmentation_ratio > 1 : 值越大,內(nèi)存碎片比例越大
mem_fragmentation_ratio < 1 : 說明Redis使用了虛擬內(nèi)存
*:由于虛擬內(nèi)存的媒介是磁盤,比內(nèi)存速度要慢很多,當(dāng)這種情況出現(xiàn)時(shí),應(yīng)該及時(shí)排查,如果內(nèi)存不足應(yīng)該及時(shí)處理,如增加Redis節(jié)點(diǎn)、增加Redis服務(wù)器的內(nèi)存、優(yōu)化應(yīng)用等。
正常情況下:mem_fragmentation_ratio = 1.03左右 (健康:對于jemalloc來說)
上面的情況:沒有向Redis中存入數(shù)據(jù),Redis進(jìn)程本身運(yùn)行的內(nèi)存使得used_memory_rss 比used_memory大得多
Redis使用的內(nèi)存分配器,在編譯時(shí)指定,可以是 libc 、jemalloc或者tcmalloc,默認(rèn)是jemalloc。
Redis的內(nèi)存消耗峰值
字面含義,以人類閱讀的方式返回。
?
數(shù)據(jù):最主要的部分,會(huì)統(tǒng)計(jì)在used_memory。實(shí)際上,在Redis內(nèi)部,每種類型可能有2種或更多的內(nèi)部編碼實(shí)現(xiàn)。此外,Redis在存儲(chǔ)對象時(shí),并不是直接將數(shù)據(jù)扔進(jìn)內(nèi)存,而是會(huì)對對象進(jìn)行各種包裝:如RedisObject、SDS等。
進(jìn)程本身內(nèi)存:Redis主進(jìn)程本身運(yùn)行肯定需要占用內(nèi)存,如代碼、常量池等等。這部分內(nèi)存大約幾兆,在大多數(shù)生產(chǎn)環(huán)境中與Redis數(shù)據(jù)占用的內(nèi)存相比可以忽略。這部分內(nèi)存不是由jemalloc分配,因此不會(huì)統(tǒng)計(jì)在used_memory中。
緩沖內(nèi)存:包含客戶端緩沖區(qū)、復(fù)制積壓緩沖區(qū)、AOF緩沖區(qū)
客戶端緩沖區(qū):存儲(chǔ)客戶端連接的輸入輸出緩沖
復(fù)制積壓緩沖區(qū):用于部分復(fù)制功能
AOF緩沖區(qū):用于在進(jìn)行AOF重寫時(shí),保存最近的寫入命令
內(nèi)存碎片:內(nèi)存碎片是Redis在分配、回收物理內(nèi)存過程中產(chǎn)生的。
?
當(dāng)我們執(zhí)行一個(gè)redis指令,比如:set hello world,redis底層存儲(chǔ)到底干了什么?
?
??
內(nèi)存分配器:可以是 libc 、jemalloc或者tcmalloc,默認(rèn)jemalloc
jemalloc內(nèi)存劃分:小、大、巨大,每個(gè)又分許多小內(nèi)存塊單位
(例如,如果需要存儲(chǔ)大小為130字節(jié)的對象,jemalloc會(huì)將其放入160字節(jié)的內(nèi)存單元中。)
redis的五種類型都是通過RedisObject存儲(chǔ),Redis對象的 類型、內(nèi)部編碼、內(nèi)存回收、共享對象等功能都需要RedisObject對象支持。
typedef struct redisObject{ unsigned type:4; unsigned encoding:4; unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */ int refcount; void *ptr;}
type:表示對象的數(shù)據(jù)類型,占4bit。
encoding:表示對象內(nèi)部的編碼,占4bit,對于redis的每種數(shù)據(jù)類型,都至少有倆
種內(nèi)部編碼。比如字符串類型有:int、embstr、raw。
lru:記錄的是對象最后一次被命令程序訪問的時(shí)間,占據(jù)的比特?cái)?shù)不同的版本有所不同(如4.0版本占24比特,2.6版本占22比特)。
refcount:
1、概念:refcount記錄的是該對象被引用的次數(shù),類型目前僅為整型。
2、作用:refcount的作用,主要在于對象的引用計(jì)數(shù)和內(nèi)存回收:
①當(dāng)創(chuàng)建新對象時(shí),refcount初始化為1;
②當(dāng)有新程序使用該對象時(shí),refcount加1;
③當(dāng)對象不再被一個(gè)新程序使用時(shí),refcount減1;
④當(dāng)refcount變?yōu)?時(shí),對象占用的內(nèi)存會(huì)被釋放。
3、為什么只支持整數(shù)值的字符串對象?對內(nèi)存和CPU(時(shí)間)的平衡:
①對于整數(shù)值,判斷操作復(fù)雜度為O(1);
②對于普通字符串,判斷復(fù)雜度為O(n);
③而對于哈希、列表、集合和有序集合,判斷的復(fù)雜度為O(n^2)。
4、目前實(shí)現(xiàn):Redis服務(wù)器在初始化時(shí),會(huì)創(chuàng)建10000個(gè)字符串對象,值分別是0~9999的整數(shù)值;10000這個(gè)數(shù)字可以通過調(diào)整參數(shù)REDIS_SHARED_INTEGERS(4.0中是 OBJ_SHARED_INTEGERS)的值進(jìn)行改變。(共享對象的引用次數(shù)可以通過object refcount命令查看:)
ptr:ptr指針指向具體的數(shù)據(jù),如前面的例子中,set hello world,ptr指向包含字符串world的SDS
1、概念:Redis沒有直接使用C字符串(即以空字符‘\0’結(jié)尾的字符數(shù)組)作為默認(rèn)的字符串表示,而是使用了SDS。SDS是簡單動(dòng)態(tài)字符串(Simple Dynamic String)的縮寫。
2、結(jié)構(gòu):
??
3、相關(guān)計(jì)算:
*:buf數(shù)組的長度 = free+len+1(其中1表示字符串結(jié)尾的空字符)
一個(gè)SDS結(jié)構(gòu)占據(jù)的空間 = free所占長度+len所占長度+ buf數(shù)組的長度=4+4+free+len+1=free+len+9。
4、加“\0”目的:為了簡單字符串能夠調(diào)用c字符串部分函數(shù)
?
??
1、字符串長度不超過512MB
2、內(nèi)部編碼有三種: int、embstr、raw
3、編碼轉(zhuǎn)換關(guān)系:
int:整形
embstr:<=39字節(jié)的字符串
raw:>39字節(jié)的字符串
4、embstr和raw的區(qū)別:
①embstr都使用redisObject和sds結(jié)構(gòu)存儲(chǔ)
②emstr創(chuàng)建只分配一次內(nèi)存空間(redisObject和sds一起分配,因?yàn)樗沁B續(xù)的)
缺點(diǎn):創(chuàng)建和刪除都需要整個(gè)redisObject和sds重新分配空間,所以emstr實(shí)現(xiàn)為只讀。
③raw需要分配兩次
5、當(dāng)emstr被修改時(shí),會(huì)先變成raw,再修改,無論是否達(dá)到39字節(jié)
這也是為了避免創(chuàng)建整個(gè)redisObject和sds
?
??
?
??
1、內(nèi)部編碼:ziplist和linkedlist:(每個(gè)節(jié)點(diǎn)指向的是redisObject)
2、壓縮列表:節(jié)約空間,連續(xù)內(nèi)存塊
3、編碼轉(zhuǎn)換:什么情況下使用壓縮列表?
①列表元素 < 512個(gè)
②列表中所有字符串對象都不足64字節(jié)(字符串長度)
?
??
??
內(nèi)層哈希:ziplist、hashtable
外層哈希:hashtable
?
??