国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化

這篇具有很好參考價值的文章主要介紹了Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

目錄

一、緩存通識

1.1??處不在的緩存

1.2?多級緩存 (重點(diǎn))

二、Redis簡介

2.1 什么是Redis

2.2 Redis的應(yīng)用場景

三、Redis數(shù)據(jù)存儲的細(xì)節(jié)

3.1?Redis數(shù)據(jù)類型

3.2?內(nèi)存結(jié)構(gòu)

3.3?內(nèi)存分配器

3.4?redisObject

3.4.1?type

3.4.2?encoding

3.4.3 ptr

3.4.4?refcount

3.4.5?lru

3.4.6??結(jié)

3.5?SDS

3.5.1?SDS內(nèi)存結(jié)構(gòu)

?3.5.2?SDS與C字符串的?較

四、Redis的對象類型與內(nèi)存編碼

4.1 字符串?

4.1.1?概況

4.1.2?內(nèi)部編碼

4.2?列表

4.2.1?概況

4.2.2?內(nèi)部編碼

4.3?哈希

4.3.1?概況

4.3.2?內(nèi)部編碼

4.3.3 編碼轉(zhuǎn)換

4.4?集合

4.4.1?概況?

4.4.2?內(nèi)部編碼

4.4.3?編碼轉(zhuǎn)換

4.5?有序集合

4.5.1?概況

4.5.2?內(nèi)部編碼

4.5.3 編碼轉(zhuǎn)換

4.5.4?跳躍表

五、Redis 設(shè)計優(yōu)化

5.1?估算Redis內(nèi)存使?量

5.2?優(yōu)化內(nèi)存占用

5.2.1?利用jemalloc特性進(jìn)行優(yōu)化

5.2.2?使用整型/長整型

5.2.3?共享對象

5.2.4?縮短鍵值對的存儲長度

六、Reids 內(nèi)存?量統(tǒng)計

6.1?used_memory

6.2?used_memory_rss

6.3?mem_fragmentation_ratio

6.4?mem_allocator

七、Redis內(nèi)存劃分

7.1?數(shù)據(jù)內(nèi)存

7.2?進(jìn)程內(nèi)存

7.3?緩沖內(nèi)存

7.4?內(nèi)存碎?


一、緩存通識

緩存:存儲在計算機(jī)上的?個原始數(shù)據(jù)復(fù)制集,以便于訪問。?

緩存是介于數(shù)據(jù)訪問者和數(shù)據(jù)源之間的?種?速存儲,當(dāng)數(shù)據(jù)需要多次讀取的時候,?于加快讀取的速 度。

緩存(Cache) 和 緩沖(Buffer) 的分別??

緩存:?般是為了數(shù)據(jù)多次讀取。

緩沖:?如CPU要把數(shù)據(jù)先硬盤,因為硬盤?較慢,先到緩沖設(shè)備Buffer,?如內(nèi)存,Buffer讀和寫都需要。

1.1??處不在的緩存

CPU?緩存

操作系統(tǒng)緩存

數(shù)據(jù)庫緩存

JVM?編譯緩存

CDN?緩存

代理與反向代理緩存

前端緩存

應(yīng)?程序緩存

分布式對象緩存

1.2?多級緩存 (重點(diǎn))

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

二、Redis簡介

2.1 什么是Redis

Redis是?C語?開發(fā)的?個開源的?性能鍵值對(key-value)的NoSQL數(shù)據(jù)庫。它通過提供多種鍵值數(shù)據(jù)類型來適應(yīng)不同場景下的存儲需求。

Redis作為?個單線程的應(yīng)?,為什么處理請求性能如此NB?IO多路復(fù)??

NoSQL,泛指?關(guān)系型的數(shù)據(jù)庫,NoSQL即Not-Only SQL,它可以作為關(guān)系型數(shù)據(jù)庫的良好補(bǔ)充。

2.2 Redis的應(yīng)用場景

緩存(數(shù)據(jù)查詢、短連接、新聞內(nèi)容、商品內(nèi)容等等)。(最多使?)

分布式集群架構(gòu)中的session分離。

聊天室的在線好友列表。

任務(wù)隊列。(秒殺、搶購、12306等等)

應(yīng)?排?榜。

?站訪問統(tǒng)計。

數(shù)據(jù)過期處理(可以精確到毫秒)

三、Redis數(shù)據(jù)存儲的細(xì)節(jié)

3.1?Redis數(shù)據(jù)類型

Redis整體上是?個KV結(jié)構(gòu),但是它的Value?可以分?以下五種數(shù)據(jù)類型。

?前為?Redis?持的鍵值數(shù)據(jù)類型如下:

字符串類型(string) ? ? ? ? ? ? ? ? ? ?set key value

散列類型(hash) ? ? ? ? ? ? ? ? ? ? ? ? hset key field value

列表類型(list) ? ? ? ? ? ? ? ? ? ? ? ? ? ?lpush key a b c d

集合類型(set) ? ? ? ? ? ? ? ? ? ? ? ? ? ?sadd key a b c d

有序集合類型(zset/sortedset) zadd key a score b score

3.2?內(nèi)存結(jié)構(gòu)

下圖是執(zhí)?set hello world時,所涉及到的數(shù)據(jù)模型。

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

1.?dictEntry:Redis是Key-Value數(shù)據(jù)庫,因此對每個鍵值對都會有?個dictEntry,??存儲了指向Key和Value的指針;next指向下?個dictEntry,與本Key-Value?關(guān)。

2.?Key:圖中右上?可?,Key(”hello”)并不是直接以字符串存儲,?是存儲在SDS結(jié)構(gòu)中。

3.?redisObject:Value(“world”)既不是直接以字符串存儲,也不是像Key?樣直接存儲在SDS中,?是存儲在redisObject中。實際上,不論Value是5種類型的哪?種,都是通過redisObject來存儲的;?redisObject中的type字段指明了Value對象的類型,ptr字段則指向?qū)ο笏诘牡刂贰2贿^可以看出,字符串對象雖然經(jīng)過了redisObject的包裝,但仍然需要通過SDS存儲。實際上,redisObject除了type和ptr字段以外,還有其他字段圖中沒有給出,如?于指定對象內(nèi)部編碼的字段;后?會詳細(xì)介紹。

4.?jemalloc:?論是DictEntry對象,還是redisObject、SDS對象,都需要內(nèi)存分配器(如

jemalloc)分配內(nèi)存進(jìn)?存儲。

3.3?內(nèi)存分配器

Redis在編譯時便會指定內(nèi)存分配器;內(nèi)存分配器可以是?libc?、jemalloc或者tcmalloc,默認(rèn)是jemalloc。

jemalloc作為Redis的默認(rèn)內(nèi)存分配器,在減?內(nèi)存碎???做的相對?較好。jemalloc在64位系統(tǒng)中,將內(nèi)存空間劃分為?、?、巨?三個范圍;每個范圍內(nèi)?劃分了許多?的內(nèi)存塊單位;當(dāng)Redis存儲數(shù)據(jù)時,會選擇??最合適的內(nèi)存塊進(jìn)?存儲。

在?jemalloc?類?過來的物流系統(tǒng)中,同城倉庫相當(dāng)于?tcache ——?線程獨(dú)有的內(nèi)存?zhèn)}庫;區(qū)域倉庫相當(dāng)于?arena ——??個線程共享的內(nèi)存?zhèn)}庫;全國倉庫相當(dāng)于全局變量指向的內(nèi)存?zhèn)}庫,為所有線程可?。

在?jemalloc?中,整塊批發(fā)內(nèi)存,之后或拆開零售,或整塊出售。整塊批發(fā)的內(nèi)存叫做?chunk,對于?件和?件訂單,則進(jìn)?步拆成?run。Chunk?的??為?4MB(可調(diào))或其倍數(shù),且為?4MB?對?;??run???為???的整數(shù)倍。

在?jemalloc?中,?件訂單叫做?small allocation,范圍?概是?1-57344?字節(jié)。并將此區(qū)間分成?44?檔,每次?分配請求歸整到某檔上。例如,?于8字節(jié)的,?律分配?8?字節(jié)空間;17-32分配請求,?律分配32?字節(jié)空間。

對于上述?44?檔,有對應(yīng)的?44?種?runs。每種?run?專?提供此檔分配的內(nèi)存塊(叫做?region)。

?件訂單叫做?large allocation,范圍?概是?57345-4MB不到?點(diǎn)的樣?,所有?件分配歸整到???。

jemalloc劃分的內(nèi)存單元如下圖所示:

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

?例如,如果需要存儲??為130字節(jié)的對象,jemalloc會將其放?160字節(jié)的內(nèi)存單元中。

3.4?redisObject

Redis對象有5種類型;?論是哪種類型,Redis都不會直接存儲,?是通過redisObject對象進(jìn)?存儲。

redisObject對象?常重要,Redis對象的類型、內(nèi)部編碼、內(nèi)存回收、共享對象等功能,都需要redisObject?持,下?將通過redisObject的結(jié)構(gòu)來說明它是如何起作?的。

Redis中的每個對象都是由如下結(jié)構(gòu)表示(列出了與保存數(shù)據(jù)有關(guān)的三個屬性)

{
 unsigned type:4;//類型 五種對象類型
 unsigned encoding:4;//編碼
 void *ptr;//指向底層實現(xiàn)數(shù)據(jù)結(jié)構(gòu)的指針
 //...
 int refcount;//引?計數(shù)
 //...
 unsigned lru:24;//記錄最后?次被命令程序訪問的時間
 //...
}robj;

3.4.1?type

type字段表示對象的類型,占4個?特;?前包括REDIS_STRING(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。當(dāng)我們執(zhí)?type命令時,便是通過讀取RedisObject的type字段獲得對象的類型;如下圖所示:

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型3.4.2?encoding

encoding表示對象的內(nèi)部編碼,占4個?特。

對于Redis?持的每種類型,都有?少兩種內(nèi)部編碼,例如對于字符串,有int、embstr、raw三種編碼。通過encoding屬性,Redis可以根據(jù)不同的使?場景來為對象設(shè)置不同的編碼,??提?了Redis的靈活性和效率。以列表對象為例,有壓縮列表和雙端鏈表兩種編碼?式;如果列表中的元素較少,Redis傾向于使?壓縮列表進(jìn)?存儲,因為壓縮列表占?內(nèi)存更少,?且?雙端鏈表可以更快載?;當(dāng)列表對象元素較多時,壓縮列表就會轉(zhuǎn)化為更適合存儲?量元素的雙端鏈表。

通過object encoding命令,可以查看對象采?的編碼?式,如下圖所示:

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

5種對象類型對應(yīng)的編碼?式以及使?條件,將在后?介紹。?

3.4.3 ptr

ptr指針指向具體的數(shù)據(jù),如前?的例?中,set hello world,ptr指向包含字符串world的SDS。

3.4.4?refcount

refcount與共享對象

refcount記錄的是該對象被引?的次數(shù),類型為整型。refcount的作?,主要在于對象的引?計數(shù)和內(nèi)存回收。

當(dāng)創(chuàng)建新對象時,refcount初始化為1;當(dāng)有新程序使?該對象時,refcount加1;當(dāng)對象不再被?個新程序使?時,refcount減1;當(dāng)refcount變?yōu)?時,對象占?的內(nèi)存會被釋放。

共享對象的具體實現(xiàn)

Redis的共享對象?前只?持整數(shù)值的字符串對象。之所以如此,實際上是對內(nèi)存和CPU(時間)的平衡:共享對象雖然會降低內(nèi)存消耗,但是判斷兩個對象是否相等卻需要消耗額外的時間。對于整數(shù)值,判斷操作復(fù)雜度為O(1);對于普通字符串,判斷復(fù)雜度O(n);?對于哈希、列表、集合和有序集合,判斷的復(fù)雜度為O(n^2)。雖然共享對象只能是整數(shù)值的字符串對象,但是5種類型都可能使?共享對象(如哈希、列表等的元素可以使?)。

共享對象池

共享對象池是指Redis內(nèi)部維護(hù)[0-9999]的整數(shù)對象池。

創(chuàng)建?量的整數(shù)類型redisObject存在內(nèi)存開銷,每個redisObject內(nèi)部結(jié)構(gòu)?少占16字節(jié),甚?超過了整數(shù)?身空間消耗。

所以Redis內(nèi)存維護(hù)?個[0-9999]的整數(shù)對象池,?于節(jié)約內(nèi)存。

除了整數(shù)值對象,其他類型如list、hash、set、zset內(nèi)部元素也可以使?整數(shù)對象池。

因此開發(fā)中在滿?需求的前提下,盡量使?整數(shù)對象以節(jié)省內(nèi)存。

就?前的實現(xiàn)來說,Redis服務(wù)器在初始化時,會創(chuàng)建10000個字符串對象,值分別是0~9999的整數(shù)值;當(dāng)Redis需要使?值為0~9999的字符串對象時,可以直接使?這些共享對象。10000這個數(shù)字定義在源碼的?OBJ_SHARED_INTEGERS?常量中定義。共享對象的引?次數(shù)可以通過object refcount命令查看,如下圖所示。命令執(zhí)?的結(jié)果?佐證了只有0~9999之間的整數(shù)會作為共享對象。

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

3.4.5?lru

?lru記錄的是對象最后?次被命令程序訪問的時間,占據(jù)的?特數(shù)不同的版本有所不同(2.6版本占22?特,4.0版本占24?特)。

通過對?lru時間與當(dāng)前時間,可以計算某個對象的閑置時間;object idletime命令可以顯示該閑置時間(單位是秒)。object idletime命令的?個特殊之處在于它不改變對象的lru值。

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

lru值除了通過object idletime命令打印之外,還與Redis的內(nèi)存回收有關(guān)系:如果Redis打開了maxmemory選項,且內(nèi)存回收算法選擇的是volatile-lru或allkeys—lru,那么當(dāng)Redis內(nèi)存占?超過maxmemory指定的值時,Redis會優(yōu)先選擇空轉(zhuǎn)時間最?的對象進(jìn)?釋放。

3.4.6??結(jié)

綜上所述,redisObject的結(jié)構(gòu)與對象類型、編碼、內(nèi)存回收、共享對象都有關(guān)系;?個redisObject對象的??為16字節(jié):

4bit(類型)+4bit(編碼)+24bit(lru)+4Byte(refcount)+8Byte(指針)=16Byte

3.5?SDS

3.5.1?SDS內(nèi)存結(jié)構(gòu)

Redis沒有直接使?C字符串(即以空字符’\0’結(jié)尾的字符數(shù)組)作為默認(rèn)的字符串表示,?是使?了SDS。SDS是簡單動態(tài)字符串(Simple Dynamic String)的縮寫。

(1) 3.2之前

struct sdshdr{
 //記錄buf數(shù)組中已使?字節(jié)的數(shù)量
 //等于 SDS 保存字符串的?度
 int len;
 //記錄 buf 數(shù)組中未使?字節(jié)的數(shù)量
 int free;
 //字節(jié)數(shù)組,?于保存字符串
 char buf[];
}

其中,buf表示字節(jié)數(shù)組,?來存儲字符串;len表示buf已使?的?度,free表示buf未使?的?度。下?是兩個例?。

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

通過SDS的結(jié)構(gòu)可以看出,[buf數(shù)組的?度=free+len+1(其中1表示字符串結(jié)尾的空字符);所以,?個SDS結(jié)構(gòu)占據(jù)的空間為:free所占?度+len所占?度+ buf數(shù)組的?度+1=4+4+字符串?度+1=字符串?度+9。

(2) 3.2之后?

typedef char *sds; 
struct __attribute__ ((__packed__)) sdshdr5 { // 對應(yīng)的字符串?度?于 1<<5 32字 節(jié)
 unsigned char flags; /* 3 lsb of type, and 5 msb of string length int
embstr*/
 char buf[];
};
struct __attribute__ ((__packed__)) sdshdr8 { // 對應(yīng)的字符串?度?于 1<<8 256
 uint8_t len; /* used */ //?前字符創(chuàng)的?度 ?1字節(jié)存儲
 uint8_t alloc; //已經(jīng)分配的總?度 ?1字節(jié)存儲
 unsigned char flags; //flag?3bit來標(biāo)明類型,類型后續(xù)
解釋,其余5bit?前沒有使? embstr raw
 char buf[]; //柔性數(shù)組,以'\0'結(jié)尾
};
struct __attribute__ ((__packed__)) sdshdr16 { // 對應(yīng)的字符串?度?于 1<<16
 uint16_t len; /*已使??度,?2字節(jié)存儲*/
 uint16_t alloc; /* 總?度,?2字節(jié)存儲*/
 unsigned char flags; /* 3 lsb of type, 5 unused bits */
 char buf[];
};
struct __attribute__ ((__packed__)) sdshdr32 { // 對應(yīng)的字符串?度?于 1<<32
 uint32_t len; /*已使??度,?4字節(jié)存儲*/
 uint32_t alloc; /* 總?度,?4字節(jié)存儲*/
 unsigned char flags;/* 低3位存儲類型, ?5位預(yù)留 */
 char buf[];/*柔性數(shù)組,存放實際內(nèi)容*/
};
struct __attribute__ ((__packed__)) sdshdr64 { // 對應(yīng)的字符串?度?于 1<<64
 uint64_t len; /*已使??度,?8字節(jié)存儲*/
 uint64_t alloc; /* 總?度,?8字節(jié)存儲*/
 unsigned char flags; /* 低3位存儲類型, ?5位預(yù)留 */
 char buf[];/*柔性數(shù)組,存放實際內(nèi)容*/
};

flag屬性保存的是當(dāng)前使?的SDS類型:

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

?3.5.2?SDS與C字符串的?較

獲取字符串?度:SDS是O(1),C字符串是O(n)

緩沖區(qū)溢出:使?C字符串的API時,如果字符串?度增加(如strcat操作)?忘記重新分配內(nèi)存,很容易造成緩沖區(qū)的溢出;?SDS由于記錄了?度,相應(yīng)的API在可能造成緩沖區(qū)溢出時會?動重新分配內(nèi)存,杜絕了緩沖區(qū)溢出。

修改字符串時內(nèi)存的重分配:對于C字符串,如果要修改字符串,必須要重新分配內(nèi)存(先釋放再申請),因為如果沒有重新分配,字符串?度增?時會造成內(nèi)存緩沖區(qū)溢出,字符串?度減?時會造成內(nèi)存泄露。?對于SDS,由于可以記錄len和free,因此解除了字符串?度和空間數(shù)組?度之間的關(guān)聯(lián),可以在此基礎(chǔ)上進(jìn)?優(yōu)化:空間預(yù)分配策略(即分配內(nèi)存時?實際需要的多)使得字符串?度增?時重新分配內(nèi)存的概率??減?;惰性空間釋放策略使得字符串?度減?時重新分配內(nèi)存的概率??減?。

存取?進(jìn)制數(shù)據(jù):SDS可以,C字符串不可以。因為C字符串以空字符作為字符串結(jié)束的標(biāo)識,?對于?些?進(jìn)制?件(如圖?等),內(nèi)容可能包括空字符串,因此C字符串?法正確存?。?SDS以字符串?度len來作為字符串結(jié)束標(biāo)識,因此沒有這個問題。

四、Redis的對象類型與內(nèi)存編碼

Redis?持5種對象類型,?每種結(jié)構(gòu)都有?少兩種編碼;

這樣做的好處在于:

???接?與實現(xiàn)分離,當(dāng)需要增加或改變內(nèi)部編碼時,?戶使?不受影響;

另???可以根據(jù)不同的應(yīng)?場景切換內(nèi)部編碼,提?效率。

Redis各種對象類型?持的內(nèi)部編碼如下圖所示(只列出重點(diǎn)的):

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

4.1 字符串?

4.1.1?概況

字符串是最基礎(chǔ)的類型,因為所有的鍵都是字符串類型,且字符串之外的其他?種復(fù)雜類型的元素也是字符串。字符串?度不能超過512MB。

4.1.2?內(nèi)部編碼

字符串類型的內(nèi)部編碼有3種,它們的應(yīng)?場景如下:

int:8個字節(jié)的?整型。字符串值是整型時,這個值使?long整型表示。

embstr:<=44字節(jié)的字符串。embstr與raw都使?redisObject和sds保存數(shù)據(jù),區(qū)別在于,embstr的使?只分配?次內(nèi)存空間(因此redisObject和sds是連續(xù)的),?raw需要分配兩次內(nèi)存空間(分別為redisObject和sds分配空間)。因此與raw相?,embstr的好處在于創(chuàng)建時少分配?次空間,刪除時少釋放?次空間,以及對象的所有數(shù)據(jù)連在?起,尋找?便。?embstr的壞處也很明顯,如果字符串的?度增加需要重新分配內(nèi)存時,整個redisObject和sds都需要重新分配空間,因此redis中的embstr實現(xiàn)為只讀。

raw:?于44個字節(jié)的字符串

3.2之后?embstr和raw進(jìn)?區(qū)分的?度,是44;是因為redisObject的?度是16字節(jié),sds的?度是4+字符串?度;因此當(dāng)字符串?度是44時,embstr的?度正好是16+4+44 =64,jemalloc正好可以分配64字節(jié)的內(nèi)存單元。

3.2?之前embstr和raw進(jìn)?區(qū)分的?度,是39,因為redisObject的?度是16字節(jié),sds的?度是9+字符串?度;因此當(dāng)字符串?度是39時,embstr的?度正好是16+9+39 =64,jemalloc正好可以分配64字節(jié)的內(nèi)存單元。

4.2?列表

4.2.1?概況

列表(list)?來存儲多個有序的字符串,每個字符串稱為元素;

?個列表可以存儲2^32-1個元素。

Redis中的列表?持兩端插?和彈出,并可以獲得指定位置(或范圍)的元素,可以充當(dāng)數(shù)組、隊列、棧等。

4.2.2?內(nèi)部編碼

Redis3.0之前列表的內(nèi)部編碼可以是壓縮列表(ziplist)或雙端鏈表(linkedlist)。選擇的折中?案是兩種數(shù)據(jù)類型的轉(zhuǎn)換,但是在3.2版本之后 因為轉(zhuǎn)換也是個費(fèi)時且復(fù)雜的操作,引?了?種新的數(shù)據(jù)格式,結(jié)合了雙向列表linkedlist和ziplist的特點(diǎn),稱之為quicklist。所有的節(jié)點(diǎn)都?quicklist存儲,省去了到臨界條件是的格式轉(zhuǎn)換。

(1)?壓縮列表

當(dāng)?個列表只包含少量列表項時,并且每個列表項時?整數(shù)值或短字符串,那么Redis會使?壓縮列表來做該列表的底層實現(xiàn)。

壓縮列表(ziplist)是Redis為了節(jié)省內(nèi)存?開發(fā)的,是由?系列特殊編碼的連續(xù)內(nèi)存塊組成的順序型數(shù)據(jù)結(jié)構(gòu),?個壓縮列表可以包含任意多個節(jié)點(diǎn)(entry),每個節(jié)點(diǎn)可以保存?個字節(jié)數(shù)組或者?個整數(shù)值。放到?個連續(xù)內(nèi)存區(qū)?

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

previous_entry_length: 記錄壓縮列表前?個字節(jié)的?度。
encoding:節(jié)點(diǎn)的encoding保存的是節(jié)點(diǎn)的content的內(nèi)容類型
content:content區(qū)域?于保存節(jié)點(diǎn)的內(nèi)容,節(jié)點(diǎn)內(nèi)容類型和?度由encoding決定。

(2) 雙向鏈表?

雙向鏈表(linkedlist):由?個list結(jié)構(gòu)和多個listNode結(jié)構(gòu)組成;

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

通過圖中可以看出,雙端鏈表同時保存了表頭指針和表尾指針,并且每個節(jié)點(diǎn)都有指向前和指向后的指針;鏈表中保存了列表的?度;dup、free和match為節(jié)點(diǎn)值設(shè)置類型特定函數(shù),所以鏈表可以?于保存各種不同類型的值。?鏈表中每個節(jié)點(diǎn)指向的是type為字符串的redisObject。

(3)?快速列表

????????簡單的說,我們?nèi)耘f可以將其看作?個雙向列表,但是列表的每個節(jié)點(diǎn)都是?個ziplist,其實就是linkedlist和ziplist的結(jié)合。quicklist中的每個節(jié)點(diǎn)ziplist都能夠存儲多個數(shù)據(jù)元素。

Redis3.2開始,列表采?quicklist進(jìn)?編碼。

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

//32byte 的空間
typedef struct quicklist {
     // 指向quicklist的頭部
    quicklistNode *head; 
     // 指向quicklist的尾部
     quicklistNode *tail;
     // 列表中所有數(shù)據(jù)項的個數(shù)總和
     unsigned long count; 
     // quicklist節(jié)點(diǎn)的個數(shù),即ziplist的個數(shù)
     unsigned int len;
     // ziplist??限定,由list-max-ziplist-size給定
     // 表示不?整個int存儲fill,?是只?了其中的16位來存儲
     int fill : 16; 
     // 節(jié)點(diǎn)壓縮深度設(shè)置,由list-compress-depth給定
     unsigned int compress : 16;
} quicklist;
typedef struct quicklistNode {
     struct quicklistNode *prev; // 指向上?個ziplist節(jié)點(diǎn)
     struct quicklistNode *next; // 指向下?個ziplist節(jié)點(diǎn)
     unsigned char *zl; // 數(shù)據(jù)指針,如果沒有被壓縮,就指向ziplist結(jié)構(gòu),反之指向    quicklistLZF結(jié)構(gòu)
     unsigned int sz; // 表示指向ziplist結(jié)構(gòu)的總?度(內(nèi)存占??度)
     unsigned int count : 16; // 表示ziplist中的數(shù)據(jù)項個數(shù)
     unsigned int encoding : 2; // 編碼?式,1--ziplist,2--quicklistLZF
     unsigned int container : 2; // 預(yù)留字段,存放數(shù)據(jù)的?式,1--NONE,2--ziplist
     unsigned int recompress : 1; // 解壓標(biāo)記,當(dāng)查看?個被壓縮的數(shù)據(jù)時,需要暫時解壓,標(biāo)記此參數(shù)為1,之后再重新進(jìn)?壓縮
     unsigned int attempted_compress : 1; // 測試相關(guān)
     unsigned int extra : 10; // 擴(kuò)展字段,暫時沒?
} quicklistNode;

4.3?哈希

4.3.1?概況

哈希(作為?種數(shù)據(jù)結(jié)構(gòu)),不僅是Redis對外提供的5種對象類型的?種(與字符串、列表、集合、有序結(jié)合并列),也是Redis作為Key-Value數(shù)據(jù)庫所使?的數(shù)據(jù)結(jié)構(gòu)。為了說明的?便,后?當(dāng)使?“內(nèi)層的哈希”時,代表的是Redis對外提供的5種對象類型的?種;使?“外層的哈希”代指Redis作為Key-Value數(shù)據(jù)庫所使?的數(shù)據(jù)結(jié)構(gòu)。

4.3.2?內(nèi)部編碼

內(nèi)層的哈希使?的內(nèi)部編碼可以是壓縮列表(ziplist)和哈希表(hashtable)兩種;Redis的外層的哈希則只使?了hashtable

壓縮列表前?已介紹。與哈希表相?,壓縮列表?于元素個數(shù)少、元素?度?的場景;其優(yōu)勢在于集中存儲,節(jié)省空間;同時,雖然對于元素的操作復(fù)雜度也由O(1)變?yōu)榱薕(n),但由于哈希中元素數(shù)量較少,因此操作的時間并沒有明顯劣勢。

hashtable:?個hashtable由1個dict結(jié)構(gòu)、2個dictht結(jié)構(gòu)、1個dictEntry指針數(shù)組(稱為bucket)和多個dictEntry結(jié)構(gòu)組成。

正常情況下(即hashtable沒有進(jìn)?rehash時)各部分關(guān)系如下圖所示:

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

?(1)?dict

?般來說,通過使?dictht和dictEntry結(jié)構(gòu),便可以實現(xiàn)普通哈希表的功能;但是Redis的實現(xiàn)中,在dictht結(jié)構(gòu)的上層,還有?個dict結(jié)構(gòu)。下?說明dict結(jié)構(gòu)的定義及作?。

dict結(jié)構(gòu)如下:

typedef struct dict{ 
 dictType *type; // type??主要記錄了?系列的函數(shù),可以說是規(guī)定了?系列的接? 
 void *privdata; // privdata保存了需要傳遞給那些類型特定函數(shù)的可選參數(shù) 
 //兩張哈希表
 dictht ht[2];//便于漸進(jìn)式rehash 
 int trehashidx; //rehash 索引,并沒有rehash時,值為 -1
 //?前正在運(yùn)?的安全迭代器的數(shù)量
 int iterators;
} dict;

其中,type屬性和privdata屬性是為了適應(yīng)不同類型的鍵值對,?于創(chuàng)建多態(tài)字典

ht屬性和trehashidx屬性則?于rehash,即當(dāng)哈希表需要擴(kuò)展或收縮時使?。ht是?個包含兩個項的數(shù)組,每項都指向?個dictht結(jié)構(gòu),這也是Redis的哈希會有1個dict、2個dictht結(jié)構(gòu)的原因。通常情況下,所有的數(shù)據(jù)都是存在放dict的ht[0]中,ht[1]只在rehash的時候使?。dict進(jìn)?rehash操作的時候,將ht[0]中的所有數(shù)據(jù)rehash到ht[1]中。然后將ht[1]賦值給ht[0],并清空ht[1]。

因此,Redis中的哈希之所以在dictht和dictEntry結(jié)構(gòu)之外還有?個dict結(jié)構(gòu),???是為了適應(yīng)不同類型的鍵值對,另???是為了rehash。

(2)?dictht

dictht結(jié)構(gòu)如下:

typedef struct dictht{ 
     //哈希表數(shù)組,每個元素都是?條鏈表
     dictEntry **table; 
     //哈希表??
     unsigned long size;
     // 哈希表??掩碼,?于計算索引值
     // 總是等于 size - 1
     unsigned long sizemask; 
     // 該哈希表已有節(jié)點(diǎn)的數(shù)量
     unsigned long used; 
}dictht;

其中,各個屬性的功能說明如下:

table屬性是?個指針,指向bucket;

size屬性記錄了哈希表的??,即bucket的??;

used記錄了已使?的dictEntry的數(shù)量;

sizemask屬性的值總是為size-1,這個屬性和哈希值?起決定?個鍵在table中存儲的位置。

(3)?bucket

bucket是?個數(shù)組,數(shù)組的每個元素都是指向dictEntry結(jié)構(gòu)的指針。Redis中bucket數(shù)組的??計算規(guī)則如下:?于dictEntry的、最?的2^n;

例如,如果有1000個dictEntry,那么bucket??為1024;如果有1500個dictEntry,則bucket??為2048。n%32 = n&(32-1)

(4)?dictEntry

dictEntry結(jié)構(gòu)?于保存鍵值對,結(jié)構(gòu)定義如下:

// 鍵
typedef struct dictEntry{ 
     void *key; 
     union{ //值v的類型可以是以下三種類型 
         void *val; 
         uint64_tu64; 
         int64_ts64; 
     }v; 
     // 指向下個哈希表節(jié)點(diǎn),形成鏈表
     struct dictEntry *next; 
}dictEntry;

其中,各個屬性的功能如下:

key:鍵值對中的鍵

val:鍵值對中的值,使?union(即共?體)實現(xiàn),存儲的內(nèi)容既可能是?個指向值的指針,也可能是64位整型,或?符號64位整型;

next:指向下?個dictEntry,?于解決哈希沖突問題

在64位系統(tǒng)中,?個dictEntry對象占24字節(jié)(key/val/next各占8字節(jié))。

4.3.3 編碼轉(zhuǎn)換

如前所述,Redis中內(nèi)層的哈希既可能使?哈希表,也可能使?壓縮列表。

只有同時滿?下?兩個條件時,才會使?壓縮列表:

哈希中元素數(shù)量?于512個;

哈希中所有鍵值對的鍵和值字符串?度都?于64字節(jié)

下圖展示了Redis內(nèi)層的哈希編碼轉(zhuǎn)換的特點(diǎn):

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

4.4?集合

4.4.1?概況?

集合(set)與列表類似,都是?來保存多個字符串,但集合與列表有兩點(diǎn)不同:集合中的元素是?序的,因此不能通過索引來操作元素;集合中的元素不能有重復(fù)。

?個集合中最多可以存儲2^32-1個元素;除了?持常規(guī)的增刪改查,Redis還?持多個集合取交集、并集、差集。

4.4.2?內(nèi)部編碼

集合的內(nèi)部編碼可以是整數(shù)集合(intset)或哈希表(hashtable)。

哈希表前?已經(jīng)講過,這?略過不提;需要注意的是,集合在使?哈希表時,值全部被置為null。

整數(shù)集合的結(jié)構(gòu)定義如下:?

typedef struct intset{ 
     uint32_t encoding; // 編碼?式
     uint32_t length; // 集合包含的元素數(shù)量
     int8_t contents[]; // 保存元素的數(shù)組
} intset;

其中,encoding代表contents中存儲內(nèi)容的類型,雖然contents(存儲集合中的元素)是int8_t類型,但實際上其存儲的值是int16_t、int32_t或int64_t,具體的類型便是由encoding決定的;

length表示元素個數(shù)。

整數(shù)集合適?于集合所有元素都是整數(shù)且集合元素數(shù)量較?的時候,與哈希表相?,整數(shù)集合的優(yōu)勢在于集中存儲,節(jié)省空間;同時,雖然對于元素的操作復(fù)雜度也由O(1)變?yōu)榱薕(n),但由于集合數(shù)量較少,因此操作的時間并沒有明顯劣勢。

4.4.3?編碼轉(zhuǎn)換

只有同時滿?下?兩個條件時,集合才會使?整數(shù)集合:

集合中元素數(shù)量?于512個;

集合中所有元素都是整數(shù)值。

如果有?個條件不滿?,則使?哈希表;且編碼只可能由整數(shù)集合轉(zhuǎn)化為哈希表,反?向則不可能。下圖展示了集合編碼轉(zhuǎn)換的特點(diǎn):

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

4.5?有序集合

4.5.1?概況

有序集合與集合?樣,元素都不能重復(fù);但與集合不同的是,有序集合中的元素是有順序的。與列表使?索引下標(biāo)作為排序依據(jù)不同,有序集合為每個元素設(shè)置?個分?jǐn)?shù)(score)作為排序依據(jù)。

4.5.2?內(nèi)部編碼

有序集合的內(nèi)部編碼可以是壓縮列表(ziplist)或跳躍表(skiplist)。ziplist在列表和哈希中都有使?,前?已經(jīng)講過,這?略過不提。

跳躍表是?種有序數(shù)據(jù)結(jié)構(gòu),通過在每個節(jié)點(diǎn)中維持多個指向其他節(jié)點(diǎn)的指針,從?達(dá)到快速訪問節(jié)點(diǎn)的?的。除了跳躍表,實現(xiàn)有序數(shù)據(jù)結(jié)構(gòu)的另?種典型實現(xiàn)是平衡樹;?多數(shù)情況下,跳躍表的效率可以和平衡樹媲美,且跳躍表實現(xiàn)?平衡樹簡單很多,因此redis中選?跳躍表代替平衡樹。跳躍表?持平均O(logN)、最壞O(N)的復(fù)雜點(diǎn)進(jìn)?節(jié)點(diǎn)查找,并?持順序操作。?

4.5.3 編碼轉(zhuǎn)換

Redis的跳躍表實現(xiàn)由zskiplist和zskiplistNode兩個結(jié)構(gòu)組成:前者?于保存跳躍表信息(如頭結(jié)點(diǎn)、尾節(jié)點(diǎn)、?度等),后者?于表示跳躍表節(jié)點(diǎn)。

只有同時滿?下?兩個條件時,才會使?壓縮列表:

1)有序集合中元素數(shù)量?于128個;

2)有序集合中所有成員?度都不?64字節(jié)。

如果有?個條件不滿?,則使?跳躍表;且編碼只可能由壓縮列表轉(zhuǎn)化為跳躍表,反?向則不可能。

下圖展示了有序集合編碼轉(zhuǎn)換的特點(diǎn):?Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

4.5.4?跳躍表

(1)?數(shù)據(jù)結(jié)構(gòu)定義

有許多數(shù)據(jù)結(jié)構(gòu)的定義其實是按照(結(jié)點(diǎn)+組織?式)來的,結(jié)點(diǎn)就是?個數(shù)據(jù)點(diǎn),組織?式就是把結(jié)點(diǎn)組織起來形成數(shù)據(jù)結(jié)構(gòu),?如?雙端鏈表?(ListNode+list)、字(dictEntry+dictht+dict)等,今天所說的SkipList其實也?樣,我們?先看下它的?結(jié)點(diǎn)?定義:

typedef struct zskiplistNode { 
     sds ele; //數(shù)據(jù)域
     double score; //分值
     struct zskiplistNode *backward; //后向指針,使得跳表第?層組織為雙向鏈表
     struct zskiplistLevel { //每?個結(jié)點(diǎn)的層級
         struct zskiplistNode *forward; //某?層的前向結(jié)點(diǎn)
         unsigned int span; //某?層距離下?個結(jié)點(diǎn)的跨度
     } level[]; //level本身是?個柔性數(shù)組,最?值為32,由ZSKIPLIST_MAXLEVEL 定義
} zskiplistNode;
接下來是組織?式,即使?上?的 zskiplistNode 組織起?個 SkipList
typedef struct zskiplist {
     struct zskiplistNode *header; //頭部
     struct zskiplistNode *tail; //尾部
     unsigned long length; //?度,即?共有多少個元素
     int level; //最?層級,即跳表?前的最?層級
} zskiplist;
核?的數(shù)據(jù)結(jié)構(gòu)就是上?兩個。

(2) 具體圖表示

普通單向鏈表:

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

?跳躍表(跳表):

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

?(3)?查詢

查找?個節(jié)點(diǎn)時,我們只需從?層到低層,?個個鏈表查找,每次找到該層鏈表中?于等于?標(biāo)節(jié)點(diǎn)的 最?節(jié)點(diǎn),直到找到為?。由于?層的鏈表迭代時會“ 跳過 低層的部分節(jié)點(diǎn),所以跳躍表會?正常的鏈 表查找少查部分節(jié)點(diǎn),這也是skiplist 名字的由來。
例如:
查找 46 55---21---55--37--55--46

(4)?插?

L1
概率算法
在此還是以上圖為例:跳躍表的初試狀態(tài)如下圖,表中沒有?個元素:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

如果我們要插?元素2,?先是在底部插?元素2,如下圖:

? ? ? ? ? ? ? ? ? ? ?Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

?然后我們拋硬幣,結(jié)果是正?,那么我們要將2插?到L2層,如下圖:

? ? ? ? ? ? ? ? ? ??Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

繼續(xù)拋硬幣,結(jié)果是反?,那么元素 2 的插?操作就停?了,插?后的表結(jié)構(gòu)就是上圖所示。接下來,我 們插?元素33 ,跟元素 2 的插??樣,現(xiàn)在 L1 層插? 33 ,如下圖:

? ?? ? ? ??Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

然后拋硬幣,結(jié)果是反?,那么元素 33 的插?操作就結(jié)束了,插?后的表結(jié)構(gòu)就是上圖所示。接下來, 我們插?元素55 ,?先在 L1 插? 55 ,插?后如下圖:

? ??Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型然后拋硬幣,結(jié)果是正?,那么L2層需要插?55,如下圖:

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型?繼續(xù)拋硬幣,結(jié)果?是正?,那么L3層需要插?55,如下圖:

?Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

?繼續(xù)拋硬幣,結(jié)果?是正?,那么要在L4插?55,結(jié)果如下圖:

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

?繼續(xù)拋硬幣,結(jié)果是反?,那么55的插?結(jié)束,表結(jié)構(gòu)就如上圖所示。

以此類推,我們插?剩余的元素。當(dāng)然因為規(guī)模?,結(jié)果很可能不是?個理想的跳躍表。但是如果元素 個數(shù)n 的規(guī)模很?,學(xué)過概率論的同學(xué)都知道,最終的表結(jié)構(gòu)肯定?常接近于理想跳躍表(隔?個?跳)。
(5)?刪除
直接刪除元素,然后調(diào)整?下刪除元素后的指針即可。跟普通的鏈表刪除操作完全?樣。
typedef struct zskiplistNode {
     //層
     struct zskiplistLevel{
         //前進(jìn)指針 后邊的節(jié)點(diǎn)
        struct zskiplistNode *forward;
         //跨度
         unsigned int span;
     }level[];
     //后退指針
     struct zskiplistNode *backward;
     //分值
     double score;
     //成員對象
     robj *obj;
} zskiplistNode
--鏈表
typedef struct zskiplist{
     //表頭節(jié)點(diǎn)和表尾節(jié)點(diǎn)
     structz skiplistNode *header, *tail;
     //表中節(jié)點(diǎn)的數(shù)量
     unsigned long length;
     //表中層數(shù)最?的節(jié)點(diǎn)的層數(shù)
     int level; 
}zskiplist;
①、搜索:從最?層的鏈表節(jié)點(diǎn)開始,如果?當(dāng)前節(jié)點(diǎn)要?和?當(dāng)前層的下?個節(jié)點(diǎn)要?,那么則往下 找,也就是和當(dāng)前層的下?層的節(jié)點(diǎn)的下?個節(jié)點(diǎn)進(jìn)??較,以此類推,?直找到最底層的最后?個節(jié) 點(diǎn),如果找到則返回,反之則返回空。
②、插?:?先確定插?的層數(shù),有?種?法是假設(shè)拋?枚硬幣,如果是正?就累加,直到遇?反?為 ?,最后記錄正?的次數(shù)作為插?的層數(shù)。當(dāng)確定插?的層數(shù)k 后,則需要將新元素插?到從底層到 k 層。
③、刪除:在各個層中找到包含指定值的節(jié)點(diǎn),然后將節(jié)點(diǎn)從鏈表中刪除即可,如果刪除以后只剩下頭 尾兩個節(jié)點(diǎn),則刪除這?層。

五、Redis 設(shè)計優(yōu)化

5.1?估算Redis內(nèi)存使?量

要估算 redis 中的數(shù)據(jù)占據(jù)的內(nèi)存??,需要對 redis 的內(nèi)存模型有?較全?的了解,包括 hashtable、 sds 、 redisobject 、各種對象類型的編碼?式等。
下?以最簡單的字符串類型來進(jìn)?說明。
假設(shè)有 90000 個鍵值對,每個 key 的?度是 12 個字節(jié) ,每個 value 的?度也是 12 個字節(jié) (且 key 和 value都不是整數(shù));

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

下?來估算這 90000 個鍵值對所占?的空間。在估算占據(jù)空間之前,?先可以判定字符串類型使?的編碼?式:embstr 。
90000 個鍵值對占據(jù)的內(nèi)存空間主要可以分為兩部分: ?部分是 90000 dictEntry 占據(jù)的空間;?部分 是鍵值對所需要的bucket 空間。
每個 dictEntry 占據(jù)的空間包括:
(1) ?個 dictEntry 結(jié)構(gòu), 24 字節(jié), jemalloc 會分配 32 字節(jié)的內(nèi)存塊 ( 64 位操作系統(tǒng)下,?個指針 8 字 節(jié),?個dictEntry 由三個指針組成 )
(2) ?個 key , 12 字節(jié),所以 SDS(key) 需要 12+4=16 個字節(jié)( [SDS 的?度 =4+ 字符串?度), jemalloc 會分配16 字節(jié)的內(nèi)存塊
(3) ?個 redisObject 16 字節(jié), jemalloc 會分配 16 字節(jié)的內(nèi)存塊? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?( 4bit+4bit+24bit+4Byte+8Byte=16Byte )
(4) ?個 value , 12 字節(jié),所以 SDS(value) 需要 12+4=16 個字節(jié)( [SDS 的?度 =4+ 字符串?度), jemalloc會分配 16 字節(jié)的內(nèi)存塊
(5) 綜上,?個 dictEntry 所占據(jù)的空間需要 32+16+16+16=80 個字節(jié)。
bucket 空間:
bucket 數(shù)組的??為?于 90000 的最?的 2^n ,是 131072 ;每個 bucket 元素( bucket 中存儲的都是指針元素 )為 8 字節(jié)( 因為 64 位系統(tǒng)中指針??為 8 字節(jié) )。
因此,可以估算出這 90000 個鍵值對占據(jù)的內(nèi)存??為: [90000*80 + 131072*8 = 8248576
作為對? key value 的?度由 12 字節(jié)增加到 13 字節(jié) ,則對應(yīng)的 SDS 變?yōu)?/span> 17 個字節(jié), jemalloc 會 分配32 個字節(jié),因此每個 dictEntry 占?的字節(jié)數(shù)也由 80 字節(jié)變?yōu)?/span> 112 字節(jié)。此時估算這 90000 個鍵 值對占據(jù)內(nèi)存??為: 90000*112 + 131072*8 = 11128576 。

5.2?優(yōu)化內(nèi)存占用

了解 redis 的內(nèi)存模型,對優(yōu)化 redis 內(nèi)存占?有很?幫助。下?介紹?種優(yōu)化場景和?式

5.2.1?利用jemalloc特性進(jìn)行優(yōu)化

上??節(jié)所講述的 90000 個鍵值便是?個例?。由于 jemalloc 分配內(nèi)存時數(shù)值是不連續(xù)的,因此
key/value 字符串變化?個字節(jié),可能會引起占?內(nèi)存很?的變動;在設(shè)計時可以利?這?點(diǎn)。
例如,如果 key 的?度如果是 13 個字節(jié),則 SDS 17 字節(jié), jemalloc 分配 32 字節(jié);此時將 key ?度 縮減為12 個字節(jié),則 SDS 16 字節(jié), jemalloc 分配 16 字節(jié);則每個 key 所占?的空間都可以縮?? 半。

5.2.2?使用整型/長整型

如果是整型 / ?整型, Redis 會使? int 類型(8字節(jié))存儲來代替字符串,可以節(jié)省更多空間。因此在可以 使??整型/ 整型代替字符串的場景下,盡量使??整型 / 整型

5.2.3?共享對象

利?共享對象,可以減少對象的創(chuàng)建(同時減少了 redisObject 的創(chuàng)建),節(jié)省內(nèi)存空間。?前 redis 中的 共享對象只包括10000 個整數(shù)( 0-9999 );可以通過調(diào)整 OBJ_SHARED_INTEGERS 參數(shù)提?共享對象的個數(shù);

5.2.4?縮短鍵值對的存儲長度

鍵值對的?度是和性能成反?的,?如我們來做?組寫?數(shù)據(jù)的性能測試,執(zhí)?結(jié)果如下:

Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化,Redis,redis,數(shù)據(jù)庫,緩存,redis內(nèi)存模型

從以上數(shù)據(jù)可以看出,在 key 不變的情況下, value 值越?操作效率越慢,因為 Redis 對于同?種數(shù)據(jù) 類型會使?不同的內(nèi)部編碼進(jìn)?存儲,?如字符串的內(nèi)部編碼就有三種:int (整數(shù)編碼)、 raw (優(yōu)化 內(nèi)存分配的字符串編碼)、embstr (動態(tài)字符串編碼),這是因為 Redis 的作者是想通過不同編碼實現(xiàn) 效率和空間的平衡,然?數(shù)據(jù)量越?使?的內(nèi)部編碼就越復(fù)雜,?越是復(fù)雜的內(nèi)部編碼存儲的性能就越低。
這還只是寫?時的速度,當(dāng)鍵值對內(nèi)容較?時,還會帶來另外?個問題:
內(nèi)容越?需要的持久化時間就越?,需要掛起的時間越?, Redis 的性能就會越低;
內(nèi)容越?在?絡(luò)上傳輸?shù)膬?nèi)容就越多,需要的時間就越?,整體的運(yùn)?速度就越低;
內(nèi)容越?占?的內(nèi)存就越多,就會更頻繁的觸發(fā)內(nèi)存淘汰機(jī)制,從?給 Redis 帶來了更多的運(yùn)?負(fù) 擔(dān)。
因此在保證完整語義的同時,我們要盡量的縮短鍵值對的存儲?度,必要時要對數(shù)據(jù)進(jìn)?序列化和壓縮再存儲,以 Java 為例,序列化我們可以使? protostuff kryo ,壓縮我們可以使? snappy 。

六、Reids 內(nèi)存?量統(tǒng)計

查看 Redis 內(nèi)存統(tǒng)計
127.0.0.1:6379> info memory
# Memory
#Redis分配的內(nèi)存總量,包括虛擬內(nèi)存(字節(jié))
used_memory:853464
#占操作系統(tǒng)的內(nèi)存,不包括虛擬內(nèi)存(字節(jié))
used_memory_rss:12247040
#內(nèi)存碎??例 如果?于1說明使?了虛擬內(nèi)存
mem_fragmentation_ratio:15.07
#內(nèi)存碎?字節(jié)數(shù)
mem_fragmentation_bytes
#Redis使?的內(nèi)存分配器
mem_allocator:jemlloc-5.1.0

6.1?used_memory

Redis 內(nèi)存分配器 分配的 數(shù)據(jù)內(nèi)存 緩沖內(nèi)存 的內(nèi)存總量(單位是字節(jié)),包括使?的虛擬內(nèi)存(即 swap) used_memory_human 只是顯示更加?性化。

6.2?used_memory_rss

記錄的是由 操作系統(tǒng)分配 Redis 進(jìn)程內(nèi)存 Redis 內(nèi)存中?法再被 jemalloc 分配的 內(nèi)存碎?
(單位是字節(jié))。
used_memoryused_memory_rss的區(qū)別:
前者是從 Redis ?度得到的量,后者是從操作系統(tǒng)?度得到的量。?者之所以有所不同,???是 因為內(nèi)存碎?和Redis 進(jìn)程運(yùn)?需要占?內(nèi)存,使得前者可能?后者?,另???虛擬內(nèi)存的存在,使得前者可能?后者?。
由于在實際應(yīng)?中, Redis 的數(shù)據(jù)量會?較?,此時進(jìn)程運(yùn)?占?的內(nèi)存與 Redis 數(shù)據(jù)量和內(nèi)存碎?相?,都會?得多;因此used_memory_rss used_memory 的?例,便成了衡量 Redis 內(nèi)存碎?率的參 數(shù);這個參數(shù)就是mem_fragmentation_ratio 。

6.3?mem_fragmentation_ratio

內(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)時,應(yīng)該及時排查,如果內(nèi)存不?應(yīng)該及時處理,如增加 Redis 節(jié)點(diǎn)、增加 Redis服務(wù)器的內(nèi)存、優(yōu)化應(yīng)?等
?般來說, mem_fragmentation_ratio 1.03 左右是?較健康的狀態(tài)(對于 jemalloc 來說);剛開始的 mem_fragmentation_ratio值很?,是因為還沒有向 Redis 中存?數(shù)據(jù), Redis 進(jìn)程本身運(yùn)?的內(nèi)存使得 used_memory_rss ? used_memory ?得多。

6.4?mem_allocator

Redis 使?的內(nèi)存分配器,在編譯時指定;可以是 libc 、 jemalloc 或者 tcmalloc , 默認(rèn)是 jemalloc

七、Redis內(nèi)存劃分

Redis 作為內(nèi)存數(shù)據(jù)庫,在內(nèi)存中存儲的內(nèi)容主要是數(shù)據(jù)(鍵值對);通過前?的敘述可以知道,除了數(shù) 據(jù)以外,Redis 的其他部分也會占?內(nèi)存。
Redis 的內(nèi)存占?主要可以劃分為以下?個部分:

7.1?數(shù)據(jù)內(nèi)存

作為數(shù)據(jù)庫,數(shù)據(jù)是最主要的部分;這部分占?的內(nèi)存會統(tǒng)計在used_memory文章來源地址http://www.zghlxwxcb.cn/news/detail-690527.html

Redis 使?鍵值對存儲數(shù)據(jù),其中的值(對象)包括 5 種類型,即字符串、哈希、列表、集合、有序集 合。這5 種類型是 Redis 對外提供的,實際上,在 Redis 內(nèi)部,每種類型可能有 2 種或更多的內(nèi)部編碼實 現(xiàn);此外,Redis 在存儲對象時,并不是直接將數(shù)據(jù)扔進(jìn)內(nèi)存,?是會對對象進(jìn)?各種包裝:如 redisObject、 SDS 等;

7.2?進(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 分配,因此不會統(tǒng)計在 used_memory中。

7.3?緩沖內(nèi)存

緩沖內(nèi)存包括客戶端緩沖區(qū)、復(fù)制積壓緩沖區(qū)、 AOF 緩沖區(qū)等;其中,客戶端緩沖存儲客戶端連接的輸 ?輸出緩沖;復(fù)制積壓緩沖?于部分復(fù)制功能;AOF 緩沖區(qū)?于在進(jìn)? AOF 重寫時,保存最近的寫?命 令。在了解相應(yīng)功能之前,不需要知道這些緩沖的細(xì)節(jié); 這部分內(nèi)存由 jemalloc 分配,因此會統(tǒng)計在 used_memory中 。

7.4?內(nèi)存碎?

內(nèi)存碎?是 Redis 在分配、回收物理內(nèi)存過程中產(chǎn)?的。 例如,如果對數(shù)據(jù)的更改頻繁,?且數(shù)據(jù)之間的 ??相差很?,可能導(dǎo)致redis 釋放的空間在物理內(nèi)存中并沒有釋放,但 redis ??法有效利?,這就形成 了內(nèi)存碎?。 內(nèi)存碎?不會統(tǒng)計在 used_memory 中。
內(nèi)存碎?的產(chǎn)?與對數(shù)據(jù)進(jìn)?的操作、數(shù)據(jù)的特點(diǎn)等都有關(guān);此外,與使?的內(nèi)存分配器也有關(guān)系:如 果內(nèi)存分配器設(shè)計合理,可以盡可能的減少內(nèi)存碎?的產(chǎn)?。jemalloc 便在控制內(nèi)存碎 ???做的很好。
如果 Redis 服務(wù)器中的內(nèi)存碎?已經(jīng)很?,可以通過安全重啟的?式減?內(nèi)存碎?:因為重啟之后, Redis重新從備份?件中讀取數(shù)據(jù),在內(nèi)存中進(jìn)?重排,為每個數(shù)據(jù)重新選擇合適的內(nèi)存單元,減?內(nèi)存碎?。
好了到這里本篇完,如果你有不懂的或者想要補(bǔ)充的,可以在評論區(qū)留言,我會及時回復(fù)。
如果這篇文章對你有幫助請不要忘記給博主個三連?。?!感謝?。?!

到了這里,關(guān)于Redis從基礎(chǔ)到進(jìn)階篇(二)----內(nèi)存模型與內(nèi)存優(yōu)化的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 【NOSQL數(shù)據(jù)庫】Redis數(shù)據(jù)庫的配置與優(yōu)化一

    【NOSQL數(shù)據(jù)庫】Redis數(shù)據(jù)庫的配置與優(yōu)化一

    關(guān)系型數(shù)據(jù)庫是一個結(jié)構(gòu)化的數(shù)據(jù)庫, 創(chuàng)建在關(guān)系模型(二維表格模型)基礎(chǔ)上 ,一般面向于記錄。 SQL 語句(標(biāo)準(zhǔn)數(shù)據(jù)查詢語言)就 是一種基于關(guān)系型數(shù)據(jù)庫的語言 ,用于執(zhí)行對關(guān)系型數(shù)據(jù)庫中數(shù)據(jù)的檢索和操作。 主流的關(guān)系型數(shù)據(jù)庫包括 Oracle、MySQL、SQL Server、Microso

    2024年02月11日
    瀏覽(16)
  • Redis 非關(guān)系型數(shù)據(jù)庫 配置與優(yōu)化

    關(guān)系型數(shù)據(jù)庫是一個結(jié)構(gòu)化的數(shù)據(jù)庫,創(chuàng)建在關(guān)系模型(二維表格模型)基礎(chǔ)上,一般面向于記錄。 SQL 語句(標(biāo)準(zhǔn)數(shù)據(jù)查詢語言)就是一種基于關(guān)系型數(shù)據(jù)庫的語言,用于執(zhí)行對關(guān)系型數(shù)據(jù)庫中數(shù)據(jù)的檢索和操作。 主流的關(guān)系型數(shù)據(jù)庫包括 Oracle、MySQL、SQL Server、Microsoft A

    2024年02月09日
    瀏覽(35)
  • 數(shù)據(jù)庫緩存服務(wù)——NoSQL之Redis配置與優(yōu)化

    數(shù)據(jù)庫緩存服務(wù)——NoSQL之Redis配置與優(yōu)化

    目錄 一、緩存概念 1.1 系統(tǒng)緩存 1.2 緩存保存位置及分層結(jié)構(gòu) 1.2.1 DNS緩存 1.2.2 應(yīng)用層緩存 1.2.3 數(shù)據(jù)層緩存 1.2.4 硬件緩存 二、關(guān)系型數(shù)據(jù)庫與非關(guān)系型數(shù)據(jù)庫 2.1 關(guān)系型數(shù)據(jù)庫 2.2 非關(guān)系型數(shù)據(jù)庫 2.3 關(guān)系型數(shù)據(jù)庫和非關(guān)系型數(shù)據(jù)庫區(qū)別: 2.4 非關(guān)系型數(shù)據(jù)庫產(chǎn)生背景 2.5 總結(jié)

    2024年02月15日
    瀏覽(24)
  • Redis基于內(nèi)存的key-value結(jié)構(gòu)化NOSQL(非關(guān)系型)數(shù)據(jù)庫

    Redis基于內(nèi)存的key-value結(jié)構(gòu)化NOSQL(非關(guān)系型)數(shù)據(jù)庫

    Redis基于內(nèi)存的key-value結(jié)構(gòu)的NOSQL(非關(guān)系型)數(shù)據(jù)庫 非關(guān)系型數(shù)據(jù)庫:表與表之間沒有復(fù)雜的關(guān)系 基于內(nèi)存存儲,讀寫性能高 – Redis讀的速度是110000次/S 適合存儲熱點(diǎn)數(shù)據(jù)(商品、新聞資訊) 它存儲的value類型比較豐富,也稱為結(jié)構(gòu)化NoSQL數(shù)據(jù)庫 直接解壓windows版壓縮包就

    2024年02月11日
    瀏覽(35)
  • Redis內(nèi)存空間預(yù)估與內(nèi)存優(yōu)化策略:保障數(shù)據(jù)安全與性能的架構(gòu)實踐AIGC/AI繪畫/chatGPT/SD/MJ

    摘要: 在現(xiàn)代軟件架構(gòu)中,Redis作為一種高性能的內(nèi)存數(shù)據(jù)庫,被廣泛應(yīng)用于緩存、會話存儲和消息隊列等場景。然而,Redis的內(nèi)存占用問題一直是開發(fā)者關(guān)注的焦點(diǎn)。本文將介紹如何準(zhǔn)確預(yù)估Redis所占內(nèi)存空間,并提供一些內(nèi)存優(yōu)化策略,以避免內(nèi)存占用過多導(dǎo)致數(shù)據(jù)丟失的

    2024年02月11日
    瀏覽(27)
  • Redis內(nèi)存優(yōu)化——內(nèi)存淘汰及回收機(jī)制

    Redis內(nèi)存優(yōu)化——內(nèi)存淘汰及回收機(jī)制

    本文是系列文章,為了增強(qiáng)您的閱讀體驗,已將系列文章目錄放入文章末尾。?????? Redis內(nèi)存淘汰及回收策略都是Redis 內(nèi)存優(yōu)化兜底 的策略,那它們是如何進(jìn)行 兜底 的呢?先來說明一下什么是內(nèi)存淘汰和內(nèi)存回收策略: Redis內(nèi)存淘汰:當(dāng)Redis的內(nèi)存使用 超過配置 的限制時

    2024年02月08日
    瀏覽(19)
  • redis如何優(yōu)化內(nèi)存

    string轉(zhuǎn)hash存 對key拆分,成hash,注意:每個hash key下的filed-value個數(shù)不能超過限定值,否則不會走ziplist存儲;因此可以進(jìn)行hash算法來分配hash桶,控制每個桶的原數(shù)個數(shù);或者取數(shù)字key 的后三位,控制每個hash只有999個元素。 key 由string轉(zhuǎn)數(shù)字 問題: 較小的概率發(fā)生hash沖突,

    2024年02月10日
    瀏覽(31)
  • Redis內(nèi)存碎片:深度解析與優(yōu)化策略

    本文已收錄至GitHub,推薦閱讀 ?? Java隨想錄 微信公眾號:Java隨想錄 原創(chuàng)不易,注重版權(quán)。轉(zhuǎn)載請注明原作者和原文鏈接 目錄 內(nèi)存碎片如何產(chǎn)生的 內(nèi)存分配器 怎么看是否有內(nèi)存碎片 碎片率的意義 清理內(nèi)存碎片 低于4.0-RC3版本的Redis 高于4.0-RC3版本的Redis 在我們探究和優(yōu)化

    2024年02月08日
    瀏覽(23)
  • Redis從基礎(chǔ)到進(jìn)階篇(一)

    Redis從基礎(chǔ)到進(jìn)階篇(一)

    目錄 一、了解NoSql 1.1?什么是Nosql? 1.2?為什么要使用NoSql? 1.3?NoSql數(shù)據(jù)庫的優(yōu)勢 1.4 常見的NoSql產(chǎn)品? 1.5 各產(chǎn)品的區(qū)別 二、Redis介紹 2.1什么是Redis? 2.2 Redis優(yōu)勢? 2.3 Redis應(yīng)用場景 2.4 Redis下載 三、Linux下安裝Redis 3.1 環(huán)境準(zhǔn)備? 3.2 Redis的安裝 3.2.1 Redis的編譯環(huán)境 3.2.2 Redis的安裝

    2024年02月12日
    瀏覽(7)
  • Redis服務(wù)端優(yōu)化(持久化配置、慢查詢、命令及安全配置、內(nèi)存配置)

    Redis服務(wù)端優(yōu)化(持久化配置、慢查詢、命令及安全配置、內(nèi)存配置)

    漏洞:Redis未授權(quán)訪問配合SSH key文件利用分析-騰訊云開發(fā)者社區(qū)-騰訊云 (tencent.com) 漏洞出現(xiàn)的核心的原因有以下幾點(diǎn) Redis未設(shè)置密碼 利用了Redis的config set命令動態(tài)修改Redis配置 使用了Root賬號權(quán)限啟動Redis 查看客戶端緩沖信息的命令 info clients client list

    2024年01月23日
    瀏覽(35)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包