每天資訊redis——記憶體滿了應該怎麼辦?

菜單

redis——記憶體滿了應該怎麼辦?

我們的redis使用的是記憶體空間來儲存資料的,但是記憶體空間畢竟有限,隨著我們儲存資料的不斷增長,當超過了我們的記憶體大小時,即在redis中設定的快取大小(maxmeory 4GB),redis會怎麼處理呢?今天就來聊聊redis的快取淘汰策略。

一、redis的快取淘汰策略

在redis中,一種有8種對應的快取淘汰策略

redis——記憶體滿了應該怎麼辦?

根據是否進行資料淘汰可以分為:不淘汰的資料策略(noeviction)和7種淘汰資料策略。

在淘汰的資料策略中,又可以根據淘汰資料的樣本分為:在設定了過期時間的資料中進行淘汰資料的四種策略(volatile-ttl,volatile-radom,volatile-lru,volatile-lfu)和在全部資料中進行資料淘汰的三種策略(allkeys-radmom、allkeys-lru、allkeys-lfu)

volatile-ttl:表示在設定可過期時間的鍵值對中,根據過期時間的先後進行淘汰資料,越早被過期的資料,越先被淘汰。

volatile-random:從名字可以看出來,就是在設定了過期時間的鍵值對中,隨機淘汰資料。

volatile-lru:會根據lru演算法進行資料的淘汰

volatile-lfu:會根據lfu演算法進行資料的淘汰

allkeys-random:在全部的鍵值對資料中,進行資料的隨機淘汰。

allkeys-lru:在全部的鍵值對資料中,根據lru演算法進行資料的淘汰。

allkeys-lfu:在全部的鍵值對資料中,根據lfu演算法進行資料的淘汰。

二、LRU演算法機制

LRU演算法的全稱叫做Least Recently Used,也就是最近最少使用原則來進行資料的淘汰演算法。其原理就是,會將資料放入到一個連結串列中,當連結串列中的某個元素被訪問時,這個元素就被會提到連結串列的前面,其他元素,預設向後移動;當這個時候我們想快取中新增一個元素時,也會被增加到連結串列的頭部,那麼尾部的最後一個元素就被淘汰了。

redis——記憶體滿了應該怎麼辦?

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的值才會被加一

#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的值的變化情況:

redis——記憶體滿了應該怎麼辦?

在實際的使用場景中,還會有這樣一種情況,某些資料可能一開始會被大量的訪問,但是過了時間段後,就不會再被訪問,如果這個時候counter的值很大,就算後續不會被訪問,也就不會被redis進行資料淘汰。針對這種情況,在redis中,設計了counter的衰減策略。其實現就是根據lfu_decay_time的配置值,來控制訪問次數的衰減。其流程如下:

lfu會計算當前時間和資料最近一次訪問的時間差值,並將這個差值換算為分鐘單位。

然後在將這個差值除以lfu_decay_time值,得到的就是我們需要減去的值

然後再講counter的值減去這個值

這樣就可以保證在一段時間後,可以淘汰這部分資料。

四、redis的淘汰策略怎麼選

存在即合理,每種淘汰策略都會有自己的使用場景,我們在設定redis的淘汰策略的時候,就需要結合自己的業務場景去定製化的配置。下面就是我給的一些建議:

可以優先將淘汰策略設定為allkeys-lru,這樣可以充分利用lru演算法的特性,把最近訪問的資料都留在快取中,長時間沒有訪問的資料給淘汰掉。

如果業務中的訪問資料,沒有冷熱資料之分,資料的訪問時間相差不大,可以採用allkeys-lru策略。

如果在業務中,有需要置頂資料,可以不給置頂資料設定過期時間,然後採用volatile-lru策略來實現。

如果在業務中,有一些定時任務的資料,過了這個時間段後,基本就不會訪問的資料,可以採用allkeys-lfu演算法。