redis4.0之MEMORY命令詳解

chenfeng發表於2019-01-02

前言

在過去,檢視redis的記憶體使用狀態只有info memory命令,而且也只有一些基礎資訊,想要獲取全域性資訊就有些困難。4.0開始redis提供了MEMORY命令,一切都變得簡單起來。


MEMORY命令

MEMORY命令一共有5個子命令,可以透過MEMORY HELP來檢視:


127.0.0.1:6379> memory help

1) "MEMORY DOCTOR - Outputs memory problems report"

2) "MEMORY USAGE <key> [SAMPLES <count>] - Estimate memory usage of key"

3) "MEMORY STATS - Show memory usage details"

4) "MEMORY PURGE - Ask the allocator to release memory"

5) "MEMORY MALLOC-STATS - Show allocator internal stats"

接下來我們從MEMORY STATS開始,一一介紹各個子命令的功能。


1. MEMORY STATS

首先,我們需要明確一個概念,redis的記憶體使用不僅包含所有的key-value資料,還有描述這些key-value的元資訊,以及許多管理功能的消耗,比如持久化、主從複製,透過MEMORY STATS可以更好的瞭解到redis的記憶體使用狀況。


這裡我們啟動了一個開啟持久化功能並且帶slave的redis,向其中隨機寫入了一些資料(某些資料還帶有過期時間),以便讀者可以更好的瞭解redis的記憶體使用,接下來執行MEMORY STATS命令:


127.0.0.1:6379> memory stats

 1) "peak.allocated"

 2) (integer) 423995952

 3) "total.allocated"

 4) (integer) 11130320

 5) "startup.allocated"

 6) (integer) 9942928

 7) "replication.backlog"

 8) (integer) 1048576

 9) "clients.slaves"

10) (integer) 16858

11) "clients.normal"

12) (integer) 49630

13) "aof.buffer"

14) (integer) 3253

15) "db.0"

16) 1) "overhead.hashtable.main"

    2) (integer) 5808

    3) "overhead.hashtable.expires"

    4) (integer) 104

17) "overhead.total"

18) (integer) 11063904

19) "keys.count"

20) (integer) 94

21) "keys.bytes-per-key"

22) (integer) 12631

23) "dataset.bytes"

24) (integer) 66416

25) "dataset.percentage"

26) "5.5934348106384277"

27) "peak.percentage"

28) "2.6251003742218018"

29) "fragmentation"

30) "1.1039986610412598"

一共有15項內容,記憶體使用量均以位元組為單位,我們一個一個來看:


1. peak.allocated

redis啟動到現在,最多使用過多少記憶體。

2. total.allocated

當前使用的記憶體總量。

3. startup.allocated

redis啟動初始化時使用的記憶體,有很多讀者會比較奇怪,為什麼我的redis啟動以後什麼都沒做就已經佔用了幾十MB的記憶體?

這是因為redis本身不僅儲存key-value,還有其他的記憶體消耗,比如共享變數、主從複製、持久化和db元資訊,下面各項會有詳細介紹。

4. replication.backlog

主從複製backlog使用的記憶體,預設10MB,backlog只在主從斷線重連時發揮作用,主從複製本身並不依賴此項。

5. clients.slaves

主從複製中所有slave的讀寫緩衝區,包括output-buffer(也即輸出緩衝區)使用的記憶體和querybuf(也即輸入緩衝區),這裡簡單介紹一下主從複製:


redis把一次事件迴圈中,所有對資料庫發生更改的內容先追加到slave的output-buffer中,在事件迴圈結束後統一傳送給slave。

那麼主從之間就難免會有資料的延遲,如果主從之間連線斷開,重連時為了保證資料的一致性就要做一次全量同步,這顯然是不夠高效的。backlog就是為此而設計,master在backlog中快取一部分主從複製的增量資料,斷線重連時如果slave的偏移量在backlog中,那就可以只把偏移量之後的增量資料同步給slave即可,避免了全量同步的開銷。

6. clients.normal

除slave外所有其他客戶端的讀寫緩衝區。

有時候一些客戶端讀取不及時,就會造成output-buffer積壓佔用記憶體過多的情況,可以透過配置項client-output-buffer-limit來限制,當超過閾值之後redis就會主動斷開連線以釋放記憶體,slave亦是如此。

7. aof.buffer

此項為aof持久化使用的快取和aofrewrite時產生的快取之和,當然如果關閉了appendonly那這項就一直為0:


redis並不是在有寫入時就立即做持久化的,而是在一次事件迴圈內把所有的寫入資料快取起來,待到事件迴圈結束後再持久化到磁碟。

aofrewrite時快取增量資料使用的記憶體,只在aofrewrite時才會使用,aofrewrite機制可以參考之前的文章《redis4.0之利用管道最佳化aofrewrite》。

可以看出這一項的大小與寫入流量成正比。


8. db.0

redis每個db的元資訊使用的記憶體,這裡只使用了db0,所以只列印了db0的記憶體使用狀態,當使用其他db時也會有相應的資訊。


db的元資訊有以下三項:

a) redis的db就是一張hash表,首先就是這張hash表使用的記憶體(redis使用鏈式hash,hash表中存放所有連結串列的頭指標);


b) 每一個key-value對都有一個dictEntry來記錄他們的關係,元資訊便包含該db中所有dictEntry使用的記憶體;


c) redis使用redisObject來描述value所對應的不同資料型別(string、list、hash、set、zset),那麼redisObject佔用的空間也計算在元資訊中。


overhead.hashtable.main:


db的元資訊也即是以上三項之和,計算公式為:

hashtable + dictEntry + redisObject


overhead.hashtable.expires:


對於key的過期時間,redis並沒有把它和value放在一起,而是單獨用一個hashtable來儲存,但是expires這張hash表記錄的是key-expire資訊,所以不需要`redisObject`來描述value,其元資訊也就少了一項,計算公式為:

hashtable + dictEntry


9. overhead.total

3-8項之和:startup.allocated+replication.backlog+clients.slaves+clients.normal+aof.buffer+dbx

10. dataset.bytes

所有資料所使用的記憶體——也即total.allocated - overhead.total——當前記憶體使用量減去管理類記憶體使用量。

11. dataset.percentage

所有資料佔比,這裡並沒有直接使用total.allocated做分母,而是除去了redis啟動初始化的記憶體,計算公式為:

100 * dataset.bytes / (total.allocated - startup.allocated)


12. keys.count

redis當前儲存的key總量

13. keys.bytes-per-key

平均每個key的記憶體大小,直覺上應該是用dataset.bytes除以keys.count即可,但是redis並沒有這麼做,而是把管理類記憶體也平攤到了每個key的記憶體使用中,計算公式為:


(total.allocated - startup.allocated) / keys.count

14. peak.percentage

當前使用記憶體與歷史最高值比例

15. fragmentation

記憶體碎片率

2. MEMORY USAGE

相信所有redis使用者都希望對每一個key-value的記憶體使用瞭如指掌,然而4.0之前redis並沒有提供一個明確的方法來進行記憶體評估,不過從4.0開始,MEMORY命令實現了這一功能。


首先看下使用方法:MEMORY usage [samples]


命令引數不多,透過字面意思也可以看出來是評估指定key的記憶體使用情況。samples是可選引數預設為5,以hash為例看下其如果工作:


首先類似於上一節中的overhead.hashtable.main,要計算hash的元資訊記憶體,包括hash表的大小以及所有dictEntry的記憶體佔用資訊。

與overhead.hashtable.main不同的是,每個dictEntry中key-value都是字串,所以沒redisObject的額外消耗。在評估真正的資料記憶體大小時redis並沒有去遍歷所有key,而是採用的抽樣估算:隨機抽取samples個key-value對計算其平均記憶體佔用,再乘以key-value對的個數即得到結果。試想一下如果要精確計算記憶體佔用,那麼就需要遍歷所有的元素,當元素很多時就是使redis阻塞,所以請合理設定samples的大小。

其他資料結構的計算方式類似於hash,此處就不再贅述。


3. MEMORY DOCTOR

此項子命令是作者給出的關於redis記憶體使用方面的建議,在不同的允許狀態下會有不同的分析結果:


首先是沒問題的情況

執行狀態良好:


Hi Sam, I can't find any memory issue in your instance. I can only account for what occurs on this base.

redis的資料量很小,暫無建議:


Hi Sam, this instance is empty or is using very little memory, my issues detector can't be used in these conditions. Please, leave for your mission on Earth and fill it with some data. The new Sam and I will be back to our programming as soon as I finished rebooting.

接下來出現的結果就需要注意了

記憶體使用峰值1.5倍於目前記憶體使用量,此時記憶體碎片率可能會比較高,需要注意:


Peak memory: In the past this instance used more than 150% the memory that is currently using. The allocator is normally not able to release memory after a peak, so you can expect to see a big fragmentation ratio, however this is actually harmless and is only due to the memory peak, and if the Redis instance Resident Set Size (RSS) is currently bigger than expected, the memory will be used as soon as you fill the Redis instance with more data. If the memory peak was only occasional and you want to try to reclaim memory, please try the MEMORY PURGE command, otherwise the only other option is to shutdown and restart the instance.

記憶體碎片率過高超過1.4,需要注意:


High fragmentation: This instance has a memory fragmentation greater than 1.4 (this means that the Resident Set Size of the Redis process is much larger than the sum of the logical allocations Redis performed). This problem is usually due either to a large peak memory (check if there is a peak memory entry above in the report) or may result from a workload that causes the allocator to fragment memory a lot. If the problem is a large peak memory, then there is no issue. Otherwise, make sure you are using the Jemalloc allocator and not the default libc malloc.

每個slave緩衝區的平均記憶體超過10MB,原因可能是master寫入流量過高,也有可能是主從同步的網路頻寬不足或者slave處理較慢:


Big slave buffers: The slave output buffers in this instance are greater than 10MB for each slave (on average). This likely means that there is some slave instance that is struggling receiving data, either because it is too slow or because of networking issues. As a result, data piles on the master output buffers. Please try to identify what slave is not receiving data correctly and why. You can use the INFO output in order to check the slaves delays and the CLIENT LIST command to check the output buffers of each slave.

普通客戶端緩衝區的平均記憶體超過200KB,原因可能是pipeline使用不當或者Pub/Sub客戶端處理訊息不及時導致:


Big client buffers: The clients output buffers in this instance are greater than 200K per client (on average). This may result from different causes, like Pub/Sub clients subscribed to channels bot not receiving data fast enough, so that data piles on the Redis instance output buffer, or clients sending commands with large replies or very large sequences of commands in the same pipeline. Please use the CLIENT LIST command in order to investigate the issue if it causes problems in your instance, or to understand better why certain clients are using a big amount of memory.

4. MEMORY MALLOC-STATS

列印記憶體分配器狀態,只在使用jemalloc時有用。


5. MEMORY PURGE

請求分配器釋放記憶體,同樣只對jemalloc生效。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/15498/viewspace-2287122/,如需轉載,請註明出處,否則將追究法律責任。