1. 程式人生 > >Redis4.0記憶體容量評估

Redis4.0記憶體容量評估

文章目錄

Redis容量評估

計算Redis容量,並不只是僅僅計算key佔多少位元組,value佔多少位元組,因為Redis為了維護自身的資料結構,也會佔用部分記憶體,本文章簡單介紹每種資料型別(StringHashSetZSetList)佔用記憶體量,供做Redis容量評估時使用。

Redis記憶體模型

要評估Redis佔用記憶體量,首先需要了解Redis的記憶體模型。通過Redis的記憶體模型,才能不僅知其然,而且知其所以然。

檢視記憶體佔用

首先在redis-cli命令列中鍵入memory stats命令檢視下Redis記憶體佔用,大概如下圖所示:

 1) "peak.allocated"
#Redis啟動到現在,佔用記憶體的峰值 2) (integer) 850160 3) "total.allocated" #當前使用的記憶體總量,Redis分配器分配的記憶體總量(單位是位元組),包括使用的虛擬記憶體(即swap) 4) (integer) 827608 5) "startup.allocated" #Redis啟動完成使用的記憶體位元組數 6) (integer) 765616 7) "replication.backlog" #主從複製backlog使用的記憶體,預設10MB,backlog只在主從斷線重連時發揮作用,主從複製本身並不依賴此項。 8) (integer)
0 9) "clients.slaves" #主從複製中所有slave的讀寫緩衝區 10) (integer) 0 11) "clients.normal" #除slave外所有其他客戶端的讀寫緩衝區 12) (integer) 49630 13) "aof.buffer" #此項為aof持久化使用的快取和aofrewrite時產生的快取之和,如果關閉了appendonly那這項就一直為0 14) (integer) 0 15) "db.0" #redis每個db的元資訊使用的記憶體,這裡只使用了db0,所以只打印了db0的記憶體使用狀態 16) 1) "overhead.hashtable.main" 2) (integer) 72 3) "overhead.hashtable.expires" 4) (integer) 0 17) "overhead.total" #redis額外的總開銷記憶體位元組數; 即分配器分配的總記憶體total.allocated,減去資料實際儲存使用記憶體:startup.allocated+replication.backlog+clients.slaves+clients.normal+aof.buffer+dbx 18) (integer) 815318 19) "keys.count" #redis當前儲存的key總量 20) (integer) 1 21) "keys.bytes-per-key" #平均每個key的記憶體大小:(total.allocated - startup.allocated) / keys.count 22) (integer) 61992 23) "dataset.bytes" #所有資料所使用的記憶體:total.allocated - overhead.total 24) (integer) 12290 25) "dataset.percentage" #所有資料佔比:100 * dataset.bytes / (total.allocated - startup.allocated) 26) "19.5934348106384277" 27) "peak.percentage" #當前使用記憶體與歷史最高值比例 28) "97.6251003742218018" 29) "fragmentation" #記憶體碎片比率 30) "2.1039986610412598"

總上圖我們可以看出,Redis佔用的記憶體並不僅僅包括資料記憶體dataset.bytes,還包括啟動初始化記憶體、主從複製佔用記憶體、緩衝區記憶體等。

記憶體劃分

Redis記憶體佔用主要可以劃分為如下幾個部分:

  • 資料
    Redis資料佔用記憶體dataset.bytes包括key-value佔用記憶體、dicEntry佔用記憶體、SDS佔用記憶體等。
    = t o t a l . a l l o c a t e d o v e r h e a d . t o t a l 資料所佔記憶體=當前所佔總記憶體`total.allocated`-額外記憶體`overhead.total`
  • 初始化記憶體
    redis啟動初始化時使用的記憶體startup.allocated,屬於額外記憶體overhead.total的一部分。
  • 主從複製記憶體
    用於主從複製,屬於額外記憶體一部分。
  • 緩衝區記憶體
    緩衝記憶體包括客戶端緩衝區、複製積壓緩衝區、AOF緩衝區等;其中,客戶端緩衝儲存客戶端連線的輸入輸出緩衝;複製積壓緩衝用於部分複製功能;AOF緩衝區用於在進行AOF重寫時,儲存最近的寫入命令。在瞭解相應功能之前,不需要知道這些緩衝的細節;這部分記憶體由jemalloc分配,因此會統計在used_memory中。
  • 記憶體碎片
    記憶體碎片是Redis在分配、回收物理記憶體過程中產生的。例如,如果對資料的更改頻繁,而且資料之間的大小相差很大,可能導致redis釋放的空間在實體記憶體中並沒有釋放,但redis又無法有效利用,這就形成了記憶體碎片。
    = R e d i s / t o t a l . a l l o c a t e d 記憶體碎片率=Redis程序佔用記憶體/當前所佔記憶體`total.allocated`
    記憶體碎片涉及到記憶體碎片率fragmentation,該值對於檢視記憶體是否夠用比較重要:

該值一般>1,數值越大,說明記憶體碎片越多。如果<1,說明Redis佔用了虛擬記憶體,而虛擬記憶體是基於磁碟的,速度會變慢,所以如果<1,就需要特別注意是否是記憶體不足了。
一般來說,mem_fragmentation_ratio在1.03左右是比較健康的狀態(對於jemalloc來說);上面截圖中的mem_fragmentation_ratio值很大,是因為還沒有向Redis中存入資料,Redis程序本身執行的記憶體使得used_memory_rss 比used_memory大得多。

Redis資料記憶體

在瞭解了Redis記憶體模型之後,我們可以知道,Redis所佔記憶體不僅僅時key-value,很包括其他記憶體佔用。現在我們就來了解下Redis資料記憶體是如何佔用的。

Redis資料記憶體分配

Redis資料記憶體除了包括key-value,還包括dicEntryredisObjectSDS等。

  • dicEntry:Redis是Key-Value資料庫,因此對每個鍵值對都會有一個dictEntry,裡面儲存了指向Key和Value的指標;next指向下一個dictEntry,與本Key-Value無關。
  • key:key的值並不是直接以字串儲存,而是儲存在SDS結構中。
  • redisObject:value的值既不是直接以字串儲存,也不是像Key一樣直接儲存在SDS中,而是儲存在redisObject中。
  • SDS:Redis沒有直接使用C字串(即以空字元’\0’結尾的字元陣列)作為預設的字串表示,而是使用了SDS。SDS是簡單動態字串(Simple Dynamic String)的縮寫。

Redis資料記憶體計算

String

公式:
= d i c t E n t r y + r e d i s O b j e c t + k e y S D S + v a l S D S × k e y + b u c k e t × 總記憶體消耗 = (dictEntry大小 + redisObject大小 +key_SDS大小 +val_SDS大小)×key個數 + bucket個數 ×指標大小
公式:
= d i c t E n t r y + r e d i s O b j e c t + k e y S D S + v a l S D S 單個記憶體消耗=dictEntry大小 + redisObject大小 +key_SDS大小 +val_SDS大小
jemalloc在分配記憶體塊時會分配大於實際值的2n的值,例如實際值時6位元組,那麼會分配8位元組

資料型別 佔用量
dicEntry 24位元組,jemalloc會分配32位元組的記憶體塊
redisObject 16位元組
key_SDS key的長度+9,jemalloc分配>=該值的2n的值
val_SDS value的長度+9,jemalloc分配>=該值的2n的值
key的個數 所有的key的個數
bucket個數 大於key的個數的2n次方,例如key個數是2000,那麼bucket=2048
指標大小 8byte

例如執行set user.name=admin,需要記憶體計算如下所示:
t o t a l = 32 + 16 + ( 9 + 9 = 18 &gt; 32 ) + ( 5 + 9 = 14 &gt; 16 ) = 96 total=32+16+(9+9=18-&gt;32)+(5+9=14-&gt;16)=96位元組

Hash

Hash結構在使用HTable資料型別時,value並不是指向一個SDS,而實又一個Dict結構。Dict結構儲存了Hash物件的鍵值對。
一個hmset命令最終會產生以下幾個消耗記憶體的結構:

  • 1個dictEntry結構,24位元組,負責儲存當前的雜湊物件;
  • 1個SDS結構,(key長度 + 9)位元組,用作key字串;
  • 1個redisObject結構,16位元組,指向當前key下屬的dict結構;
  • 1個dict結構,88位元組,負責儲存雜湊物件的鍵值對;
  • n個dictEntry結構,24×n位元組,負責儲存具體的field和value,n等於field個數;
  • n個redisObject結構,16×n位元組,用作field物件;
  • n個redisObject結構,16×n位元組,用作value物件;
  • n個SDS結構,(field長度 + 9)× n位元組,用作field字串;
  • n個SDS結構,(value長度 + 9)× n位元組,用作value字串;

公式:
= [ r e d i s O b j e c t × 2 + f i e l d S D S + v a l S D S + d i c t E n t r y × f i e l d + f i e l d b u c k e t × + d i c t + r e d i s O b j e c t + k e y S D S + d i c t E n t r y ] × k e y + k e y b u c k e t × 總記憶體消耗 = [(redisObject大小 ×2 +field SDS大小 +val SDS大小 + dictEntry大小)× field個數 +field bucket個數× 指標大小 + dict大小 + redisObject大小 +key SDS大小 + dictEntry大小 ] × key個數 +key bucket個數×指標大小

SortedSet

一個zadd命令最終會產生以下幾個消耗記憶體的結構:

  • 1個dictEntry結構,24位元組,負責儲存當前的有序集合物件;
  • 1個SDS結構,(key長度 + 9)位元組,用作key字串;
  • 1個redisObject結構,16位元組,指向當前key下屬的zset結構;
  • 1個zset結構,16位元組,負責儲存下屬的dict和zskiplist結構;
  • 1個dict結構,88位元組,負責儲存集