學(xué)習(xí)下Redis內(nèi)存模型

2022-12-27 12:10:54 來源:51CTO博客

作者:京東零售 吳佳

前言

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)存模型,手字筆錄,潛心修行。


(資料圖片)

一、redis的內(nèi)存統(tǒng)計(jì)

info memory 命令查看內(nèi)存使用情況:服務(wù)器基本信息、CPU、內(nèi)存、持久化、客戶端連接信息等等,如下圖: ?

??

(1)used_memory和used_memory_rss

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)存的存在,使得前者可能比后者大

(2)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)時(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大得多

(3)mem_allocator:

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

(4)used_memory_peak:

Redis的內(nèi)存消耗峰值

(5)used_memory_human和used_memory_peak_human:

字面含義,以人類閱讀的方式返回。

?

二、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)生的。

?

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

當(dāng)我們執(zhí)行一個(gè)redis指令,比如:set hello world,redis底層存儲(chǔ)到底干了什么?

?

??

上面就涉及到兩個(gè)概念:jemalloc和RedisObject

(1)jemalloc

內(nèi)存分配器:可以是 libc 、jemalloc或者tcmalloc,默認(rèn)jemalloc

jemalloc內(nèi)存劃分:小、大、巨大,每個(gè)又分許多小內(nèi)存塊單位

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

(2)RedisObject(核心數(shù)據(jù)結(jié)構(gòu))

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

(3)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ù)

四、redis的對象類型&內(nèi)部編碼

?

??

(1)字符串

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

?

??

?

??

(2)列表

1、內(nèi)部編碼:ziplist和linkedlist:(每個(gè)節(jié)點(diǎn)指向的是redisObject)

2、壓縮列表:節(jié)約空間,連續(xù)內(nèi)存塊

3、編碼轉(zhuǎn)換:什么情況下使用壓縮列表?

①列表元素 < 512個(gè)

②列表中所有字符串對象都不足64字節(jié)(字符串長度)

?

??

??

(3)hash:內(nèi)層哈希和外層哈希

內(nèi)層哈希:ziplist、hashtable

外層哈希:hashtable

?

??

標(biāo)簽: 內(nèi)存碎片 虛擬內(nèi)存 字符串長度

上一篇:天天報(bào)道:嵌入式:ARM常用開發(fā)編譯軟件介紹
下一篇:當(dāng)前要聞:Mysql到TiDB遷移,雙寫數(shù)據(jù)庫兜底方案