Redis 深入理解內存回收策略設計思想(過期鍵刪除策略和內存淘汰策略)

過期鍵刪除策略

一般一個鍵過期了,有以下三種不同的策略可以進行刪除:

(1)定時刪除:在設置鍵的過期時間的同時,創建一個定時器(timer),讓定時器在鍵的過期時間來臨時,立即執行對鍵的刪除操作。

定時刪除策略對內存是最友好的:通過使用定時器,定時刪除策略可以保證過期鍵會儘可能快地被刪除,並釋放過期鍵所佔的內存

缺點:對CPU時間最不友好,在過期鍵較多的情況下,刪除過期鍵這一行爲可能會佔用相當一部分CPU,在內存不緊張但是CPU時間非常緊張的情況下,將對CPU時間用在刪除和當前任務無關的過期鍵上,無疑會對服務器的響應時間和吞吐量造成影響,另外創建一個定時器需要用到Redis服務器中的時間時間,而當前時間時間的實現方式爲無需鏈表,查找一個時間的時間複雜度爲O(N),並不能高效地處理大量時間事件,因此讓Redis創建大量的定時器來實現定時刪除策略,現階段並不現實

(2)惰性刪除:放任鍵過期不管,但是每次從鍵空間中獲取鍵時,都檢查取得的鍵是否過期,如果過期的話,就刪除鍵;如果沒有過期,就返回該鍵

惰性刪除策略對CPU來說是最友好的,程序只會在取出鍵時纔對鍵進行過期檢查,可以保證刪除過期鍵的操作只會在非做不可得情況下進行,並且刪除得目標僅限於當前處得鍵,這個策略不會在刪除其他無關得過期鍵上花費任何CPU事件

缺點:對內存不友好,如果一個鍵已經過期,而這個鍵又仍保留在數據庫中,那麼只要這個過期鍵不被刪除,它所佔用得內存就不會釋放

在使用惰性刪除策略時,如果數據庫中存在非常多得過期鍵,而這些過期鍵又恰好沒有被查詢,那麼它們也許會永遠不會被刪除(除非用戶手動執行FLUSHDB)並佔用內存,我們可以把這種情況看作是一種內存泄漏(無用得垃圾數據佔用了大量的內存),而服務器卻不能自己去釋放它們,這對於運行狀態非常依賴於內存的Redis服務器來說(注:Redis是內存型數據庫),肯定不是一個好消息

(3)定期刪除:每隔一段時間,程序就對數據庫進行一次檢查,刪除裏面的過期鍵。至於刪除多少過期鍵,以及檢查多少個數據庫,由算法實現

定期刪除和惰性刪除都存在明顯的缺陷,定期刪除策略是對前兩種策略的一種整合和折中,定期刪除策略每隔一段時間執行一次刪除過期鍵操作,並通過限制刪除操作執行的時長和頻率來減少刪除操作對CPU的影響,通過定期刪除過期鍵,定期刪除策略可以有效地減少了因爲過期鍵而帶來的內存浪費

定期刪除策略的難點在於確定刪除操作執行的時長和頻率:

  • 如果刪除操作執行過於頻繁,或者執行時間過長,定期刪除策略就會退化爲定時刪除策略,以至於CPU時間過多地消耗在刪除過期鍵上面
  • 如果刪除操作執行得太少,或者執行時間太多,定期刪除策略又會和惰性刪除策略一樣,出現內存浪費得情況

因此,如果採用定期刪除策略,服務器必須根據實際情況,合理地設置刪除操作得執行時長和執行頻率

在Redis中,鍵過期刪除策略採用的鍵刪除策略是惰性刪除和定期刪除

Redis中惰性刪除實現

過期鍵的惰性刪除策略由db.c/expireIfNeeded函數實現,所有讀寫數據庫的Redis命令在執行之前都會調用expireIfNeeded函數對輸入鍵進行檢查

  • 如果輸入見已經過期,那麼expireIfNeeded函數將輸入鍵從數據庫中刪除。
  • 如果輸入鍵未過期,那麼expireIfNeeded函數不做動作

expireIfNeeded函數類似於一個過濾器,它可以在命令真正執行之前,過濾掉過期的輸入鍵,從而避免命令接觸到過期鍵

Redis定期刪除策略

過期鍵的定期刪除策略由redis.c/activeExpireCycle函數實現,每當Redis的服務器週期性操作redis.c/serverCron函數時執行時,activeExpireCycle函數被調用,它在規定的時間內,分多次遍歷服務器中的各個數據庫,從數據庫的expires字典中隨機檢查一部分鍵的過期時間,並刪除其中的過期鍵。

簡單理解:Redis內部維護一個定時任務,默認每秒運行10次(通過配置hz控制)。定時任務中刪除過期鍵邏輯採用了自適應算法,根據鍵的過期比例,使用快慢兩種速率模式回收鍵。

比如:

  1. 定時任務在每個數據庫空間隨機檢查20個鍵,當發現過期時刪除對應的鍵。
  2. 如果超過檢查數25%的鍵過期,循環執行回收邏輯直到不足25%或運行超時爲止,慢模式下超時時間爲25ms。
  3. 如果之前回收鍵邏輯超時,則在Redis觸發內部事件之前再次以快模式運行回收過期鍵任務,快模式下超時時間爲1ms且2s內只能運行1次。
  4. 快慢兩種模式內部刪除邏輯相同,只是執行的超時時間不同。

Redis內存溢出淘汰控制策略

當然Redis中並不是所有鍵都會設置過期時間,那這些鍵會一直的保存在內存中?內存不會炸?

因此,當Redis所用內存達到maxmemory上限時會觸發相應的溢出控制策略。具體策略受maxmemory-policy參數控制,Redis支持6種策略,如下所示:

  • noeviction:默認策略,當內存不足以容納新寫入數據時,新寫入操作會報錯。應該沒人用吧。
  • allkeys-lru:當內存不足以容納新寫入數據時,在鍵空間中,移除最近最少使用的 Key。推薦使用,目前項目在用這種。
  • allkeys-random:當內存不足以容納新寫入數據時,在鍵空間中,隨機移除某個 Key。應該也沒人用吧,你不刪最少使用Key,去隨機刪。
  • volatile-lru:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,移除最近最少使用的 Key。這種情況一般是把 Redis 既當緩存,又做持久化存儲的時候才用。不推薦。
  • volatile-random:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,隨機移除某個 Key。依然不推薦。
  • volatile-ttl:當內存不足以容納新寫入數據時,在設置了過期時間的鍵空間中,有更早過期時間的 Key 優先移除。不推薦。如果沒有對應的鍵,則回退到noeviction策略。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章