1. 程式人生 > >《Redis官方文件》使用Redis作為LRU快取

《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之間的比較。

687474703a2f2f72656469732e696f2f696d616765732f7265646973646f632f6c72755f636f6d70617269736f6e2e706e67

用於測試生成了上面影象的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>命令在生產環境上試驗各種不同的取樣大小值是很簡單的。