Redis內存碎片率調優(yōu)處理方式
1.背景概述
在生產環(huán)境中Redis Cluster集群觸發(fā)了內存碎片化的告警(碎片率>1.5),集群節(jié)點分布三臺宿主機六個節(jié)點三主三從架構,Redis版本是6.2.X。
2.redis內存碎片的形成
內存碎片形成的原因主要有2點:
- 內因:操作系統(tǒng)的內存分配機制。
- 外因:Redis的負載特征造成。
內因:
內存分配器會按照固定大小來分配內存,而不是按需分配。例如Linux下默認是4KB,開啟內存大頁機制后就變成2MB。
Redis中使用jemalloc分配器來分配內存。它會按照一系列固定大小的內存來進行分配。例如當Redis中需要申請一個20B大小的空間來保存數據,那么jemalloc分配器就會分配32B:
倘若此時應用還要寫入5B大小的數據,那么無需申請額外的空間。
倘若此時應用還要寫入20B大小的數據,那么必須在申請額外的空間了,此時就會有產生內存碎片的風險(之前分配的32B中,10B就是內存碎片了)
外因:
我們有一個Redis實例,里面有著不同大小的鍵值對,那么根據內存分配器的分配機制來看。就有可能分配著不同大小的連續(xù)內存空間。
另一方面,我們對鍵值對也有可能有著不同的操作,增刪改查。
如圖所示

上圖中,白色部分的就是內存碎片,可以看出大小不一的鍵值對以及修改刪除操作導致產生了內存碎片。
3.清理內存碎片
清理內存碎片之前,首先應該做的就是判斷是否有內存碎片:
我們可以登錄Redis后使用INFO MEMORY命令查看。mem_fragmentation_ratio參數的值就是內存碎片化的值。
mem_fragmentation_ratio代表Redis實例當前的內存碎片率。其計算公式為:
mem_fragmentation_ratio = used_memory_rss / used_memory
- used_memory_rss:操作系統(tǒng)實際分配Redis的物理內存空間。
- used_memory:Redis為了保存數據而實際申請的空間。
針對mem_fragmentation_ratio,有兩個參考:
- mem_fragmentation_ratio ∈ (1, 1.5]:屬于合理范圍內,暫時可以放放。
- mem_fragmentation_ratio ∈ (1.5, +∞):表明內存碎片率超過了50%,需要采取措施降低內存碎片率。
那么如何清理內存碎片呢(一般不會重啟實例,因為生產上往往不允許這種神操作出現),在Redis4.0版本以后,Redis提供了內置的內存碎片清理機制。
#內存碎片回收機制使用 activedefrag yes # active-defrag-ignore-bytes與active-defrag-threshold-lower兩個條件同時滿足會觸發(fā)內存碎片清理,當有一個不滿足則停止清理 # 啟動活動碎片整理的最小碎片浪費量,內存碎片的字節(jié)數達到100M時開始清理 active-defrag-ignore-bytes 100mb # 啟動活動碎片整理的最小碎片百分比,內存碎片空間占操作系統(tǒng)分配給redis的總空間比例達到 10% 時開始清理 active-defrag-threshold-lower 10 # 內存碎片超過 100%,則盡最大努力整理 active-defrag-threshold-upper 100 # 以CPU百分比表示的碎片整理的最小工作量,自動清理過程所用CPU時間的比例不低于5%,保證能正常清理 active-defrag-cycle-min 5 # 自動清理過程所用CPU時間的比例不高于75%,超過75%停止清理,避免redis主線程阻塞 active-defrag-cycle-max 75
以上參數可根據主機資源配置及應用場景自行調整。
除此之外,值得注意的是,雖然Redis提供了這樣的自動內存清理機制,能夠帶來清理內存碎片的好處,但是與此同時的必定有著其對應的犧牲,也就是性能影響問題。
Redis提供了內置的內存碎片清理機制的使用前提是在編譯的過程中添加了內存分配器參數MALLOC=jemalloc
make MALLOC=jemalloc
4.擴展
當然我們在生產環(huán)境使用Redis過程中也可以使用自帶的命令進行內存使用情況的診斷,便于我們及時對問題進行優(yōu)化處理。
我們可以使用MEMORY help查看:
127.0.0.1:6379> MEMORY help 1) MEMORY <subcommand> arg arg ... arg. Subcommands are: 2) DOCTOR - Return memory problems reports. 3) MALLOC-STATS -- Return internal statistics report from the memory allocator. 4) PURGE -- Attempt to purge dirty pages for reclamation by the allocator. 5) STATS -- Return information about the memory usage of the server. 6) USAGE <key> [SAMPLES <count>] -- Return memory in bytes used by <key> and its value. Nested values are sampled up to <count> times (default: 5).
MEMORY STATS
redis的內存使用不只包含全部的key-value數據,還有描述這些key-value的元信息,以及許多管理功能的消耗,好比持久化、主從復制,經過MEMORY STATS能夠更好的了解到redis的內存使用情況
127.0.0.1:6379> MEMORY STATS
1) "peak.allocated"
2) (integer) 13749176
3) "total.allocated"
4) (integer) 7308872
5) "startup.allocated"
6) (integer) 791424
7) "replication.backlog"
8) (integer) 0
9) "clients.slaves"
10) (integer) 0
11) "clients.normal"
12) (integer) 2587994
13) "aof.buffer"
14) (integer) 0
15) "lua.caches"
16) (integer) 1912
17) "db.0"
18) 1) "overhead.hashtable.main"
2) (integer) 285216
3) "overhead.hashtable.expires"
4) (integer) 1568
19) "overhead.total"
20) (integer) 3668114
21) "keys.count"
22) (integer) 5492
23) "keys.bytes-per-key"
24) (integer) 1186
25) "dataset.bytes"
26) (integer) 3640758
27) "dataset.percentage"
28) "55.861713409423828"
29) "peak.percentage"
30) "53.158615112304688"
31) "allocator.allocated"
32) (integer) 7323560
33) "allocator.active"
34) (integer) 8429568
35) "allocator.resident"
36) (integer) 12427264
37) "allocator-fragmentation.ratio"
38) "1.1510205268859863"
39) "allocator-fragmentation.bytes"
40) (integer) 1106008
41) "allocator-rss.ratio"
42) "1.4742468595504761"
43) "allocator-rss.bytes"
44) (integer) 3997696
45) "rss-overhead.ratio"
46) "0.65557020902633667"
47) "rss-overhead.bytes"
48) (integer) -4280320
49) "fragmentation"
50) "1.1209555864334106"
51) "fragmentation.bytes"
52) (integer) 879088一共有15項內容,內存使用量均以字節(jié)為單位
1. peak.allocated
redis啟動到如今,最多使用過多少內存。
2. total.allocated
當前使用的內存總量。
3. startup.allocated
redis啟動初始化時使用的內存,有不少讀者會比較奇怪,為何個人redis啟動之后什么都沒作就已經占用了幾十MB的內存?
這是由于redis自己不只存儲key-value,還有其余的內存消耗,好比共享變量、主從復制、持久化和db元信息,下面各項會有詳細介紹。
4. replication.backlog
主從復制backlog使用的內存,默認10MB,backlog只在主從斷線重連時發(fā)揮做用,主從復制自己并不依賴此項。
5. clients.slaves
主從復制中全部slave的讀寫緩沖區(qū),包括output-buffer(也即輸出緩沖區(qū))使用的內存和querybuf(也即輸入緩沖區(qū)),這里簡單介紹一下主從復制:app
redis把一次事件循環(huán)中,全部對數據庫發(fā)生更改的內容先追加到slave的output-buffer中,在事件循環(huán)結束后統(tǒng)一發(fā)送給slave。
那么主從之間就不免會有數據的延遲,若是主從之間鏈接斷開,重連時為了保證數據的一致性就要作一次全量同步,這顯然是不夠高效的。
backlog就是為此而設計,master在backlog中緩存一部分主從復制的增量數據,斷線重連時若是slave的偏移量在backlog中,那就能夠只把偏移量以后的增量數據同步給slave便可,避免了全量同步的開銷。
6. clients.normal
除slave外全部其余客戶端的讀寫緩沖區(qū)。
有時候一些客戶端讀取不及時,就會形成output-buffer積壓占用內存過多的狀況,能夠經過配置項client-output-buffer-limit來限制,當超過閾值以后redis就會主動斷開鏈接以釋放內存,slave亦是如此。
7. aof.buffer
此項為aof持久化使用的緩存和aofrewrite時產生的緩存之和,固然若是關閉了appendonly那這項就一直為0:less
redis并非在有寫入時就當即作持久化的,而是在一次事件循環(huán)內把全部的寫入數據緩存起來,待到事件循環(huán)結束后再持久化到磁盤。
aofrewrite時緩存增量數據使用的內存,只在aofrewrite時才會使用。
能夠看出這一項的大小與寫入流量成正比。ide
8. db.0
redis每一個db的元信息使用的內存,這里只使用了db0,因此只打印了db0的內存使用狀態(tài),當使用其余db時也會有相應的信息。優(yōu)化
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
內存碎片率
MEMORY USAGE
使用方法:MEMORY USAGE <key> [SAMPLES <count>]
命令參數很少,經過字面意思也能夠看出來是評估指定key的內存使用狀況。samples是可選參數默認為5,以hash為例看下:
127.0.0.1:6379> HGETALL 9527 1) "name" 2) "zhouxingxing" 3) "age" 4) "50" 5) "city" 6) "hongkong" 127.0.0.1:6379> MEMORY USAGE 9527 (integer) 101
首先相似于上一節(jié)中的overhead.hashtable.main,要計算hash的元信息內存,包括hash表的大小以及全部dictEntry的內存占用信息。
與overhead.hashtable.main不一樣的是,每一個dictEntry中key-value都是字符串,因此沒redisObject的額外消耗。
在評估真正的數據內存大小時redis并無去遍歷全部key,而是采用的抽樣估算:隨機抽取samples個key-value對計算其平均內存占用,再乘以key-value對的個數即獲得結果。
試想一下若是要精確計算內存占用,那么就須要遍歷全部的元素,當元素不少時就是使redis阻塞,因此請合理設置samples的大小。
其余數據結構的計算方式相似于hash,此處就再也不贅述。
MEMORY DOCTOR
此項子命令是做者給出的關于redis內存使用方面的建議,在不一樣的容許狀態(tài)下會有不一樣的分析結果。
首先是沒問題的狀況
運行狀態(tài)良好
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倍于目前內存使用量,此時內存碎片率可能會比較高,須要注意:
Sam, I detected a few issues in this Redis instance memory implants:
* 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.
I'm here to keep you safe, Sam. I want to help you.
內存碎片率太高超過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緩沖區(qū)的平均內存超過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.
普通客戶端緩沖區(qū)的平均內存超過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.
MEMORY MALLOC-STATS
打印內存分配器狀態(tài),只在使用jemalloc時有用。
MEMORY PURGE
請求分配器釋放內存,只對jemalloc生效。
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
redis啟動報錯Can‘t?open?the?log?file:?No?such?file?or?d
這篇文章主要介紹了redis啟動報錯Can‘t?open?the?log?file:?No?such?file?or?directory問題及解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11

