面試官:為什麼刪除快取後,Redis記憶體佔用依然很高?

👉 這是一個或許對你有用的社群
🐱 一對一交流/面試小冊/簡歷最佳化/求職解惑,歡迎加入芋道快速開發平臺知識星球。下面是星球提供的部分資料:
👉這是一個或許對你有用的開源專案
國產 Star 破 10w+ 的開源專案,前端包括管理後臺 + 微信小程式,後端支援單體和微服務架構。
功能涵蓋 RBAC 許可權、SaaS 多租戶、資料許可權、商城、支付、工作流、大屏報表、微信公眾號、ERPCRMAI 大模型等等功能:
  • Boot 多模組架構:https://gitee.com/zhijiantianya/ruoyi-vue-pro
  • Cloud 微服務架構:https://gitee.com/zhijiantianya/yudao-cloud
  • 影片教程:https://doc.iocoder.cn
【國內首批】支援 JDK 17/21 + SpringBoot 3.3、JDK 8/11 + Spring Boot 2.7 雙版本 

前言

上週剛來了個應屆小師弟,組長說讓我帶著,週二問了我這樣一個問題:師兄啊,我用top命令看了下伺服器的記憶體佔用情況,發現Redis記憶體佔用嚴重,於是我就刪除了大部分不用的keys,為什麼記憶體佔用還是很嚴重,並沒有釋放呢?
嗯?為什麼呢?今天就帶著這個問題來介紹一下如何正確釋放Redis的記憶體。
基於 Spring Boot + MyBatis Plus + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/ruoyi-vue-pro
  • 影片教程:https://doc.iocoder.cn/video/

什麼是記憶體碎片?

記憶體碎片這個概念應該不是第一聽說了,熟悉JVM或者作業系統的應該都熟悉,以火車賣票為例,一個車廂128個車位,由於高峰期,只剩餘兩個位置了,但是此時三個人想要坐在一起,能夠吹吹牛批,喝喝酒的,那麼這三個人肯定不會買這節車廂的兩個位置了,此時這兩個位置可以稱之為座位碎片 。
作業系統中對於記憶體分配也是一樣的,比如應用需要申請一塊連續N個位元組的空間,雖然剩餘記憶體總量大於N個位元組,但是沒有一塊連續的記憶體空間是N個位元組,那麼剩餘的空間就是記憶體碎片。如下圖:
上圖中的空閒3個位元組和空閒2個位元組都是記憶體碎片。
那麼什麼原因會造成記憶體碎片呢?這個其實大致分為兩個原因,一個是作業系統的記憶體分配策略,一個是Redis自身原因,下面就這兩個原因詳細分析。
基於 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現的後臺管理系統 + 使用者小程式,支援 RBAC 動態許可權、多租戶、資料許可權、工作流、三方登入、支付、簡訊、商城等功能
  • 專案地址:https://github.com/YunaiV/yudao-cloud
  • 影片教程:https://doc.iocoder.cn/video/

記憶體分配器的分配策略

記憶體分配器的分配策略一般是按照固定大小來分配記憶體,而不是按照應用程式申請的記憶體空間按需分配。比如8位元組、16位元組、32位元組……
Redis提供了多種的記憶體分配策略,比如libcjemalloctcmalloc,預設使用jemalloc
jemalloc這種分配策略,是按照固定的空間分配,比如8位元組、32位元組….2KB、4KB等。當應用程式申請的記憶體接近某個固定值的時候,jemalloc則會分配固定的大小。比如申請了6位元組,則會分配8位元組的空間。
這種分配的方式的好處很明顯,則會減少記憶體分配的次數,比如申請了20位元組的記憶體,實際分配的是32位元組的記憶體空間,當應用再寫入10位元組的資料時,則不會再次分配,剩餘的12位元組足夠用了。這樣就避免了一次的記憶體分配。如下圖:
但是壞處也很明顯,申請的和分配的空間不一樣,則剩餘的空間很可能形成記憶體碎片,一旦記憶體碎片多了,記憶體利用率也會隨之降低,這是很可怕的。

Redis自身的原因

Redis作為鍵值對儲存的資料庫,本身鍵值對的大小就是不確定的,正如上面的例子中,Redis申請了20位元組的空間,但實際分配卻是32位元組,那麼剩餘的12位元組則會被閒置成為記憶體碎片。如下圖:
上圖中剩餘12個位元組空間則是閒置的,很有可能成為記憶體碎片,因此鍵值對大小不同則會造成一定的記憶體碎片,這是第一個原因。
第二個原因其實理解起來很簡單,鍵值對的修改或者刪除肯定會造成空間的擴容或者釋放;
一方面,如果修改後的鍵值對變大或者變小了,勢必會將佔用的空間擴大或者釋放不用的空間,如下圖:
上圖中鍵值對修改後變小了,從原來的10個位元組變成了7個位元組,從而釋放了3個位元組,此時剩餘了5個位元組的空閒空間。
另一方面,如果鍵值對刪除了,則會釋放掉佔用的空間,形成空閒空間。

如何判斷存在記憶體碎片?

這個對於運維人員來說很重要,一旦出現Redis執行緩慢或者阻塞了,一定需要先判斷記憶體的佔用情況,而不是說胡亂的重啟Redis。
Redis自身提供了INFO命令,可以用來查詢記憶體的使用情況,命令如下:

INFO memory

# Memory

used_memory:1073741736

used_memory_human:1024.00M

used_memory_rss:1997159792

used_memory_rss_human:1.86G

mem_fragmentation_ratio:1.86

上面的各種屬性含義如下:

mem_fragmentation_ratio`這個指標很清楚的展示了當前記憶體的碎片率,比如Redis申請了1000位元組,但是作業系統實際分配的記憶體1800個位元組,則`mem_fragmentation_ratio=1800/1000=1.8

從上文也知道了,由於記憶體分配器的侷限性,實際分配的記憶體絕大部分都是大於實際申請的記憶體,則如何透過mem_fragmentation_ratio這個值來衡量呢?這個值的範圍在多少是正常的呢?
作者這裡參照了許多開發人員的建議,列出了以下經驗閥值:
  1. >1&&<1.5:在這個範圍內是合理的,畢竟大部分情況下作業系統分配的記憶體總是總是大於實際申請的空間。
  2. >1.5:這表明記憶體碎片率已經超過50%,此時需要採取一些措施來降低碎片率了。
  3. <1:what?表明實際分配的記憶體小於申請的記憶體了,很顯然記憶體不足了,這樣會導致部分資料寫入到Swap中,之後Redis訪問Swap中的資料時,延遲會變大,效能會降低。

如何清理記憶體碎片?

既然存在記憶體碎片,那麼的一定有方法清除記憶體碎片,最簡單的方法則是重啟Redis
但是這也存在一些風險,如下;
  1. 如果Redis未持久化,則資料會丟失(忽略從後端恢復)
  2. 即使持久化了,但是恢復資料時長不定,這個要根據AOF和RDB檔案大小決定,在恢復階段則無法提供服務。
好在Redis 4.0-RC3版本之後,Redis自身提供了一種清除記憶體碎片的方法
清除的原理很簡單,透過複製複製將不連續的存放的資料搬到一起形成一塊連續的記憶體空間,如下圖:
如上圖,清除之前AB不是連續的,中間隔著兩個位元組空閒1,但是在執行清除記憶體碎片操作之後,Redis複製了B空閒1,釋放掉之前B的空間,此時空閒1空閒2則變成了連續的空閒空間了。
那麼問題來了,這種方式固然好,但是對於單執行緒的Redis來說,透過這種複製複製的方式顯然是一種耗時的操作,效能大大降低,那麼有什麼好的方法呢?
Redis提供了引數配置,可以控制清除記憶體碎片的時機,命令如下:

config 

set

 activedefrag yes

以上命令啟動自動清理,但是具體什麼時候清理,還要受以下兩個引數的影響:
  1. active-defrag-ignore-bytes 400mb:如果記憶體碎片達到了400mb,開始清理(自定義)
  2. active-defrag-threshold-lower 20:記憶體碎片空間佔作業系統分配給 Redis 的總空間比例達到20%時,開始清理(自定義)
以上兩個引數只有全部滿足才會開始清理
除了以上觸發清理記憶體碎片的引數,Redis還提供了兩個引數來保證在清理過程中不影響處理正常的請求,如下:
  1. active-defrag-cycle-min 25:表示自動清理過程所用 CPU 時間的比例不低於 25%,保證清理能正常開展
  2. active-defrag-cycle-max 75:表示自動清理過程所用 CPU 時間的比例不高於 75%,一旦超過,就停止清理,從而避免在清理時,大量的記憶體複製阻塞 Redis,導致響應延遲升高。
以上兩個引數控制了清理過程中的CPU時間佔比,保證了正常處理請求不受影響

總結

本文以師弟的一個疑問開頭介紹了刪除資料導致記憶體佔用還是很高的原因是存在記憶體碎片,導致記憶體碎片大致分為兩個原因,如下:
  1. 記憶體分配策略侷限性,一般都會分配固定的空間大小,導致實際分配的記憶體空間大於實際申請的,從而多出了許多不連續的空閒記憶體塊。
  2. 鍵值對的修改、刪除導致了記憶體的擴容或者釋放,導致多餘的不連續的空閒記憶體塊。
介紹瞭如何透過INFO memory命令檢視記憶體的碎片率,透過mem_fragmentation_ratio的經驗閥值來判斷異常。
介紹了Redis清理記憶體碎片的方式以、自動清理的兩個觸發條件、保證正常處理請求的兩個控制CPU時間的引數。

歡迎加入我的知識星球,全面提升技術能力。
👉 加入方式,長按”或“掃描”下方二維碼噢
星球的內容包括:專案實戰、面試招聘、原始碼解析、學習路線。
文章有幫助的話,在看,轉發吧。
謝謝支援喲 (*^__^*)

相關文章