Redis++:Redis 內存爆滿 之 淘汰策略


前言:

  我們的redis使用的是內存空間來存儲數據的,但是內存空間畢竟有限,隨着我們存儲數據的不斷增長,當超過了我們的內存大小時,即在redis中設置的緩存大小(maxmeory 4GB),redis會怎么處理呢?

Redis內存淘汰策略,是被很多小伙伴忽略的知識盲區,注意,是盲區。

注意,Redis如果內存淘汰策略配置不合理,可能會導致Redis無法服務。

今天就來聊聊redis的緩存淘汰策略:↓ ↓ ↓

首先,介紹一下Redis過期刪除策略,然后,再介紹Redis淘汰策略.

1):Redis過期刪除策略 

Redis對於過期的key,有三種刪除策略:

  • 定期刪除(主動刪除:由於惰性刪除策略無法保證冷數據被及時刪掉,所以Redis會定期主動淘汰一批已過期的key)
  • 惰性刪除(被動刪除:當讀/寫一個已經過期的key時,會觸發惰性刪除策略,直接刪除掉這個過期key)
  • 當前已用內存超過maxmemory限定時,觸發主動清理策略

定期刪除:

redis 會將每個設置了過期時間的 key 放入到一個獨立的字典中,以后會定期遍歷這個字典來刪除到期的 key。

Redis 默認會每秒進行十次過期掃描(100ms一次),過期掃描不會遍歷過期字典中所有的 key,而是采用了一種簡單的貪心策略。

1、從過期字典中隨機 20 個 key;

2、刪除這 20 個 key 中已經過期的 key;

3、如果過期的 key 比率超過 1/4,那就重復步驟 1;

redis默認是每隔 100ms就隨機抽取一些設置了過期時間的key,檢查其是否過期,如果過期就刪除。

注意這里是隨機抽取的。為什么要隨機呢?

你想一想假如 redis 存了幾十萬個 key ,每隔100ms就遍歷所有的設置過期時間的 key 的話,就會給 CPU 帶來很大的負載。

惰性刪除:

所謂惰性策略就是在客戶端訪問這個key的時候,redis對key的過期時間進行檢查,如果過期了就立即刪除,不會給你返回任何東西。

為啥需要兩種刪除策略呢?

定期刪除可能會導致很多過期key到了時間並沒有被刪除掉。

所以就有了惰性刪除。假如你的過期 key,靠定期刪除沒有被刪除掉,還停留在內存里,除非你的系統去查一下那個 key,才會被redis給刪除掉。

這就是所謂的惰性刪除,即當你主動去查過期的key時,如果發現key過期了,就立即進行刪除,不返回任何東西.

總結:定期刪除是集中處理,惰性刪除是零散處理

2):Redis 內存爆滿 淘汰置換策略 

當 Redis 內存使用達到 maxmemory 時,需要選擇設置好的 maxmemory-policy 進行對老數據的置換。

下面是可以選擇的置換策略:

不同於之前的版本,redis5.0為我們提供了八個不同的內存置換策略;很早之前提供了6種。

  1. volatile-lru:從已設置過期時間的數據集中挑選最近最少使用的數據淘汰。
  2. volatile-ttl:從已設置過期時間的數據集中挑選將要過期的數據淘汰。
  3. volatile-random:從已設置過期時間的數據集中任意選擇數據淘汰。
  4. volatile-lfu:從已設置過期時間的數據集挑選使用頻率最低的數據淘汰。
  5. allkeys-lru:從數據集中挑選最近最少使用的數據淘汰
  6. allkeys-lfu:從數據集中挑選使用頻率最低的數據淘汰。
  7. allkeys-random:從數據集(server.db[i].dict)中任意選擇數據淘汰
  8. no-enviction(驅逐):禁止驅逐數據,這也是默認策略

意思是當內存不足以容納新入數據時,新寫入操作就會報錯,請求可以繼續進行,線上任務也不能持續進行,采用no-enviction策略可以保證數據不被丟失。

這八種大體上可以分為4中:

  1. lru(Least Recently Used,最近最少使用)
  2. lfu(Least Frequently Used,最不經常使用)、
  3. random(隨機)
  4. ttl

設置 maxmemory-policy 的方法 和 設置 maxmemory 方法類似,通過 redis.conf 或是通過 CONFIG SET 動態修改。

選擇合適的置換策略是很重要的,這主要取決於你的應用的訪問模式,當然你也可以動態的修改置換策略;

並通過用 Redis 命令——INFO 去輸出 cache 的命中率情況,進而可以對置換策略進行調優。

置換策略是如何工作的?

  1. 客戶端執行一條新命令,導致數據庫需要增加數據(比如set key value)
  2. Redis會檢查內存使用,如果內存使用超過 maxmemory,就會按照置換策略刪除一些 key
  3. 新的命令執行成功

我們持續的寫數據會導致內存達到或超出上限 maxmemory,但是置換策略會將內存使用降低到上限以下。

如果一次需要使用很多的內存(比如一次寫入一個很大的set),那么,Redis 的內存使用可能超出最大內存限制一段時間。

LRU 算法機制:

LRU算法的全稱叫做Least Recently Used,也就是最近最少使用原則來進行數據的淘汰算法。

其原理就是,會將數據放入到一個鏈表中,當鏈表中的某個元素被訪問時,這個元素就被會提到鏈表的前面,其他元素,默認向后移動;

當這個時候我們想緩存中新增一個元素時,也會被增加到鏈表的頭部,那么尾部的最后一個元素就被淘汰了。

lru的實現思想就是:就是剛被訪問的數據,在接下來的時間里,更容易被再次訪問,而一段時間沒有被訪問的數據,之后也不會再次訪問。

但是要將redis的全部數據都放入這樣一個鏈表中的話,redis的數據被頻繁訪問時,需要不停的移動鏈表的位置,降低redis的性能。

所以redis對LRU算法進行了優化 ↓

在redis中,會給每個數據記錄一個最近訪問的時間戳(記錄在RedisObject的lru字段中),

當需要進行數據淘汰時,redis就隨機篩選出N個數據放入到候選集合中去,然后比較這N個數據中的lru的值,最小的就會被淘汰。

當再次需要淘汰數據時,這個時候篩選數據放入到第一次創建的淘汰集合中,那么這次篩選就不在是隨機篩選,而是能進入候選集合的數據的 lru 字段值必須小於候選集合中最小的 lru 值,

然后再次將最小的lru的值的數據進行淘汰。

其中N的配置項為:

maxmemory-samples 100 # 表示N為100

LFU 算法機制:

LFU(Least frequently used)稱為最近使用最少的數據將被淘汰,redis在就是在LRU的基礎上增加一個次數統計。

其步驟就是根據數據的訪問次數進行篩選,淘汰訪問次數少的數據,如果訪問次數相同,在根據訪問時間進行比較,淘汰訪問時間久遠的數據。

redis中的實現方式:就是在RedisObject的字段lru上,拆分為兩個部分:

  • ldt值:lru字段的前16bit位,還是用來表示時間戳。
  • counter值:lru字段的后8bit位,用來表示數據的訪問次數。

當 LFU 策略篩選數據時,Redis 會在候選集合中,根據數據 lru 字段的后 8bit 選擇訪問次數最少的數據進行淘汰。

當訪問次數相同時,再根據 lru 字段的前 16bit 值大小,選擇訪問時間最久遠的數據進行淘汰。

但是8個bit位,最大只能記錄255的值,但是redis中的數據,有時候會被訪問成千上萬次,那么這個問題如何進行解決呢?

redis對計數進行了優化,並不是數據被訪問一次,counter就會被加1,而是遵循如下規則:↓

當數據被訪問一次時,首先用計數器當前的值乘以配置項lfu_log_factor再加1,再取倒數得到一個p值然后把這個p值和一個取值范圍在(0,1)的一個隨機數r,進行比大小,只有p值大於r時,counter的值才會被加一

lfu-log-factor可以調整計數器counter的增長速度,lfu-log-factor越大,counter增長的越慢。 lfu-decay-time是一個以分鍾為單位的數值,可以調整counter的減少速度
#redis部分源碼展示 double r = (double)rand()/RAND_MAX; double p = 1.0/(baseval*server.lfu_log_factor+1); if (r < p) counter++;

其中 baseval是計數器的當前值。計數器的默認初始值為5(由代碼中的 LFU_INIT_VAL 常量設置),並不是為0,這樣可以避免數據剛進入緩存,就因為訪問次數少而被立即淘汰。

當lfu_log_factor取不同的值時,實際訪問次數下,counter的值的變化情況:

在實際的使用場景中,還會有這樣一種情況,某些數據可能一開始會被大量的訪問,但是過了時間段后,就不會再被訪問。

如果這個時候counter的值很大,就算后續不會被訪問,也就不會被redis進行數據淘汰。

針對這種情況,在redis中,設計了counter的衰減策略。其實現就是根據lfu_decay_time的配置值,來控制訪問次數的衰減。

其流程如下:

  • lfu會計算當前時間和數據最近一次訪問的時間差值,並將這個差值換算為分鍾單位。
  • 然后在將這個差值除以lfu_decay_time值,得到的就是我們需要減去的值
  • 然后再講counter的值減去這個值

這樣就可以保證在一段時間后,可以淘汰這部分數據。

Redis 的淘汰策略怎么選:

一般來說,有這樣一些常用的經驗:

  • 在所有的 key 都是最近最經常使用,那么就需要選擇 allkeys-lru 進行置換最近最不經常使用的 key,如果你不確定使用哪種策略,那么推薦使用 allkeys-lru
  • 如果所有的 key 的訪問概率都是差不多的,那么可以選用 allkeys-random 策略去置換數據
  • 如果對數據有足夠的了解,能夠為 key 指定 hint(通過expire/ttl指定),那么可以選擇 volatile-ttl 進行置換

volatile-lru 和 volatile-random 經常在一個Redis實例既做cache又做持久化的情況下用到,然而,更好的選擇使用兩個Redis實例來解決這個問題。

設置是失效時間 expire 會占用一些內存,而采用 allkeys-lru 就沒有必要設置失效時間,進而更有效的利用內存。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM