深入瞭解一下Redis的記憶體模型!
一前言
Redis是目前最火爆的記憶體資料庫之一,透過在記憶體中讀寫資料,大大提高了讀寫速度,可以說Redis是實現網站高併發不可或缺的一部分。
我們使用Redis時,會接觸Redis的5種物件型別(字串、雜湊、列表、集合、有序集合),豐富的型別是Redis相對於Memcached等的一大優勢。在瞭解Redis的5種物件型別的用法和特點的基礎上,進一步瞭解Redis的記憶體模型,對Redis的使用有很大幫助,例如:
1、估算Redis記憶體使用量。目前為止,記憶體的使用成本仍然相對較高,使用記憶體不能無所顧忌;根據需求合理的評估Redis的記憶體使用量,選擇合適的機器配置,可以在滿足需求的情況下節約成本。
2、最佳化記憶體佔用。瞭解Redis記憶體模型可以選擇更合適的資料型別和編碼,更好的利用Redis記憶體。
3、分析解決問題。當Redis出現阻塞、記憶體佔用等問題時,儘快發現導致問題的原因,便於分析解決問題。
這篇文章主要介紹Redis的記憶體模型(以3.0為例),包括Redis佔用記憶體的情況及如何查詢、不同的物件型別在記憶體中的編碼方式、記憶體分配器(jemalloc)、簡單動態字串(SDS)、RedisObject等;然後在此基礎上介紹幾個Redis記憶體模型的應用。
在後面的文章中,會陸續介紹關於Redis高可用的內容,包括主從複製、哨兵、叢集等等,歡迎關注。
目錄
工欲善其事必先利其器,在說明Redis記憶體之前首先說明如何統計Redis使用記憶體的情況。
在客戶端透過redis-cli連線伺服器後(後面如無特殊說明,客戶端一律使用redis-cli),透過info命令可以檢視記憶體使用情況:
1info memory
其中,info命令可以顯示redis伺服器的許多資訊,包括伺服器基本資訊、CPU、記憶體、持久化、客戶端連線資訊等等;memory是引數,表示只顯示記憶體相關的資訊。
返回結果中比較重要的幾個說明如下:
(1)used_memory:Redis分配器分配的記憶體總量(單位是位元組),包括使用的虛擬記憶體(即swap);Redis分配器後面會介紹。used_memory_human只是顯示更友好。
(2)used_memory_rss:Redis程式佔據作業系統的記憶體(單位是位元組),與top及ps命令看到的值是一致的;除了分配器分配的記憶體之外,used_memory_rss還包括程式執行本身需要的記憶體、記憶體碎片等,但是不包括虛擬記憶體。
因此,used_memory和used_memory_rss,前者是從Redis角度得到的量,後者是從作業系統角度得到的量。二者之所以有所不同,一方面是因為記憶體碎片和Redis程式執行需要佔用記憶體,使得前者可能比後者小,另一方面虛擬記憶體的存在,使得前者可能比後者大。
由於在實際應用中,Redis的資料量會比較大,此時程式執行佔用的記憶體與Redis資料量和記憶體碎片相比,都會小得多;因此used_memory_rss和used_memory的比例,便成了衡量Redis記憶體碎片率的引數;這個引數就是mem_fragmentation_ratio。
(3)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來說);上面截圖中的mem_fragmentation_ratio值很大,是因為還沒有向Redis中存入資料,Redis程式本身執行的記憶體使得used_memory_rss 比used_memory大得多。
(4)mem_allocator:Redis使用的記憶體分配器,在編譯時指定;可以是 libc 、jemalloc或者tcmalloc,預設是jemalloc;截圖中使用的便是預設的jemalloc。
Redis作為記憶體資料庫,在記憶體中儲存的內容主要是資料(鍵值對);透過前面的敘述可以知道,除了資料以外,Redis的其他部分也會佔用記憶體。
Redis的記憶體佔用主要可以劃分為以下幾個部分:
作為資料庫,資料是最主要的部分;這部分佔用的記憶體會統計在used_memory中。
Redis使用鍵值對儲存資料,其中的值(物件)包括5種型別,即字串、雜湊、列表、集合、有序集合。這5種型別是Redis對外提供的,實際上,在Redis內部,每種型別可能有2種或更多的內部編碼實現;此外,Redis在儲存物件時,並不是直接將資料扔進記憶體,而是會對物件進行各種包裝:如redisObject、SDS等;這篇文章後面將重點介紹Redis中資料儲存的細節。
Redis主程式本身執行肯定需要佔用記憶體,如程式碼、常量池等等;這部分記憶體大約幾兆,在大多數生產環境中與Redis資料佔用的記憶體相比可以忽略。這部分記憶體不是由jemalloc分配,因此不會統計在used_memory中。
補充說明:除了主程式外,Redis建立的子程式執行也會佔用記憶體,如Redis執行AOF、RDB重寫時建立的子程式。當然,這部分記憶體不屬於Redis程式,也不會統計在used_memory和used_memory_rss中。
緩衝記憶體包括客戶端緩衝區、複製積壓緩衝區、AOF緩衝區等;其中,客戶端緩衝儲存客戶端連線的輸入輸出緩衝;複製積壓緩衝用於部分複製功能;AOF緩衝區用於在進行AOF重寫時,儲存最近的寫入命令。在瞭解相應功能之前,不需要知道這些緩衝的細節;這部分記憶體由jemalloc分配,因此會統計在used_memory中。
記憶體碎片是Redis在分配、回收實體記憶體過程中產生的。例如,如果對資料的更改頻繁,而且資料之間的大小相差很大,可能導致redis釋放的空間在實體記憶體中並沒有釋放,但redis又無法有效利用,這就形成了記憶體碎片。記憶體碎片不會統計在used_memory中。
記憶體碎片的產生與對資料進行的操作、資料的特點等都有關;此外,與使用的記憶體分配器也有關係:如果記憶體分配器設計合理,可以儘可能的減少記憶體碎片的產生。後面將要說到的jemalloc便在控制記憶體碎片方面做的很好。
如果Redis伺服器中的記憶體碎片已經很大,可以透過安全重啟的方式減小記憶體碎片:因為重啟之後,Redis重新從備份檔案中讀取資料,在記憶體中進行重排,為每個資料重新選擇合適的記憶體單元,減小記憶體碎片。
關於Redis資料儲存的細節,涉及到記憶體分配器(如jemalloc)、簡單動態字串(SDS)、5種物件型別及內部編碼、redisObject。在講述具體內容之前,先說明一下這幾個概念之間的關係。
下圖是執行set hello world時,所涉及到的資料模型。
圖片來源:
(1)dictEntry:Redis是Key-Value資料庫,因此對每個鍵值對都會有一個dictEntry,裡面儲存了指向Key和Value的指標;next指向下一個dictEntry,與本Key-Value無關。
(2)Key:圖中右上角可見,Key(”hello”)並不是直接以字串儲存,而是儲存在SDS結構中。
(3)redisObject:Value(“world”)既不是直接以字串儲存,也不是像Key一樣直接儲存在SDS中,而是儲存在redisObject中。實際上,不論Value是5種型別的哪一種,都是透過redisObject來儲存的;而redisObject中的type欄位指明瞭Value物件的型別,ptr欄位則指向物件所在的地址。不過可以看出,字串物件雖然經過了redisObject的包裝,但仍然需要透過SDS儲存。
實際上,redisObject除了type和ptr欄位以外,還有其他欄位圖中沒有給出,如用於指定物件內部編碼的欄位;後面會詳細介紹。
(4)jemalloc:無論是DictEntry物件,還是redisObject、SDS物件,都需要記憶體分配器(如jemalloc)分配記憶體進行儲存。以DictEntry物件為例,有3個指標組成,在64位機器下佔24個位元組,jemalloc會為它分配32位元組大小的記憶體單元。
下面來分別介紹jemalloc、redisObject、SDS、物件型別及內部編碼。
Redis在編譯時便會指定記憶體分配器;記憶體分配器可以是 libc 、jemalloc或者tcmalloc,預設是jemalloc。
jemalloc作為Redis的預設記憶體分配器,在減小記憶體碎片方面做的相對比較好。jemalloc在64位系統中,將記憶體空間劃分為小、大、巨大三個範圍;每個範圍內又劃分了許多小的記憶體塊單位;當Redis儲存資料時,會選擇大小最合適的記憶體塊進行儲存。
jemalloc劃分的記憶體單元如下圖所示:
圖片來源:http://blog.csdn.net/zhengpeitao/article/details/76573053
例如,如果需要儲存大小為130位元組的物件,jemalloc會將其放入160位元組的記憶體單元中。
前面說到,Redis物件有5種型別;無論是哪種型別,Redis都不會直接儲存,而是透過redisObject物件進行儲存。
redisObject物件非常重要,Redis物件的型別、內部編碼、記憶體回收、共享物件等功能,都需要redisObject支援,下面將透過redisObject的結構來說明它是如何起作用的。
redisObject的定義如下(不同版本的Redis可能稍稍有所不同):
1
2
3
4
5
6
7
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
unsigned lru:REDIS_LRU_BITS;
int refcount;
void *ptr;
} robj;
redisObject的每個欄位的含義和作用如下:
(1)type
type欄位表示物件的型別,佔4個位元;目前包括REDIS_STRING(字串)、REDIS_LIST (列表)、REDIS_HASH(雜湊)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。
當我們執行type命令時,便是透過讀取RedisObject的type欄位獲得物件的型別;如下圖所示:
(2)encoding
encoding表示物件的內部編碼,佔4個位元。
對於Redis支援的每種型別,都有至少兩種內部編碼,例如對於字串,有int、embstr、raw三種編碼。透過encoding屬性,Redis可以根據不同的使用場景來為物件設定不同的編碼,大大提高了Redis的靈活性和效率。以列表物件為例,有壓縮列表和雙端連結串列兩種編碼方式;如果列表中的元素較少,Redis傾向於使用壓縮列表進行儲存,因為壓縮列表佔用記憶體更少,而且比雙端連結串列可以更快載入;當列表物件元素較多時,壓縮列表就會轉化為更適合儲存大量元素的雙端連結串列。
透過object encoding命令,可以檢視物件採用的編碼方式,如下圖所示:
5種物件型別對應的編碼方式以及使用條件,將在後面介紹。
(3)lru
lru記錄的是物件最後一次被命令程式訪問的時間,佔據的位元數不同的版本有所不同(如4.0版本佔24位元,2.6版本佔22位元)。
透過對比lru時間與當前時間,可以計算某個物件的空轉時間;object idletime命令可以顯示該空轉時間(單位是秒)。object idletime命令的一個特殊之處在於它不改變物件的lru值。
lru值除了透過object idletime命令列印之外,還與Redis的記憶體回收有關係:如果Redis開啟了maxmemory選項,且記憶體回收演算法選擇的是volatile-lru或allkeys—lru,那麼當Redis記憶體佔用超過maxmemory指定的值時,Redis會優先選擇空轉時間最長的物件進行釋放。
(4)refcount
refcount與共享物件
refcount記錄的是該物件被引用的次數,型別為整型。refcount的作用,主要在於物件的引用計數和記憶體回收。當建立新物件時,refcount初始化為1;當有新程式使用該物件時,refcount加1;當物件不再被一個新程式使用時,refcount減1;當refcount變為0時,物件佔用的記憶體會被釋放。
Redis中被多次使用的物件(refcount>1),稱為共享物件。Redis為了節省記憶體,當有一些物件重複出現時,新的程式不會建立新的物件,而是仍然使用原來的物件。這個被重複使用的物件,就是共享物件。目前共享物件僅支援整數值的字串物件。
共享物件的具體實現
Redis的共享物件目前只支援整數值的字串物件。之所以如此,實際上是對記憶體和CPU(時間)的平衡:共享物件雖然會降低記憶體消耗,但是判斷兩個物件是否相等卻需要消耗額外的時間。對於整數值,判斷操作複雜度為O(1);對於普通字串,判斷複雜度為O(n);而對於雜湊、列表、集合和有序集合,判斷的複雜度為O(n^2)。
雖然共享物件只能是整數值的字串物件,但是5種型別都可能使用共享物件(如雜湊、列表等的元素可以使用)。
就目前的實現來說,Redis伺服器在初始化時,會建立10000個字串物件,值分別是0~9999的整數值;當Redis需要使用值為0~9999的字串物件時,可以直接使用這些共享物件。10000這個數字可以透過調整引數REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值進行改變。
共享物件的引用次數可以透過object refcount命令檢視,如下圖所示。命令執行的結果頁佐證了只有0~9999之間的整數會作為共享物件。
(5)ptr
ptr指標指向具體的資料,如前面的例子中,set hello world,ptr指向包含字串world的SDS。
(6)總結
綜上所述,redisObject的結構與物件型別、編碼、記憶體回收、共享物件都有關係;一個redisObject物件的大小為16位元組:
4bit+4bit+24bit+4Byte+8Byte=16Byte。
Redis沒有直接使用C字串(即以空字元’
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/3486/viewspace-2820246/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 來,瞭解一下Java記憶體模型(JMM)Java記憶體模型
- 深入學習 Redis(1):Redis 記憶體模型Redis記憶體模型
- 深入瞭解 JavaScript 記憶體洩漏JavaScript記憶體
- 你瞭解Java記憶體模型麼(Java7、8、9記憶體模型的區別)Java記憶體模型
- 你真的瞭解Java記憶體模型JMM嗎?Java記憶體模型
- 精講Redis記憶體模型Redis記憶體模型
- Redis記憶體碎片深入分析Redis記憶體
- 深入淺出Java記憶體模型Java記憶體模型
- 比memcpy還要快的記憶體複製,老哥瞭解一下?memcpy記憶體
- 虛擬記憶體系統——瞭解記憶體的工作原理記憶體
- 作為php瞭解一下共享記憶體的概念及優缺點PHP記憶體
- 深入理解Java記憶體模型(五)——鎖Java記憶體模型
- 深入理解JVM(一)JVM記憶體模型JVM記憶體模型
- 深入理解Java記憶體模型(六)——finalJava記憶體模型
- 深入理解JVM(一)——JVM記憶體模型JVM記憶體模型
- JVM記憶體模型詳解JVM記憶體模型
- 深入理解JVM(③)學習Java的記憶體模型JVMJava記憶體模型
- 要升級家裡機器的記憶體了,瞭解了一下記憶體規格記憶體
- Java中的記憶體模型詳解Java記憶體模型
- Redis 雜湊結構記憶體模型剖析Redis記憶體模型
- 深入理解Java記憶體模型(七)——總結Java記憶體模型
- 深入理解Java記憶體模型(二)——重排序Java記憶體模型排序
- 深入理解jvm記憶體模型以及gc原理JVM記憶體模型GC
- 深入理解Java記憶體模型(一)——基礎Java記憶體模型
- 深入理解JVM(1)之--JVM記憶體模型JVM記憶體模型
- 九、JVM記憶體模型詳解JVM記憶體模型
- Redis記憶體——記憶體消耗(記憶體都去哪了?)Redis記憶體
- 筆記-更深層次的瞭解iOS記憶體管理筆記iOS記憶體
- 記憶體模型記憶體模型
- redis的記憶體滿了之後,redis如何回收記憶體嗎Redis記憶體
- 程式的記憶體模型記憶體模型
- Java的記憶體模型Java記憶體模型
- Redis的記憶體回收機制和記憶體過期淘汰策略詳解Redis記憶體
- 深入理解JVM-記憶體模型(jmm)和GCJVM記憶體模型GC
- Java記憶體模型是什麼,為什麼要有Java記憶體模型,Java記憶體模型解決了什麼問題?Java記憶體模型
- Redis 記憶體淘汰機制詳解Redis記憶體
- 聊聊 記憶體模型與記憶體序記憶體模型
- Java記憶體模型最全詳解(5大模型圖解)Java記憶體大模型圖解