天天看點

《Redis官方文檔》使用Redis作為LRU緩存

原文連結  譯者:boyhou (wechat:houyongbo923)

如果你使用redis作為緩存,當添加新資料時,若有記憶體大小等限制,系統預設會根據一定的規則自動清理舊資料。這種處理方式在開發社群中衆所周知,因為它也是非常流行的緩存系統 memcached 的預設處理方式。

lru(lru全稱是least recently used,即最近最久未使用)實際上隻是redis支援的記憶體回收政策中的一種。這篇文章将要講述redis的 maxmemory 配置選項,該配置選項用來限制 redis 的記憶體使用大小,同時深入研究 lru(确切的說是近似lru算法) 算法在 redis 中的應用。

最大記憶體配置選項

maxmemory 配置選項使用來配置 redis 的存儲資料所能使用的最大記憶體限制。可以通過在内置檔案redis.conf中配置,也可在redis運作時通過指令config set來配置。例如,我們要配置記憶體上限是100m的redis緩存,那麼我們可以在 redis.conf 配置如下:

maxmemory 100mb

設定 maxmemory 為 0 表示沒有記憶體限制。在 64-bit 系統中,預設是 0 無限制,但是在 32-bit 系統中預設是 3gb。

當存儲資料達到限制時,redis 會根據情形選擇不同政策,或者傳回errors(這樣會導緻浪費更多的記憶體),或者清除一些舊資料回收記憶體來添加新資料。

回收政策

當記憶體達到限制時,redis 具體的回收政策是通過 maxmemory-policy 配置項配置的。

以下的政策都是可用的:

noenviction:不清除資料,隻是傳回錯誤,這樣會導緻浪費掉更多的記憶體,對大多數寫指令(del 指令和其他的少數指令例外)

allkeys-lru:從所有的資料集(server.db[i].dict)中挑選最近最少使用的資料淘汰,以供新資料使用

volatile-lru:從已設定過期時間的資料集(server.db[i].expires)中挑選最近最少使用的資料淘汰,以供新資料使用

allkeys-random:從所有資料集(server.db[i].dict)中任意選擇資料淘汰,以供新資料使用

volatile-random:從已設定過期時間的資料集(server.db[i].expires)中任意選擇資料淘汰,以供新資料使用

volatile-ttl:從已設定過期時間的資料集(server.db[i].expires)中挑選将要過期的資料淘汰,以供新資料使用

當 cache 中沒有符合清除條件的 key 時,回收政策 volatile-lru, volatile-random 和volatile-ttl 将會和 政策 noeviction 一樣傳回錯誤。選擇正确的回收政策是很重要的,取決于你的應用程式的通路模式。但是,你可以在程式運作時重新配置政策,使用 info 輸出來監控緩存命中和錯過的次數,以調優你的設定。

普适經驗規則:

如果期望使用者請求呈現幂律分布(power-law distribution),也就是,期望一部分子集元素被通路得遠比其他元素多時,可以使用allkeys-lru政策。在你不确定時這是一個好的選擇。

如果期望是循環周期的通路,所有的鍵被連續掃描,或者期望請求符合平均分布(每個元素以相同的機率被通路),可以使用allkeys-random政策。

如果你期望能讓 redis 通過使用你建立緩存對象的時候設定的ttl值,确定哪些對象應該是較好的清除候選項,可以使用volatile-ttl政策。

當你想使用單個redis執行個體來實作緩存和持久化一些鍵,allkeys-lru和volatile-random政策會很有用。但是,通常最好是運作兩個redis執行個體來解決這個問題。

另外值得注意的是,為鍵設定過期時間需要消耗記憶體,是以使用像allkeys-lru這樣的政策會更高效,因為在記憶體壓力下沒有必要為鍵的回收設定過期時間。

回收過程

了解回收過程是運作流程非常的重要,回收過程如下:

一個用戶端運作一個新指令,添加了新資料。

redis檢查記憶體使用情況,如果大于maxmemory限制,根據政策來回收鍵。

一個新的指令被執行,如此等等。

我們添加資料時通過檢查,然後回收鍵以傳回到限制以下,來連續不斷的穿越記憶體限制的邊界。

如果一個指令導緻大量的記憶體被占用(比如一個很大的集合儲存到一個新的鍵),那麼記憶體限制很快就會被這個明顯的記憶體量所超越。

近似lru算法

redis的lru算法不是一個嚴格的lru實作。這意味着redis不能選擇最佳候選鍵來回收,也就是最久未被通路的那些鍵。相反,redis 會嘗試執行一個近似的lru算法,通過采樣一小部分鍵,然後在采樣鍵中回收最适合(擁有最久通路時間)的那個。

然而,從redis3.0開始,算法被改進為維護一個回收候選鍵池。這改善了算法的性能,使得更接近于真實的lru算法的行為。redis的lru算法有一點很重要,你可以調整算法的精度,通過改變每次回收時檢查的采樣數量。

這個參數可以通過如下配置指令:

maxmemory-samples 5

redis沒有使用真實的lru實作的原因,是因為這會消耗更多的記憶體。然而,近似值對使用redis的應用來說基本上也是等價的。下面的圖形對比,為redis使用的lru近似值和真實lru之間的比較。

《Redis官方文檔》使用Redis作為LRU緩存

用于測試生成了上面圖像的redis服務被填充了指定數量的鍵。鍵被從頭通路到尾,是以第一個鍵是lru算法的最佳候選回收鍵。然後,再新添加50%的鍵,強制一般的舊鍵被回收。

你可以從圖中看到三種不同的原點,形成三個不同的帶。

淺灰色帶是被回收的對象

灰色帶是沒有被回收的對象

綠色帶是被添加的對象

在理論的lru實作中,我們期望看到的是,在舊鍵中第一半會過期。而redis的lru算法則隻是機率性的過期這些舊鍵。 你可以看到,同樣使用5個采樣點,redis 3.0表現得比redis 2.8要好,redis 2.8中最近被通路的對象之間的對象仍然被保留。在redis 3.0中使用10為采樣大小,近似值已經非常接近理論性能。

注意,lru隻是一個預測模型用來指定鍵在未來如何被通路。另外,如果你的資料通路模式非常接近幂律,大多數的通路都将集中在一個集合中,lru近似算法将能處理得很好。

在模拟實驗的過程中,我們發現使用幂律通路模式,真實的lru算法和redis的近似算法之間的差異非常小,或者根本就沒有。然而,你可以提高采樣大小到10,這會消耗額外的cpu,來更加近似于真實的lru算法,看看這會不會使你的緩存錯失率有差異。

使用config set maxmemory-samples <count>指令在生産環境上試驗各種不同的采樣大小值是很簡單的。

上一篇: Flutter随記

繼續閱讀