Redis 內存回收

Redis默認無限使用服務器內存,爲防止極端情況下導致系統內存耗盡,建議所有的Redis進程都要配置maxmemory。

Redis的內存回收機制主要體現在以下兩個方面:

  1. 刪除鍵(主動或者刪除到達過期時間的鍵)
  2. 內存使用達到maxmemory上限時觸發內存溢出控制策略。

一、刪除過期鍵對象

Redis所有的鍵都可以設置過期屬性,內部保存在過期字典中。由於進程內保存大量的鍵,維護每個鍵精準的過期刪除機制會導致消耗大量的CPU,對於單線程的Redis來說成本過高,因此Redis採用惰性刪除和定時任務刪除機制實現過期鍵的內存回收。

1.惰性刪除

惰性刪除用於當客戶端讀取帶有超時屬性的鍵時,如果已經超過鍵設置的過期時間,會執行刪除操作並返回空,這種策略是出於節省CPU成本考慮,不需要單獨維護TTL鏈表來處理過期鍵的刪除。但是單獨用這種方式存在內存泄露的問題,當過期鍵一直沒有訪問將無法得到及時刪除,從而導致內存不能及時釋放。正因爲如此,Redis還提供另一種定時任務刪除機制作爲惰性刪除的補充。

2.定時任務刪除

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

  • 從過期字典中隨機 20 個 key;
  • 刪除這 20 個 key 中已經過期的 key;
  • 如果過期的 key 比率超過 1/4,那就重複步驟 1;

同時,爲了保證過期掃描不會出現循環過度,導致線程卡死現象,算法還增加了掃描時間的上限,默認不會超過 25ms

設想一個大型的 Redis 實例中所有的 key 在同一時間過期了,會出現怎樣的結果?

毫無疑問,Redis 會持續掃描過期字典 (循環多次),直到過期字典中過期的 key 變得稀疏,纔會停止 (循環次數明顯下降)。這就會導致線上讀寫請求出現明顯的卡頓現象。導致這種卡頓的另外一種原因是內存管理器需要頻繁回收內存頁,這也會產生一定的 CPU 消耗。

當客戶端請求到來時,服務器如果正好進入過期掃描狀態,客戶端的請求將會等待至少 25ms 後纔會進行處理,如果客戶端將超時時間設置的比較短,比如 10ms,那麼就會出現大量的鏈接因爲超時而關閉,業務端就會出現很多異常。而且這時你還無法從 Redis 的 slowlog 中看到慢查詢記錄,因爲慢查詢指的是邏輯處理過程慢,不包含等待時間。

所以業務開發人員一定要注意過期時間,如果有大批量的 key 過期,要給過期時間設置一個隨機範圍,而不宜全部在同一時間過期,分散過期處理的壓力

、內存溢出控制策略

當Redis所用內存達到maxmemory上限時會觸發相應的溢出控制策略。具體策略受  maxmemory-policy 參數控制(不控制的話,當內存達到最大物理內存就會出現內存交換),Redis支持8種策略(後兩種4.0以上提供):

  1. noeviction: 不會繼續服務寫請求 (DEL 請求可以繼續服務),拒絕所有寫入操作並返回客戶端錯誤信息(error)OOM command not allowed when used memory,此時Redis只響應讀操作。這樣可以保證不會丟失數據,但是會讓線上的業務不能持續進行。這是默認的淘汰策略
  1. volatile-lru :根據LRU算法(最近最少使用)刪除設置了超時屬性(expire)的鍵,直到騰出足夠空間爲止。如果沒有可刪除的鍵對象,回退到noeviction策略。
  2. volatile-ttl 跟上面一樣,除了淘汰的策略不是 LRU,而是 key 的剩餘壽命 ttl 的值,ttl 越小越優先被淘汰(將要過期的)。
  3. volatile-random: 跟上面一樣,不過淘汰的 key 是過期 key 集合中隨機的 key(直到騰出足夠空間爲止)。
  4. allkeys-lru  :區別於 volatile-lru,這個策略要淘汰的 key 對象是全體的 key 集合,而不只是過期的 key 集合。這意味着沒有設置過期時間的 key 也會被淘汰。
  5. allkeys-random: 跟上面一樣,不過淘汰的策略是隨機的 key。
  1. allkey-lfu: 根據LFU算法隨機淘汰全部key
  2. volatile-lfu: 根據LFU算法隨機淘汰過期的key

值得一提的是後兩者淘汰策略在redis4.0及以上版本才提供。Redis4.0提供了LFU算法還可以通過object命令獲取某個key 的訪問頻次

        object freq key

基於LFU機制,用戶可以使用 scan + object freq 來發現熱點key

三、redis4.0之lazyfree

unlink

刪除指令 del 會直接釋放對象的內存,大部分情況下,這個指令非常快,沒有明顯延遲。不過如果刪除的 key是一個非常大的對象,比如一個包含了千萬元素的 hash,那麼刪除操作就會導致單線程卡頓。

Redis 爲了解決這個卡頓問題,在 4.0 版本引入了 unlink 指令,它能對刪除操作進行懶處理,丟給後臺線程來異步回收內存。

        unlink key [key ...]

flushdb/flushall

Redis 提供了 flushdb 和 flushall 指令,用來清空數據庫,這也是極其緩慢的操作。flushdb/flushall在redis-4.0中新引入了選項,可以指定是否使用Lazyfree的方式來清空整個內存。

        > flushall async

        OK

        Rename

執行 rename oldkey newkey時,如果newkey已經存在,redis會先刪除,這也會引發上面提到的刪除大key問題,如果想讓redis在這種場景下也使用lazyfree的方式來刪除,可以在控制檯上打開如下配置:

            lazyfree-lazy-server-del yes/no

       其他場景

       key 的過期、LRU 淘汰及從庫全量同步時接受完 rdb 文件後會立即進行的 flush 操作,Redis4.0 爲這些刪除點也帶來了異步   刪除機制,打開這些點需要額外的配置選項。

  1. slave-lazy-flush  yes/no 從庫接受完 rdb 文件後的 flush 操作
  2. lazyfree-lazy-eviction yes/no 內存達到 maxmemory 時進行淘汰
  3. lazyfree-lazy-expire key yes/no 過期刪除

  AOF RDB 和複製功能對過期鍵的處理

  1. 生成RDB文件:
  2. 在執行save命令或者bgsave命令創建一個新的RDB文件時,程序會對數據中的鍵進行檢查,已過期的鍵不會保存
  3. 載入RDB文件:
    • 主服務器運行:載入的同時忽略過期鍵
    1. 從服務器運行:全部key載入(過期也載入)。但是,因爲主從在數據同步的時候,從服務器會先被清空,所以一般來說,過期鍵對載入rdb文件的從服務器不會造成影響
  4. aof文件寫入:當過期鍵唄惰性刪除或者定期刪除之後,程序會像aof文件追加一個del命令,來顯示的記錄該鍵已被刪除
  5. aof重寫:在執行aof重寫的過程中,程序會對數據庫中的鍵進行檢查,已過期的不會被保存到重寫的aof 文件中。具體過程見上篇
  6. 複製:
    1. 主服務器在刪除一個過期鍵之後,會顯示的向所有從服務器發送一個del命令
    2. 從服務器執行客戶端發送的讀命令時,即使碰到過期鍵也不會將過期鍵刪除,而是繼續像處理未過期鍵一樣處理它
    3. 從服務器只有在接到主服務器發來的del命令之後,纔會刪除該鍵
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章