天天看點

「資料庫」深入學習 Redis 記憶體模型

作者:架構思考
Redis 對于一個 Java 開發工程師來講,其實算不得什麼複雜新奇的技術,但可能也很少人去深入了解學習它的底層的一些東西。下面将通過對記憶體統計、記憶體劃分、存儲細節、對象類型&内部編碼這四個子產品來學習學習 Redis 的記憶體模型,手字筆錄,潛心修行。歡迎閱讀~

一、Redis 的記憶體統計

info memory 指令檢視記憶體使用情況:伺服器基本資訊、CPU、記憶體、持久化、用戶端連接配接資訊等等,如下圖:

「資料庫」深入學習 Redis 記憶體模型

1.1 used_memory和used_memory_rss

used_memory:Redis 配置設定器配置設定的記憶體總量 + 虛拟記憶體(磁盤)。

used_memory_rss:Redis程序占據作業系統的記憶體 + 程序運作本身需要的記憶體 + 記憶體碎片等 (*:注意 used_memory_rss 不包括虛拟記憶體)。

兩者差別:

①面向角度:used_memory: Redis角度 used_memory_rss:作業系統角度。

②大小不一定是後者大于前者:記憶體碎片和 Redis 程序運作需要占用記憶體,使得前者可能比後者小,另一方面虛拟記憶體的存在,使得前者可能比後者大。

1.2 mem_fragmentation_ratio

記憶體碎片比率, 等于 used_memory_rss / used_memory。

mem_fragmentation_ratio > 1 : 值越大,記憶體碎片比例越大。

mem_fragmentation_ratio < 1 : 說明 Redis 使用了虛拟記憶體。

*:由于虛拟記憶體的媒介是磁盤,比記憶體速度要慢很多,當這種情況出現時,應該及時排查,如果記憶體不足應該及時處理,如增加 Redis 節點、增加 Redis 伺服器的記憶體、優化應用等。

正常情況下:mem_fragmentation_ratio = 1.03左右 (健康:對于jemalloc來說)。

上面的情況:沒有向 Redis 中存入資料,Redis 程序本身運作的記憶體使得 used_memory_rss 比 used_memory 大得多。

1.3 mem_allocator:

Redis 使用的記憶體配置設定器,在編譯時指定,可以是 libc 、jemalloc或者tcmalloc,預設是 jemalloc。

1.4 used_memory_peak:

Redis 的記憶體消耗峰值。

1.5 used_memory_human 和 used_memory_peak_human:

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

二、Redis 的記憶體劃分

資料:最主要的部分,會統計在 used_memory。實際上,在 Redis 内部,每種類型可能有2種或更多的内部編碼實作。此外,Redis 在存儲對象時,并不是直接将資料扔進記憶體,而是會對對象進行各種包裝:如 RedisObject、SDS 等。

程序本身記憶體:Redis 主程序本身運作肯定需要占用記憶體,如代碼、常量池等等。這部分記憶體大約幾兆,在大多數生産環境中與 Redis 資料占用的記憶體相比可以忽略。這部分記憶體不是由 jemalloc 配置設定,是以不會統計在 used_memory 中。

緩沖記憶體:包含用戶端緩沖區、複制積壓緩沖區、AOF 緩沖區。

用戶端緩沖區:存儲用戶端連接配接的輸入輸出緩沖。

複制積壓緩沖區:用于部分複制功能。

AOF 緩沖區:用于在進行 AOF 重寫時,儲存最近的寫入指令。

記憶體碎片:記憶體碎片是 Redis 在配置設定、回收實體記憶體過程中産生的。

三、Redis 的資料存儲細節

當我們執行一個 redis 指令,比如:set hello world,redis 底層存儲到底幹了什麼?

「資料庫」深入學習 Redis 記憶體模型

上面就涉及到兩個概念:jemalloc 和 RedisObject。

3.1 jemalloc

記憶體配置設定器:可以是 libc 、jemalloc 或者 tcmalloc,預設 jemalloc。

jemalloc記憶體劃分:小、大、巨大,每個又分許多小記憶體塊機關。

「資料庫」深入學習 Redis 記憶體模型

例如,如果需要存儲大小為 130 位元組的對象,jemalloc 會将其放入 160 位元組的記憶體單元中。

3.2 RedisObject(核心資料結構)

redis 的五種類型都是通過 RedisObject 存儲,Redis 對象的 類型、内部編碼、記憶體回收、共享對象等功能都需要 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:表示對象的資料類型,占 4 bit。

encoding:表示對象内部的編碼,占 4 bit,對于 redis 的每種資料類型,都至少有倆種内部編碼。比如字元串類型有:int、embstr、raw。

lru:記錄的是對象最後一次被指令程式通路的時間,占據的比特數不同的版本有所不同(如4.0 版本占 24 比特,2.6 版本占 22 比特)。

refcount:

1、概念:refcount 記錄的是該對象被引用的次數,類型目前僅為整型。

2、作用:refcount 的作用,主要在于對象的引用計數和記憶體回收:

①當建立新對象時,refcount 初始化為 1。

②當有新程式使用該對象時,refcount 加 1。

③當對象不再被一個新程式使用時,refcount 減 1。

④當 refcount 變為 0 時,對象占用的記憶體會被釋放。

3、為什麼隻支援整數值的字元串對象?對記憶體和 CPU(時間)的平衡:

①對于整數值,判斷操作複雜度為 O(1)。

②對于普通字元串,判斷複雜度為 O(n)。

③而對于哈希、清單、集合和有序集合,判斷的複雜度為 O(n^2)。

4、目前實作:Redis 伺服器在初始化時,會建立 10000 個字元串對象,值分别是 0~9999 的整數值;10000 這個數字可以通過調整參數REDIS_SHARED_INTEGERS(4.0中是 OBJ_SHARED_INTEGERS)的值進行改變。(共享對象的引用次數可以通過 object refcount 指令檢視。

ptr:ptr 指針指向具體的資料,如前面的例子中,set hello world,ptr 指向包含字元串 world 的 SDS。

3.3 SDS

1、概念:Redis 沒有直接使用 C 字元串(即以空字元‘\0’結尾的字元數組)作為預設的字元串表示,而是使用了 SDS。SDS 是簡單動态字元串(Simple Dynamic String)的縮寫。

2、結構:

「資料庫」深入學習 Redis 記憶體模型

3、相關計算:

*:buf 數組的長度 = free+len+1(其中1表示字元串結尾的空字元)。

一個 SDS 結構占據的空間 = free 所占長度+len 所占長度+ buf 數組的長度 = 4+4+free+len+1 = free+len+9。

4、加“\0”目的:為了簡單字元串能夠調用c字元串部分函數。

四、Redis 的對象類型&内部編碼

「資料庫」深入學習 Redis 記憶體模型

4.1 字元串

1、字元串長度不超過512MB。

2、内部編碼有三種: int、embstr、raw。

3、編碼轉換關系:

  • int:整形。
  • embstr:<=39位元組的字元串。
  • raw:>39位元組的字元串。

4、embstr 和 raw 的差別:

①embstr 都使用redisObject和sds結構存儲。

②emstr 建立隻配置設定一次記憶體空間(redisObject 和 sds 一起配置設定,因為它是連續的)。

  • 缺點:建立和删除都需要整個redisObject和sds重新配置設定空間,是以 emstr 實作為隻讀。

③raw 需要配置設定兩次。

5、當 emstr 被修改時,會先變成 raw,再修改,無論是否達到39位元組

這也是為了避免建立整個 redisObject 和 sds。

「資料庫」深入學習 Redis 記憶體模型
「資料庫」深入學習 Redis 記憶體模型

4.2 清單

1、内部編碼:ziplist 和 linkedlist:(每個節點指向的是redisObject)。

2、壓縮清單:節約空間,連續記憶體塊。

3、編碼轉換:什麼情況下使用壓縮清單?

①清單元素 < 512個。

②清單中所有字元串對象都不足64位元組(字元串長度)。

「資料庫」深入學習 Redis 記憶體模型
「資料庫」深入學習 Redis 記憶體模型

4.3 hash:内層哈希和外層哈希

内層哈希:ziplist、hashtable。

外層哈希:hashtable。

「資料庫」深入學習 Redis 記憶體模型
「資料庫」深入學習 Redis 記憶體模型

文章來源:吳佳_版權歸原作者所有

繼續閱讀