Redis內(nèi)存模型(詳解)

redis是目前最火爆的內(nèi)存數(shù)據(jù)庫之一,通過在內(nèi)存中讀寫數(shù)據(jù),大大提高了讀寫速度,可以說redis是實現(xiàn)網(wǎng)站高并發(fā)不可或缺的一部分。【推薦學習:redis視頻教程

Redis內(nèi)存模型(詳解)

我們使用Redis時,會接觸Redis的5種對象類型(字符串、哈希、列表、集合、有序集合),豐富的類型是Redis相對于memcached等的一大優(yōu)勢。在了解Redis的5種對象類型的用法和特點的基礎(chǔ)上,進一步了解Redis的內(nèi)存模型,對Redis的使用有很大幫助,例如:

1、估算Redis內(nèi)存使用量。目前為止,內(nèi)存的使用成本仍然相對較高,使用內(nèi)存不能無所顧忌;根據(jù)需求合理的評估Redis的內(nèi)存使用量,選擇合適的機器配置,可以在滿足需求的情況下節(jié)約成本。

2、優(yōu)化內(nèi)存占用。了解Redis內(nèi)存模型可以選擇更合適的數(shù)據(jù)類型和編碼,更好的利用Redis內(nèi)存。

3、分析解決問題。當Redis出現(xiàn)阻塞、內(nèi)存占用等問題時,盡快發(fā)現(xiàn)導致問題的原因,便于分析解決問題。

這篇文章主要介紹Redis的內(nèi)存模型(以3.0為例),包括Redis占用內(nèi)存的情況及如何查詢、不同的對象類型在內(nèi)存中的編碼方式、內(nèi)存分配器(jemalloc)、簡單動態(tài)字符串(SDS)、RedisObject等;然后在此基礎(chǔ)上介紹幾個Redis內(nèi)存模型的應(yīng)用。

一、Redis內(nèi)存統(tǒng)計

工欲善其事必先利其器,在說明Redis內(nèi)存之前首先說明如何統(tǒng)計Redis使用內(nèi)存的情況。

在客戶端通過redis-cli連接服務(wù)器后(后面如無特殊說明,客戶端一律使用redis-cli),通過info命令可以查看內(nèi)存使用情況:

info?memory

Redis內(nèi)存模型(詳解)

其中,info命令可以顯示redis服務(wù)器的許多信息,包括服務(wù)器基本信息、CPU、內(nèi)存、持久化、客戶端連接信息等等;memory是參數(shù),表示只顯示內(nèi)存相關(guān)的信息。

返回結(jié)果中比較重要的幾個說明如下:

(1)used_memoryRedis分配器分配的內(nèi)存總量(單位是字節(jié)),包括使用的虛擬內(nèi)存(即swap);Redis分配器后面會介紹。used_memory_human只是顯示更友好。

(2)used_memory_rssRedis進程占據(jù)操作系統(tǒng)的內(nèi)存(單位是字節(jié)),與top及ps命令看到的值是一致的;除了分配器分配的內(nèi)存之外,used_memory_rss還包括進程運行本身需要的內(nèi)存、內(nèi)存碎片等,但是不包括虛擬內(nèi)存。

因此,used_memory和used_memory_rss,前者是從Redis角度得到的量,后者是從操作系統(tǒng)角度得到的量。二者之所以有所不同,一方面是因為內(nèi)存碎片和Redis進程運行需要占用內(nèi)存,使得前者可能比后者小,另一方面虛擬內(nèi)存的存在,使得前者可能比后者大。

由于在實際應(yīng)用中,Redis的數(shù)據(jù)量會比較大,此時進程運行占用的內(nèi)存與Redis數(shù)據(jù)量和內(nèi)存碎片相比,都會小得多;因此used_memory_rss和used_memory的比例,便成了衡量Redis內(nèi)存碎片率的參數(shù);這個參數(shù)就是mem_fragmentation_ratio。

(3)mem_fragmentation_ratio內(nèi)存碎片比率,該值是used_memory_rss / used_memory的比值。

mem_fragmentation_ratio一般大于1,且該值越大,內(nèi)存碎片比例越大。mem_fragmentation_ratio

一般來說,mem_fragmentation_ratio在1.03左右是比較健康的狀態(tài)(對于jemalloc來說);上面截圖中的mem_fragmentation_ratio值很大,是因為還沒有向Redis中存入數(shù)據(jù),Redis進程本身運行的內(nèi)存使得used_memory_rss 比used_memory大得多。

(4)mem_allocatorRedis使用的內(nèi)存分配器,在編譯時指定;可以是 libc 、jemalloc或者tcmalloc,默認是jemalloc;截圖中使用的便是默認的jemalloc。

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

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

Redis的內(nèi)存占用主要可以劃分為以下幾個部分:

1、數(shù)據(jù)

作為數(shù)據(jù)庫,數(shù)據(jù)是最主要的部分;這部分占用的內(nèi)存會統(tǒng)計在used_memory中。

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

2、進程本身運行需要的內(nèi)存

Redis主進程本身運行肯定需要占用內(nèi)存,如代碼、常量池等等;這部分內(nèi)存大約幾兆,在大多數(shù)生產(chǎn)環(huán)境中與Redis數(shù)據(jù)占用的內(nèi)存相比可以忽略。這部分內(nèi)存不是由jemalloc分配,因此不會統(tǒng)計在used_memory中。

補充說明:除了主進程外,Redis創(chuàng)建的子進程運行也會占用內(nèi)存,如Redis執(zhí)行AOF、RDB重寫時創(chuàng)建的子進程。當然,這部分內(nèi)存不屬于Redis進程,也不會統(tǒng)計在used_memory和used_memory_rss中。

3、緩沖內(nèi)存

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

4、內(nèi)存碎片

內(nèi)存碎片是Redis在分配、回收物理內(nèi)存過程中產(chǎn)生的。例如,如果對數(shù)據(jù)的更改頻繁,而且數(shù)據(jù)之間的大小相差很大,可能導致redis釋放的空間在物理內(nèi)存中并沒有釋放,但redis又無法有效利用,這就形成了內(nèi)存碎片。內(nèi)存碎片不會統(tǒng)計在used_memory中。

內(nèi)存碎片的產(chǎn)生與對數(shù)據(jù)進行的操作、數(shù)據(jù)的特點等都有關(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)存中進行重排,為每個數(shù)據(jù)重新選擇合適的內(nèi)存單元,減小內(nèi)存碎片。

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

1、概述

關(guān)于Redis數(shù)據(jù)存儲的細節(jié),涉及到內(nèi)存分配器(如jemalloc)、簡單動態(tài)字符串(SDS)、5種對象類型及內(nèi)部編碼、redisObject。在講述具體內(nèi)容之前,先說明一下這幾個概念之間的關(guān)系。

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

?Redis內(nèi)存模型(詳解)

圖片來源:https://searchdatabase.techtarget.com.cn/7-20218/

(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)部編碼的字段;后面會詳細介紹。

(4)jemalloc:無論是DictEntry對象,還是redisObject、SDS對象,都需要內(nèi)存分配器(如jemalloc)分配內(nèi)存進行存儲。以DictEntry對象為例,有3個指針組成,在64位機器下占24個字節(jié),jemalloc會為它分配32字節(jié)大小的內(nèi)存單元。

下面來分別介紹jemalloc、redisObject、SDS、對象類型及內(nèi)部編碼。

2、jemalloc

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

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

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

?Redis內(nèi)存模型(詳解)

圖片來源:http://blog.csdn.net/zhengpeitao/article/details/76573053

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

3、redisObject

前面說到,Redis對象有5種類型;無論是哪種類型,Redis都不會直接存儲,而是通過redisObject對象進行存儲。

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

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;

redisObject的每個字段的含義和作用如下:

(1)type

type字段表示對象的類型,占4個比特;目前包括REDIS_String(字符串)、REDIS_LIST (列表)、REDIS_HASH(哈希)、REDIS_SET(集合)、REDIS_ZSET(有序集合)。

當我們執(zhí)行type命令時,便是通過讀取RedisObject的type字段獲得對象的類型;如下圖所示:

Redis內(nèi)存模型(詳解)

(2)encoding

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

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

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

Redis內(nèi)存模型(詳解)

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

(3)lru

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

通過對比lru時間與當前時間,可以計算某個對象的空轉(zhuǎn)時間;object idletime命令可以顯示該空轉(zhuǎn)時間(單位是秒)。object idletime命令的一個特殊之處在于它不改變對象的lru值。

Redis內(nèi)存模型(詳解)

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

(4)refcount

refcount與共享對象

refcount記錄的是該對象被引用的次數(shù),類型為整型。refcount的作用,主要在于對象的引用計數(shù)和內(nèi)存回收。當創(chuàng)建新對象時,refcount初始化為1;當有新程序使用該對象時,refcount加1;當對象不再被一個新程序使用時,refcount減1;當refcount變?yōu)?時,對象占用的內(nèi)存會被釋放。

Redis中被多次使用的對象(refcount>1),稱為共享對象。Redis為了節(jié)省內(nèi)存,當有一些對象重復(fù)出現(xiàn)時,新的程序不會創(chuàng)建新的對象,而是仍然使用原來的對象。這個被重復(fù)使用的對象,就是共享對象。目前共享對象僅支持整數(shù)值的字符串對象。

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

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

雖然共享對象只能是整數(shù)值的字符串對象,但是5種類型都可能使用共享對象(如哈希、列表等的元素可以使用)。

就目前的實現(xiàn)來說,Redis服務(wù)器在初始化時,會創(chuàng)建10000個字符串對象,值分別是0~9999的整數(shù)值;當Redis需要使用值為0~9999的字符串對象時,可以直接使用這些共享對象。10000這個數(shù)字可以通過調(diào)整參數(shù)REDIS_SHARED_INTEGERS(4.0中是OBJ_SHARED_INTEGERS)的值進行改變。

共享對象的引用次數(shù)可以通過object refcount命令查看,如下圖所示。命令執(zhí)行的結(jié)果頁佐證了只有0~9999之間的整數(shù)會作為共享對象。

Redis內(nèi)存模型(詳解)

(5)ptr

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

(6)總結(jié)

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

4bit+4bit+24bit+4Byte+8Byte=16Byte。

4、SDS

Redis沒有直接使用C字符串(即以空字符’

精品久久人人爽天天玩人人妻| 亚洲国产成人精品女人久久久| 一本色道久久HEZYO无码| 三级三级久久三级久久| 国产Av激情久久无码天堂| 国内精品久久久久伊人av| 成人久久精品一区二区三区| 青青草原综合久久大伊人导航| 一级女性全黄久久生活片免费| 久久久亚洲欧洲日产国码aⅴ| 久久一区二区三区99| 久久国产欧美日韩精品| 国内精品久久久久久不卡影院| 欧美亚洲国产精品久久高清| 久久99国产精品一区二区| 久久天天躁夜夜躁狠狠| 青春久久| 欧美亚洲国产精品久久蜜芽| 无码人妻久久一区二区三区免费丨 | 青青草原精品99久久精品66| 久久99热这里只有精品国产| 无码国内精品久久人妻蜜桃| 亚洲精品国产综合久久一线| av无码久久久久不卡免费网站 | 韩国免费A级毛片久久| 青青热久久国产久精品| Xx性欧美肥妇精品久久久久久| 亚洲AV无码成人网站久久精品大| 久久人人爽人人精品视频| 国产麻豆精品久久一二三| 综合人妻久久一区二区精品| 中文字幕久久精品 | 久久精品a亚洲国产v高清不卡| 狠狠色丁香久久婷婷综合蜜芽五月| 91精品无码久久久久久五月天| 国产精品久久午夜夜伦鲁鲁| 精品多毛少妇人妻AV免费久久| 伊人久久大香线蕉综合热线| 亚洲精品无码久久久影院相关影片| 亚洲精品美女久久久久99小说| 久久久噜噜噜久久中文字幕色伊伊|