聊一聊Redis內存碎片處理
Redis內存碎片處理
不知道我們在執行刪除操作時有沒有注意過這樣一個現象,刪除一些bigkey后內存分配器分配的容量并沒有減少,實際容量減少了,這是為什么呢?演示如下:
模擬bigkey刪除
創建生成bigkey的腳本文件createdata.sh
#!/bin/bash
cd /opt/redis/redis-6.0.6/bin/
for i in {1..10000}
do
echo "key${i} ${i}"
redis-cli hset obj key${i} ${i}
done
賦執行權限
[root@zzf993 redis-6.0.6]# chmod +x createdata.sh
### 執行權限如果分不清可以賦予所有權限
[root@zzf993 redis-6.0.6]# chmod 777 createdata.sh
執行createdata.sh腳本,等待執行完畢
查看內存容量
########## 執行createdata.sh腳本前的內存容量
127.0.0.1:6379> info memory
# Memory
used_memory:864192
used_memory_human:843.94K
used_memory_rss:4681728
used_memory_rss_human:4.46M
########## 執行createdata.sh腳本后的內存容量
# Memory
used_memory:1565384
used_memory_human:1.49M
used_memory_rss:5992448
used_memory_rss_human:5.71M
將key為obj的鍵值刪除后,查看內存容量
127.0.0.1:6379> info memory
# Memory
used_memory:896416
used_memory_human:875.41K
used_memory_rss:5746688
used_memory_rss_human:5.48M
刪除bigkey后used_memory_rss波動很小,而used_memory波動很大,因為used_memory_rss是系統向redis分配的內存空間,而used_memory是redis實際使用的內存空間,上述實驗表明redis刪除鍵值后并沒有馬上的將內存空間回收,所以即使刪除了一些bigkey的鍵值,redis占用的空間依然還是那么大,這些空間后續還會不會使用呢?這個需要分情況
- 如果刪除的鍵值空間是連續的那么后續新增鍵值還是可以用來存儲數據。
- 如果刪除的鍵值空間不連續那么這就有可能形成的內存碎片。
那么這些內存碎片能不能重復利用呢?我們可以接著往下面思考。
什么是內存碎片
內存碎片一句話描述就是有那么多內存但是放不下那么多數據,我們以高鐵買票為例解釋,張三、李四、王五去訂票這三個人想訂連在一起的三個座位,而最近一趟車有100個座位,已售97個,剩余三個座位并不相鄰,那么他們三個只能改換下一趟高鐵,因為座位不符合他們的預期,那么這趟車就有三個座位空閑,這就是座位碎片。
將碎片概念放到內存中就是連續空閑的空間放不下那么多數據,如下圖,需要向內存中放入一個3字節的數據,雖然內存中存在3字節空閑的內存,但空閑內存不連續所以無法存放,這就是內存碎片
內存碎片如何產生
內存分配器
在Redis中有多種內存分配策略如libc、jemalloc、tcmalloc,默認使用jemalloc,操作系統的內存分配器根據這些分配策略分配內存,但是內存分配器無法做到按需分配,一般按照固定大小分配,以默認分配策略jemalloc為例,一般按照2的整數次冪分配如2、4、8、16、32等等,如redis申請一個6字節的內存,操作系統就會分配一個8字節的內存給redis使用,那么多出來的2個字節空間,如果后續沒有其它操作,那么這2個字節就是內存碎片。
那這樣分配是不是完全沒有優勢呢?顯然不是,如redis第一次向操作系統申請了24字節的內存,分配器給redis分配了32字節的空間,當下次redis寫入需要8字節以內的空間就不需要再次向操作系統申請了,現有的內存空間完全可以滿足要求,所以這樣的分配方式可以減少向操作系統申請空間分配。
鍵值大小不同
因為內存分配器不是按需分配,而不同的鍵值大小也就會給redis帶來不一樣的碎片,如鍵值key1占用內存5個字節,鍵值key2占用內存7個字節,向操作系統申請時都會給分配8個字節,key1鍵值的碎片就是3個字節,而key2鍵值的碎片就是1個字節。
鍵值的操作
鍵值的修改、刪除也會造成碎片如下所示
碎片信息如何查看
碎片信息redis提供了info memory命令給用戶監控碎片情況
127.0.0.1:6379> info memory
# Memory
used_memory:917256
used_memory_human:895.76K
used_memory_rss:5488640
used_memory_rss_human:5.23M
.......
mem_fragmentation_ratio:6.26
內存信息中mem_fragmentation_ratio指標就是內存的碎片率,碎片率計算如下
mem_fragmentation_ratio = used_memory_rss/used_memory
used_memory_rss:表示是由操作系統分配的內存大小。
used_memory:表示Redis實例占用的內存大小。
如redis向操作系統申請100字節的內存這就是used_memory,操作系統為redis分配128字節的內存這就是used_memory_rss,碎片率就是mem_fragmentation_ratio=1.28。
當mem_fragmentation_ratio<=1.5時,因為操作系統的分配器緣故碎片率避免不了,而且鍵值的修改,刪除也會導致碎片率,所以這算是一個正常范疇。
當mem_fragmentation_ratio>1.5時,相當于碎片率超過了實際占用內存的50%,這就造成了內存的浪費,需要采取一些措施降低碎片率。
注意:如果線上數據顯示mem_fragmentation_ratio<1,證明碎片率低,是不是碎片率越低就越好呢?顯然不是,碎片率小于1說明used_memory_rss操作系統分配的內存少了,也就是說Redis能使用的物理內存不夠了,這就會觸發swap,將內存的數據換到磁盤中,后續客戶端如果訪問了磁盤中的數據將產生延遲。
碎片率如何降低
當Redis版本是4.0以下,那么我們只能通過重啟實例解決問題,但需要注意的是重啟會有部分數據丟失,即使開啟了持久化。
當Redis版本是4.0以上,我們可以通過配置activedefrag yes自動碎片清理來完成,簡單形容就是零換整的思想,將空閑內存碎片合并到一起,形成一片連續的空間,如下所示
不過萬事都有兩面性,開啟自動碎片清理后,會阻塞主線程,所以需要注意清理參數控制,參數如下
active-defrag-ignore-bytes 100mb:碎片清理的最小碎片內存,碎片內存到達100mb后開始清理。
active-defrag-threshold-lower 10:表示內存碎片空間占分配給redis總空間的10%開始清理和active-defrag-ignore-bytes必須同時滿足,有一個不滿足停止自動清理。
active-defrag-cycle-min 25:表示碎片清理占用CPU最少的比例,保證碎片清理可以正常運行。
active-defrag-cycle-max 75:表示碎片清理占用CPU最大的比例,不能讓碎片清理影響業務正常處理。
如果碎片清理期間阻塞了主線程的業務處理,一般需要將active-defrag-cycle-max占用CPU的最大比例調小。