幾個(gè)常用了命令行
- 登錄
redis-cli -h 127.0.0.1 -p 6379 -a 123
- 查看內(nèi)存
info memory
這里面 info 是命令 memory 是參數(shù)
單單輸入 info 就死查看所有的信息,如果只需要查看內(nèi)存情況杭朱,只需要加上內(nèi)存這個(gè)參數(shù)
127.0.0.1:6379> info memory
# Memory
used_memory:1031440
used_memory_human:1007.27K
used_memory_rss:897024
used_memory_rss_human:876.00K
used_memory_peak:1031440
used_memory_peak_human:1007.27K
used_memory_peak_perc:100.01%
used_memory_overhead:1030414
used_memory_startup:980784
used_memory_dataset:1026
used_memory_dataset_perc:2.03%
total_system_memory:8589934592
total_system_memory_human:8.00G
used_memory_lua:37888
used_memory_lua_human:37.00K
maxmemory:0
maxmemory_human:0B
maxmemory_policy:noeviction
mem_fragmentation_ratio:0.87
mem_allocator:libc
active_defrag_running:0
lazyfree_pending_objects:0
返回結(jié)果中比較重要的幾個(gè)說(shuō)明如下:
(1)used_memory:Redis分配器分配的內(nèi)存總量(單位是字節(jié))嚣镜,包括使用的虛擬內(nèi)存(即swap)轻局;Redis分配器后面會(huì)介紹。used_memory_human只是顯示更友好京办。
(2)used_memory_rss:Redis進(jìn)程占據(jù)操作系統(tǒng)的內(nèi)存(單位是字節(jié)),與top及ps命令看到的值是一致的;除了分配器分配的內(nèi)存之外们何,used_memory_rss還包括進(jìn)程運(yùn)行本身需要的內(nèi)存、內(nèi)存碎片等控轿,但是不包括虛擬內(nèi)存冤竹。
因此拂封,used_memory和used_memory_rss,前者是從Redis角度得到的量鹦蠕,后者是從操作系統(tǒng)角度得到的量冒签。二者之所以有所不同,一方面是因?yàn)閮?nèi)存碎片和Redis進(jìn)程運(yùn)行需要占用內(nèi)存钟病,使得前者可能比后者小萧恕,另一方面虛擬內(nèi)存的存在,使得前者可能比后者大肠阱。
由于在實(shí)際應(yīng)用中票唆,Redis的數(shù)據(jù)量會(huì)比較大,此時(shí)進(jìn)程運(yùn)行占用的內(nèi)存與Redis數(shù)據(jù)量和內(nèi)存碎片相比屹徘,都會(huì)小得多惰说;因此used_memory_rss和used_memory的比例,便成了衡量Redis內(nèi)存碎片率的參數(shù)缘回;這個(gè)參數(shù)就是mem_fragmentation_ratio吆视。
(3)mem_fragmentation_ratio:內(nèi)存碎片比率,該值是used_memory_rss / used_memory的比值酥宴。
mem_fragmentation_ratio一般大于1啦吧,且該值越大,內(nèi)存碎片比例越大拙寡。mem_fragmentation_ratio<1授滓,說(shuō)明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)用等和橙。
一般來(lái)說(shuō),mem_fragmentation_ratio在1.03左右是比較健康的狀態(tài)(對(duì)于jemalloc來(lái)說(shuō))造垛;上面截圖中的mem_fragmentation_ratio值很大魔招,是因?yàn)檫€沒(méi)有向Redis中存入數(shù)據(jù),Redis進(jìn)程本身運(yùn)行的內(nèi)存使得used_memory_rss 比used_memory大得多五辽。
(4)mem_allocator:Redis使用的內(nèi)存分配器办斑,在編譯時(shí)指定;可以是 libc 杆逗、jemalloc或者tcmalloc乡翅,默認(rèn)是jemalloc吁讨;截圖中使用的便是默認(rèn)的jemalloc。
Redis內(nèi)存劃分
Redis作為內(nèi)存數(shù)據(jù)庫(kù)峦朗,在內(nèi)存中存儲(chǔ)的內(nèi)容主要是數(shù)據(jù)(鍵值對(duì))建丧;通過(guò)前面的敘述可以知道,除了數(shù)據(jù)以外波势,Redis的其他部分也會(huì)占用內(nèi)存翎朱。
Redis的內(nèi)存占用主要可以劃分為以下幾個(gè)部分:
數(shù)據(jù)
作為數(shù)據(jù)庫(kù),數(shù)據(jù)是最主要的部分尺铣;這部分占用的內(nèi)存會(huì)統(tǒng)計(jì)在used_memory中拴曲。
Redis使用鍵值對(duì)存儲(chǔ)數(shù)據(jù),其中的值(對(duì)象)包括5種類型凛忿,即字符串澈灼、哈希、列表店溢、集合叁熔、有序集合。這5種類型是Redis對(duì)外提供的床牧,實(shí)際上荣回,在Redis內(nèi)部,每種類型可能有2種或更多的內(nèi)部編碼實(shí)現(xiàn)戈咳;此外心软,Redis在存儲(chǔ)對(duì)象時(shí),并不是直接將數(shù)據(jù)扔進(jìn)內(nèi)存著蛙,而是會(huì)對(duì)對(duì)象進(jìn)行各種包裝:如redisObject删铃、SDS等;這篇文章后面將重點(diǎn)介紹Redis中數(shù)據(jù)存儲(chǔ)的細(xì)節(jié)踏堡。
進(jìn)程本身運(yù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中。
補(bǔ)充說(shuō)明:除了主進(jìn)程外挤渔,Redis創(chuàng)建的子進(jìn)程運(yùn)行也會(huì)占用內(nèi)存肮街,如Redis執(zhí)行AOF、RDB重寫(xiě)時(shí)創(chuàng)建的子進(jìn)程判导。當(dāng)然嫉父,這部分內(nèi)存不屬于Redis進(jìn)程沛硅,也不會(huì)統(tǒng)計(jì)在used_memory和used_memory_rss中。
緩沖內(nèi)存
緩沖內(nèi)存包括客戶端緩沖區(qū)绕辖、復(fù)制積壓緩沖區(qū)摇肌、AOF緩沖區(qū)等;其中仪际,客戶端緩沖存儲(chǔ)客戶端連接的輸入輸出緩沖围小;復(fù)制積壓緩沖用于部分復(fù)制功能;AOF緩沖區(qū)用于在進(jìn)行AOF重寫(xiě)時(shí)树碱,保存最近的寫(xiě)入命令肯适。在了解相應(yīng)功能之前,不需要知道這些緩沖的細(xì)節(jié)成榜;這部分內(nèi)存由jemalloc分配框舔,因此會(huì)統(tǒng)計(jì)在used_memory中。
內(nèi)存碎片
內(nèi)存碎片是Redis在分配赎婚、回收物理內(nèi)存過(guò)程中產(chǎn)生的刘绣。例如,如果對(duì)數(shù)據(jù)的更改頻繁挣输,而且數(shù)據(jù)之間的大小相差很大额港,可能導(dǎo)致redis釋放的空間在物理內(nèi)存中并沒(méi)有釋放,但redis又無(wú)法有效利用歧焦,這就形成了內(nèi)存碎片移斩。內(nèi)存碎片不會(huì)統(tǒng)計(jì)在used_memory中。
內(nèi)存碎片的產(chǎn)生與對(duì)數(shù)據(jù)進(jìn)行的操作绢馍、數(shù)據(jù)的特點(diǎn)等都有關(guān)向瓷;此外,與使用的內(nèi)存分配器也有關(guān)系:如果內(nèi)存分配器設(shè)計(jì)合理舰涌,可以盡可能的減少內(nèi)存碎片的產(chǎn)生猖任。后面將要說(shuō)到的jemalloc便在控制內(nèi)存碎片方面做的很好。
如果Redis服務(wù)器中的內(nèi)存碎片已經(jīng)很大瓷耙,可以通過(guò)安全重啟的方式減小內(nèi)存碎片:因?yàn)橹貑⒅笾焯桑琑edis重新從備份文件中讀取數(shù)據(jù),在內(nèi)存中進(jìn)行重排搁痛,為每個(gè)數(shù)據(jù)重新選擇合適的內(nèi)存單元长搀,減小內(nèi)存碎片。
Redis數(shù)據(jù)存儲(chǔ)的細(xì)節(jié)
關(guān)于Redis數(shù)據(jù)存儲(chǔ)的細(xì)節(jié)鸡典,涉及到內(nèi)存分配器(如jemalloc)源请、簡(jiǎn)單動(dòng)態(tài)字符串(SDS)、5種對(duì)象類型及內(nèi)部編碼、redisObject谁尸。在講述具體內(nèi)容之前舅踪,先說(shuō)明一下這幾個(gè)概念之間的關(guān)系。
下圖是執(zhí)行set hello world時(shí)良蛮,所涉及到的數(shù)據(jù)模型抽碌。
(1)dictEntry:Redis是Key-Value數(shù)據(jù)庫(kù),因此對(duì)每個(gè)鍵值對(duì)都會(huì)有一個(gè)dictEntry决瞳,里面存儲(chǔ)了指向Key和Value的指針货徙;next指向下一個(gè)dictEntry,與本Key-Value無(wú)關(guān)瞒斩。
(2)Key:圖中右上角可見(jiàn)破婆,Key(”hello”)并不是直接以字符串存儲(chǔ),而是存儲(chǔ)在SDS結(jié)構(gòu)中胸囱。
(3)redisObject:value(“world”)既不是直接以字符串存儲(chǔ)祷舀,也不是像Key一樣直接存儲(chǔ)在SDS中,而是存儲(chǔ)在redisObject中烹笔。實(shí)際上裳扯,不論Value是5種類型的哪一種,都是通過(guò)redisObject來(lái)存儲(chǔ)的谤职;而redisObject中的type字段指明了value對(duì)象的類型饰豺,ptr字段則指向?qū)ο笏诘牡刂贰2贿^(guò)可以看出允蜈,字符串對(duì)象雖然經(jīng)過(guò)了redisObject的包裝冤吨,但仍然需要通過(guò)SDS存儲(chǔ)。
實(shí)際上饶套,redisObject除了type和ptr字段以外漩蟆,還有其他字段圖中沒(méi)有給出,如用于指定對(duì)象內(nèi)部編碼的字段妓蛮;后面會(huì)詳細(xì)介紹怠李。
(4)jemalloc:無(wú)論是DictEntry對(duì)象,還是redisObject蛤克、SDS對(duì)象捺癞,都需要內(nèi)存分配器(如jemalloc)分配內(nèi)存進(jìn)行存儲(chǔ)。以DictEntry對(duì)象為例构挤,有3個(gè)指針組成髓介,在64位機(jī)器下占24個(gè)字節(jié),jemalloc會(huì)為它分配32字節(jié)大小的內(nèi)存單元儿倒。
下面來(lái)分別介紹jemalloc版保、redisObject呜笑、SDS夫否、對(duì)象類型及內(nèi)部編碼彻犁。
jemalloc
Redis在編譯時(shí)便會(huì)指定內(nèi)存分配器;內(nèi)存分配器可以是 libc 凰慈、jemalloc或者tcmalloc汞幢,默認(rèn)是jemalloc。
jemalloc作為Redis的默認(rèn)內(nèi)存分配器微谓,在減小內(nèi)存碎片方面做的相對(duì)比較好森篷。jemalloc在64位系統(tǒng)中,將內(nèi)存空間劃分為小豺型、大仲智、巨大三個(gè)范圍;每個(gè)范圍內(nèi)又劃分了許多小的內(nèi)存塊單位姻氨;當(dāng)Redis存儲(chǔ)數(shù)據(jù)時(shí)钓辆,會(huì)選擇大小最合適的內(nèi)存塊進(jìn)行存儲(chǔ)。
jemalloc劃分的內(nèi)存單元如下圖所示:
例如肴焊,如果需要存儲(chǔ)大小為130字節(jié)的對(duì)象前联,jemalloc會(huì)將其放入160字節(jié)的內(nèi)存單元中。
redisObject
前面說(shuō)到娶眷,Redis對(duì)象有5種類型似嗤;無(wú)論是哪種類型,Redis都不會(huì)直接存儲(chǔ)届宠,而是通過(guò)redisObject對(duì)象進(jìn)行存儲(chǔ)烁落。
redisObject對(duì)象非常重要,Redis對(duì)象的類型豌注、內(nèi)部編碼伤塌、內(nèi)存回收、共享對(duì)象等功能幌羞,都需要redisObject支持寸谜,下面將通過(guò)redisObject的結(jié)構(gòu)來(lái)說(shuō)明它是如何起作用的。
redisObject的定義如下(不同版本的Redis可能稍稍有所不同):
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
int refcount;
void *ptr;
} robj;
(1)type
type字段表示對(duì)象的類型属桦,占4個(gè)比特熊痴;目前包括REDIS_STRING(字符串)、REDIS_LIST (列表)聂宾、REDIS_HASH(哈希)果善、REDIS_SET(集合)、REDIS_ZSET(有序集合)系谐。
當(dāng)我們執(zhí)行type命令時(shí)巾陕,便是通過(guò)讀取RedisObject的type字段獲得對(duì)象的類型讨跟;如下圖所示:
2)encoding
encoding表示對(duì)象的內(nèi)部編碼,占4個(gè)比特鄙煤。
對(duì)于Redis支持的每種類型晾匠,都有至少兩種內(nèi)部編碼,例如對(duì)于字符串梯刚,有int凉馆、embstr、raw三種編碼亡资。通過(guò)encoding屬性澜共,Redis可以根據(jù)不同的使用場(chǎng)景來(lái)為對(duì)象設(shè)置不同的編碼,大大提高了Redis的靈活性和效率锥腻。以列表對(duì)象為例嗦董,有壓縮列表和雙端鏈表兩種編碼方式;如果列表中的元素較少瘦黑,Redis傾向于使用壓縮列表進(jìn)行存儲(chǔ)京革,因?yàn)閴嚎s列表占用內(nèi)存更少,而且比雙端鏈表可以更快載入供璧;當(dāng)列表對(duì)象元素較多時(shí)存崖,壓縮列表就會(huì)轉(zhuǎn)化為更適合存儲(chǔ)大量元素的雙端鏈表。
通過(guò)object encoding命令睡毒,可以查看對(duì)象采用的編碼方式来惧,如下圖所示:
3)lru
lru記錄的是對(duì)象最后一次被命令程序訪問(wèn)的時(shí)間,占據(jù)的比特?cái)?shù)不同的版本有所不同(如4.0版本占24比特演顾,2.6版本占22比特)供搀。
通過(guò)對(duì)比lru時(shí)間與當(dāng)前時(shí)間,可以計(jì)算某個(gè)對(duì)象的空轉(zhuǎn)時(shí)間钠至;object idletime命令可以顯示該空轉(zhuǎn)時(shí)間(單位是秒)葛虐。object idletime命令的一個(gè)特殊之處在于它不改變對(duì)象的lru值。
lru值除了通過(guò)object idletime命令打印之外棉钧,還與Redis的內(nèi)存回收有關(guān)系:如果Redis打開(kāi)了maxmemory選項(xiàng)屿脐,且內(nèi)存回收算法選擇的是volatile-lru或allkeys—lru,那么當(dāng)Redis內(nèi)存占用超過(guò)maxmemory指定的值時(shí)宪卿,Redis會(huì)優(yōu)先選擇空轉(zhuǎn)時(shí)間最長(zhǎng)的對(duì)象進(jìn)行釋放的诵。
(4)refcount
refcount與共享對(duì)象
refcount記錄的是該對(duì)象被引用的次數(shù),類型為整型佑钾。refcount的作用西疤,主要在于對(duì)象的引用計(jì)數(shù)和內(nèi)存回收。當(dāng)創(chuàng)建新對(duì)象時(shí)休溶,refcount初始化為1代赁;當(dāng)有新程序使用該對(duì)象時(shí)扰她,refcount加1;當(dāng)對(duì)象不再被一個(gè)新程序使用時(shí)芭碍,refcount減1徒役;當(dāng)refcount變?yōu)?時(shí),對(duì)象占用的內(nèi)存會(huì)被釋放豁跑。
Redis中被多次使用的對(duì)象(refcount>1)廉涕,稱為共享對(duì)象泻云。Redis為了節(jié)省內(nèi)存艇拍,當(dāng)有一些對(duì)象重復(fù)出現(xiàn)時(shí),新的程序不會(huì)創(chuàng)建新的對(duì)象宠纯,而是仍然使用原來(lái)的對(duì)象卸夕。這個(gè)被重復(fù)使用的對(duì)象,就是共享對(duì)象婆瓜。目前共享對(duì)象僅支持整數(shù)值的字符串對(duì)象快集。
共享對(duì)象的具體實(shí)現(xiàn)
Redis的共享對(duì)象目前只支持整數(shù)值的字符串對(duì)象。之所以如此廉白,實(shí)際上是對(duì)內(nèi)存和CPU(時(shí)間)的平衡:共享對(duì)象雖然會(huì)降低內(nèi)存消耗个初,但是判斷兩個(gè)對(duì)象是否相等卻需要消耗額外的時(shí)間。對(duì)于整數(shù)值猴蹂,判斷操作復(fù)雜度為O(1)院溺;對(duì)于普通字符串,判斷復(fù)雜度為O(n)磅轻;而對(duì)于哈希珍逸、列表、集合和有序集合聋溜,判斷的復(fù)雜度為O(n^2)谆膳。
雖然共享對(duì)象只能是整數(shù)值的字符串對(duì)象,但是5種類型都可能使用共享對(duì)象(如哈希撮躁、列表等的元素可以使用)漱病。
就目前的實(shí)現(xiàn)來(lái)說(shuō),Redis服務(wù)器在初始化時(shí)把曼,會(huì)創(chuàng)建10000個(gè)字符串對(duì)象杨帽,值分別是0到9999的整數(shù)值;當(dāng)Redis需要使用值為0到9999的字符串對(duì)象時(shí)祝迂,可以直接使用這些共享對(duì)象睦尽。10000這個(gè)數(shù)字可以通過(guò)調(diào)整參數(shù)REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值進(jìn)行改變。
共享對(duì)象的引用次數(shù)可以通過(guò)object refcount命令查看型雳,如下圖所示当凡。命令執(zhí)行的結(jié)果頁(yè)佐證了只有0~9999之間的整數(shù)會(huì)作為共享對(duì)象山害。
(5)ptr
ptr指針指向具體的數(shù)據(jù),如前面的例子中沿量,set hello world浪慌,ptr指向包含字符串world的SDS。
(6)總結(jié)
綜上所述朴则,redisObject的結(jié)構(gòu)與對(duì)象類型权纤、編碼、內(nèi)存回收乌妒、共享對(duì)象都有關(guān)系汹想;一個(gè)redisObject對(duì)象的大小為16字節(jié):
4bit+4bit+24bit+4Byte+8Byte=16Byte。
SDS
Redis沒(méi)有直接使用C字符串(即以空字符’\0’結(jié)尾的字符數(shù)組)作為默認(rèn)的字符串表示撤蚊,而是使用了SDS古掏。SDS是簡(jiǎn)單動(dòng)態(tài)字符串(Simple Dynamic String)的縮寫(xiě)。
(1)SDS結(jié)構(gòu)
sds的結(jié)構(gòu)如下:
struct sdshdr {
int len;
int free;
char buf[];
};
其中侦啸,buf表示字節(jié)數(shù)組槽唾,用來(lái)存儲(chǔ)字符串;len表示buf已使用的長(zhǎng)度光涂,free表示buf未使用的長(zhǎng)度庞萍。下面是兩個(gè)例子。
通過(guò)SDS的結(jié)構(gòu)可以看出忘闻,buf數(shù)組的長(zhǎng)度=free+len+1(其中1表示字符串結(jié)尾的空字符)钝计;所以牍疏,一個(gè)SDS結(jié)構(gòu)占據(jù)的空間為:free所占長(zhǎng)度+len所占長(zhǎng)度+ buf數(shù)組的長(zhǎng)度=4+4+free+len+1=free+len+9最盅。
(2)SDS與C字符串的比較
SDS在C字符串的基礎(chǔ)上加入了free和len字段,帶來(lái)了很多好處:
獲取字符串長(zhǎng)度:SDS是O(1)檀何,C字符串是O(n)
緩沖區(qū)溢出:使用C字符串的API時(shí)重虑,如果字符串長(zhǎng)度增加(如strcat操作)而忘記重新分配內(nèi)存践付,很容易造成緩沖區(qū)的溢出;而SDS由于記錄了長(zhǎng)度缺厉,相應(yīng)的API在可能造成緩沖區(qū)溢出時(shí)會(huì)自動(dòng)重新分配內(nèi)存永高,杜絕了緩沖區(qū)溢出。
修改字符串時(shí)內(nèi)存的重分配:對(duì)于C字符串提针,如果要修改字符串命爬,必須要重新分配內(nèi)存(先釋放再申請(qǐng)),因?yàn)槿绻麤](méi)有重新分配辐脖,字符串長(zhǎng)度增大時(shí)會(huì)造成內(nèi)存緩沖區(qū)溢出饲宛,字符串長(zhǎng)度減小時(shí)會(huì)造成內(nèi)存泄露。而對(duì)于SDS嗜价,由于可以記錄len和free艇抠,因此解除了字符串長(zhǎng)度和空間數(shù)組長(zhǎng)度之間的關(guān)聯(lián)幕庐,可以在此基礎(chǔ)上進(jìn)行優(yōu)化:空間預(yù)分配策略(即分配內(nèi)存時(shí)比實(shí)際需要的多)使得字符串長(zhǎng)度增大時(shí)重新分配內(nèi)存的概率大大減小家淤;惰性空間釋放策略使得字符串長(zhǎng)度減小時(shí)重新分配內(nèi)存的概率大大減小异剥。
存取二進(jìn)制數(shù)據(jù):SDS可以,C字符串不可以絮重。因?yàn)镃字符串以空字符作為字符串結(jié)束的標(biāo)識(shí)冤寿,而對(duì)于一些二進(jìn)制文件(如圖片等),內(nèi)容可能包括空字符串青伤,因此C字符串無(wú)法正確存榷搅;而SDS以字符串長(zhǎng)度len來(lái)作為字符串結(jié)束標(biāo)識(shí)潮模,因此沒(méi)有這個(gè)問(wèn)題亮蛔。
此外,由于SDS中的buf仍然使用了C字符串(即以’\0’結(jié)尾)擎厢,因此SDS可以使用C字符串庫(kù)中的部分函數(shù);但是需要注意的是辣吃,只有當(dāng)SDS用來(lái)存儲(chǔ)文本數(shù)據(jù)時(shí)才可以這樣使用动遭,在存儲(chǔ)二進(jìn)制數(shù)據(jù)時(shí)則不行(’\0’不一定是結(jié)尾)。
(3)SDS與C字符串的應(yīng)用
Redis在存儲(chǔ)對(duì)象時(shí)神得,一律使用SDS代替C字符串厘惦。例如set hello world命令,hello和world都是以SDS的形式存儲(chǔ)的哩簿。而sadd myset member1 member2 member3命令宵蕉,不論是鍵(”myset”),還是集合中的元素(”member1”节榜、 ”member2”和”member3”)羡玛,都是以SDS的形式存儲(chǔ)。除了存儲(chǔ)對(duì)象宗苍,SDS還用于存儲(chǔ)各種緩沖區(qū)稼稿。
只有在字符串不會(huì)改變的情況下,如打印日志時(shí)讳窟,才會(huì)使用C字符串让歼。
應(yīng)用舉例
估算Redis內(nèi)存使用量
要估算redis中的數(shù)據(jù)占據(jù)的內(nèi)存大小,需要對(duì)redis的內(nèi)存模型有比較全面的了解丽啡,包括前面介紹的hashtable谋右、sds、redisobject补箍、各種對(duì)象類型的編碼方式等改执。
下面以最簡(jiǎn)單的字符串類型來(lái)進(jìn)行說(shuō)明浦徊。
假設(shè)有90000個(gè)鍵值對(duì),每個(gè)key的長(zhǎng)度是7個(gè)字節(jié)天梧,每個(gè)value的長(zhǎng)度也是7個(gè)字節(jié)(且key和value都不是整數(shù))盔性;下面來(lái)估算這90000個(gè)鍵值對(duì)所占用的空間。在估算占據(jù)空間之前呢岗,首先可以判定字符串類型使用的編碼方式:embstr冕香。
90000個(gè)鍵值對(duì)占據(jù)的內(nèi)存空間主要可以分為兩部分:一部分是90000個(gè)dictEntry占據(jù)的空間;一部分是鍵值對(duì)所需要的bucket空間后豫。
每個(gè)dictEntry占據(jù)的空間包括:
一個(gè)dictEntry悉尾,24字節(jié),jemalloc會(huì)分配32字節(jié)的內(nèi)存塊
一個(gè)key挫酿,7字節(jié)构眯,所以SDS(key)需要7+9=16個(gè)字節(jié),jemalloc會(huì)分配16字節(jié)的內(nèi)存塊
一個(gè)redisObject早龟,16字節(jié)惫霸,jemalloc會(huì)分配16字節(jié)的內(nèi)存塊
一個(gè)value,7字節(jié)葱弟,所以SDS(value)需要7+9=16個(gè)字節(jié)壹店,jemalloc會(huì)分配16字節(jié)的內(nèi)存塊
綜上,一個(gè)dictEntry需要32+16+16+16=80個(gè)字節(jié)芝加。
bucket空間:bucket數(shù)組的大小為大于90000的最小的2^n硅卢,是131072;每個(gè)bucket元素為8字節(jié)(因?yàn)?4位系統(tǒng)中指針大小為8字節(jié))藏杖。
因此将塑,可以估算出這90000個(gè)鍵值對(duì)占據(jù)的內(nèi)存大小為:9000080 + 1310728 = 8248576。
下面寫(xiě)個(gè)程序在redis中驗(yàn)證一下:
public class RedisTest {
public static Jedis jedis = new Jedis("localhost", 6379);
public static void main(String[] args) throws Exception{
Long m1 = Long.valueOf(getMemory());
insertData();
Long m2 = Long.valueOf(getMemory());
System.out.println(m2 - m1);
}
public static void insertData(){
for(int i = 10000; i < 100000; i++){
jedis.set("aa" + i, "aa" + i); //key和value長(zhǎng)度都是7字節(jié)蝌麸,且不是整數(shù)
}
}
public static String getMemory(){
String memoryAllLine = jedis.info("memory");
String usedMemoryLine = memoryAllLine.split("\r\n")[1];
String memory = usedMemoryLine.substring(usedMemoryLine.indexOf(':') + 1);
return memory;
}
}
運(yùn)行結(jié)果:8247552
理論值與結(jié)果值誤差在萬(wàn)分之1.2点寥,對(duì)于計(jì)算需要多少內(nèi)存來(lái)說(shuō),這個(gè)精度已經(jīng)足夠了祥楣。之所以會(huì)存在誤差开财,是因?yàn)樵谖覀儾迦?0000條數(shù)據(jù)之前redis已分配了一定的bucket空間,而這些bucket空間尚未使用误褪。
作為對(duì)比將key和value的長(zhǎng)度由7字節(jié)增加到8字節(jié)责鳍,則對(duì)應(yīng)的SDS變?yōu)?7個(gè)字節(jié),jemalloc會(huì)分配32個(gè)字節(jié)兽间,因此每個(gè)dictEntry占用的字節(jié)數(shù)也由80字節(jié)變?yōu)?12字節(jié)历葛。此時(shí)估算這90000個(gè)鍵值對(duì)占據(jù)內(nèi)存大小為:90000112 + 1310728 = 11128576。
在redis中驗(yàn)證代碼如下(只修改插入數(shù)據(jù)的代碼):
public static void insertData(){
for(int i = 10000; i < 100000; i++){
jedis.set("aaa" + i, "aaa" + i); //key和value長(zhǎng)度都是8字節(jié),且不是整數(shù)
}
}
對(duì)于字符串類型之外的其他類型恤溶,對(duì)內(nèi)存占用的估算方法是類似的乓诽,需要結(jié)合具體類型的編碼方式來(lái)確定。
優(yōu)化內(nèi)存占用
了解redis的內(nèi)存模型咒程,對(duì)優(yōu)化redis內(nèi)存占用有很大幫助鸠天。下面介紹幾種優(yōu)化場(chǎng)景。
(1)利用jemalloc特性進(jìn)行優(yōu)化
上一小節(jié)所講述的90000個(gè)鍵值便是一個(gè)例子帐姻。由于jemalloc分配內(nèi)存時(shí)數(shù)值是不連續(xù)的稠集,因此key/value字符串變化一個(gè)字節(jié),可能會(huì)引起占用內(nèi)存很大的變動(dòng)饥瓷;在設(shè)計(jì)時(shí)可以利用這一點(diǎn)剥纷。
例如,如果key的長(zhǎng)度如果是8個(gè)字節(jié)呢铆,則SDS為17字節(jié)晦鞋,jemalloc分配32字節(jié);此時(shí)將key長(zhǎng)度縮減為7個(gè)字節(jié)棺克,則SDS為16字節(jié)悠垛,jemalloc分配16字節(jié);則每個(gè)key所占用的空間都可以縮小一半逆航。
(2)使用整型/長(zhǎng)整型
如果是整型/長(zhǎng)整型鼎文,Redis會(huì)使用int類型(8字節(jié))存儲(chǔ)來(lái)代替字符串,可以節(jié)省更多空間因俐。因此在可以使用長(zhǎng)整型/整型代替字符串的場(chǎng)景下,盡量使用長(zhǎng)整型/整型周偎。
(3)共享對(duì)象
利用共享對(duì)象抹剩,可以減少對(duì)象的創(chuàng)建(同時(shí)減少了redisObject的創(chuàng)建),節(jié)省內(nèi)存空間蓉坎。目前redis中的共享對(duì)象只包括10000個(gè)整數(shù)(0-9999)澳眷;可以通過(guò)調(diào)整REDIS_SHARED_INTEGERS參數(shù)提高共享對(duì)象的個(gè)數(shù);例如將REDIS_SHARED_INTEGERS調(diào)整到20000蛉艾,則0-19999之間的對(duì)象都可以共享钳踊。
考慮這樣一種場(chǎng)景:論壇網(wǎng)站在redis中存儲(chǔ)了每個(gè)帖子的瀏覽數(shù),而這些瀏覽數(shù)絕大多數(shù)分布在0-20000之間勿侯,這時(shí)候通過(guò)適當(dāng)增大REDIS_SHARED_INTEGERS參數(shù)拓瞪,便可以利用共享對(duì)象節(jié)省內(nèi)存空間。
(4)避免過(guò)度設(shè)計(jì)
然而需要注意的是助琐,不論是哪種優(yōu)化場(chǎng)景祭埂,都要考慮內(nèi)存空間與設(shè)計(jì)復(fù)雜度的權(quán)衡;而設(shè)計(jì)復(fù)雜度會(huì)影響到代碼的復(fù)雜度兵钮、可維護(hù)性蛆橡。
如果數(shù)據(jù)量較小舌界,那么為了節(jié)省內(nèi)存而使得代碼的開(kāi)發(fā)、維護(hù)變得更加困難并不劃算泰演;還是以前面講到的90000個(gè)鍵值對(duì)為例呻拌,實(shí)際上節(jié)省的內(nèi)存空間只有幾MB。但是如果數(shù)據(jù)量有幾千萬(wàn)甚至上億睦焕,考慮內(nèi)存的優(yōu)化就比較必要了藐握。
關(guān)注內(nèi)存碎片率
內(nèi)存碎片率是一個(gè)重要的參數(shù),對(duì)redis 內(nèi)存的優(yōu)化有重要意義复亏。
如果內(nèi)存碎片率過(guò)高(jemalloc在1.03左右比較正常)趾娃,說(shuō)明內(nèi)存碎片多,內(nèi)存浪費(fèi)嚴(yán)重缔御;這時(shí)便可以考慮重啟redis服務(wù)抬闷,在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行重排,減少內(nèi)存碎片耕突。
如果內(nèi)存碎片率小于1笤成,說(shuō)明redis內(nèi)存不足,部分?jǐn)?shù)據(jù)使用了虛擬內(nèi)存(即swap)眷茁;由于虛擬內(nèi)存的存取速度比物理內(nèi)存差很多(2-3個(gè)數(shù)量級(jí))炕泳,此時(shí)redis的訪問(wèn)速度可能會(huì)變得很慢。因此必須設(shè)法增大物理內(nèi)存(可以增加服務(wù)器節(jié)點(diǎn)數(shù)量上祈,或提高單機(jī)內(nèi)存)培遵,或減少redis中的數(shù)據(jù)。
要減少redis中的數(shù)據(jù)登刺,除了選用合適的數(shù)據(jù)類型籽腕、利用共享對(duì)象等,還有一點(diǎn)是要設(shè)置合理的數(shù)據(jù)回收策略(maxmemory-policy)纸俭,當(dāng)內(nèi)存達(dá)到一定量后皇耗,根據(jù)不同的優(yōu)先級(jí)對(duì)內(nèi)存進(jìn)行回收。