LRU算法及其優化策略——Redis篇

LRU算法及其優化策略——Redis篇 .jpg

上一篇文章中,分享了一些LRU基本算法及優化策略,本篇繼續該主題,分享在Redis中LRU算法的使用和優化。git

Redis 內存回收策略

在介紹Redis的LRU使用以前,咱們須要先要了解一下Redis的內存回收策略。github

Redis做爲一個高性能的內存型的KV數據庫,勢必須要一個機制來控制Redis的內存佔用,避免內存溢出。而和內存佔用限制有關的配置即爲maxmemory這個參數,經過動態調整該參數便可動態地限制Redis的內存佔用。redis

Redis內存回收策略分爲兩個方面:算法

  • 刪除過時鍵
  • 回收策略

刪除過時鍵

Redis經過兩種手段刪除過時鍵:數據庫

  1. 惰性刪除c#

    對於有過時時間的鍵,會在鍵上附加一個過時時間的時間戳,當客戶端訪問到該鍵時,會先檢查過時時間戳,若是該鍵值已過時,則返回空,並刪除該鍵。使用惰性刪除能夠避免維護過時時間的鏈表,可是若是一個過時鍵一直沒有被訪問,則有內存泄漏的問題。數組

  2. 定時任務刪除緩存

    爲了不惰性刪除形成的內存泄漏,Redis會啓動一個定時任務,默認爲10s運行一次,該定時任務會隨機從數據庫中選取一些鍵並刪除其中過時的鍵,若是發現其中超過25%的鍵都已過時,則重複執行一次該定時任務,直至隨機掃描到的一批鍵中,過時鍵值的佔比小於25%。dom

回收策略

當Redis的內存佔用超過配置值時,就會執行驅逐政策,按照必定的規則驅逐舊數據,Redis能夠配置六種回收策略:post

  1. noeviction:拒絕全部的寫入操做並返回Error,對讀取操做沒有影響。
  2. allkeys-lru:對全部的鍵執行LRU算法。
  3. volatile-lru:對全部有過時時間的鍵執行LRU算法。
  4. allkeys-random:對全部的鍵隨機驅逐。
  5. volatile-random:對有過時時間的鍵隨機驅逐。
  6. volatile-ttl:驅逐最快要過時的數據。

能夠根據具體應用的數據訪問方式及配合info命令計算出的緩存命中率來選擇合適的驅逐策略。

咱們能夠看到有兩條驅逐策略是和LRU相關的,並且Redis對LRU算法進行了一些優化。

近似LRU算法

原理

Redis 使用了一種基於採樣的近似LRU算法來替代普通的LRU算法,從而避免普通的LRU算法帶來的太高的資源消耗,而像Redis這樣的程序,並不須要驅逐精確的那個最久沒有訪問的鍵。

簡述:Redis隨機採樣一小部分鍵值,並選中其中最久沒有訪問的鍵,而後淘汰。

能夠經過maxmemory-samples參數來配置每次採樣的鍵數量,通常來講該值配置爲5,當該值爲10時就和普通的LRU算法的回收結果很接近,可是也會帶來相應的資源消耗。Redis官網中有下面這麼一個測試結果:

LRU測試.png

  • 淺灰色:被回收的數據
  • 深灰色:尚未被回收的數據
  • 綠色:最新插入的數據

左上角是普通的LRU的回收結果,由於只會插入和訪問一次,因此就是按照先來後到的順序,將舊數據依次的淘汰了。由於Redis3.0增長了一個驅逐池,因此能夠看到3.0的回收效果要好與2.8的版本。並且能夠看到,提升採樣數量也能提升回收效果。

實現

1. LRU時鐘

Redis中有一個全局的LRU時鐘lruclock,該時鐘記錄了自服務啓動後的LRU時間,而且該時鐘每100ms更新一次。當客戶端建立或訪問鍵值時,都會根據這個全局的時鐘更新鍵值自身的LRU時鐘。

如此至關於每一個鍵值都有一個訪問時間的記錄。

2. 執行回收策略的判斷

在插入數據時,會先判斷是否有足夠的容量,若是沒有,則執行回收策略,驅逐舊數據。此處咱們指定爲LRU相關的回收策略。

3. 驅逐池

一個驅逐池中的Entry的結構以下圖所示:

#define REDIS_EVICTION_POOL_SIZE 16
struct evictionPoolEntry {
    unsigned long long idle;    /* Object idle time. */
    sds key;                    /* Key name. */
};
複製代碼

其中idle即爲空閒時間,key即爲鍵值,空閒時間就是按照上面的LRU時鐘計算的。驅逐池就是以Entry的idle爲順序組成組成的數組,一共有16個Entry。

當須要在驅逐池中插入一個鍵時,首先須要填充驅逐池,按照配置的採樣參數從數據庫中隨機挑選幾個鍵值,而後依次執行下面的操做。

該鍵值會依次比較驅逐池中尋找該鍵空閒時間大的鍵值:

  • 若是沒有找到這樣的鍵值,且驅逐池沒有空位,則表明該插入的鍵值是空閒時間最小的鍵,因此該鍵並無接入驅逐池的必要,因此直接跳過。
  • 若是找到了這樣的鍵值,且驅逐池有空位,則直接插入到已有鍵值以前,之前的Entry移位。
  • 若是找到了這樣的鍵值,而驅逐池沒有空位,則在驅逐池中剔除掉空閒時間最小的鍵值後插入。

而後就到了真正刪除鍵的邏輯了:

在驅逐池中,依次從空閒時間最長的鍵值開始刪除,直到有足夠的數據空間。

對這部分感興趣的道友,能夠去看下Redis的源碼,主要是freeMemoryIfNeeded()方法evictionPoolPopulate()方法

源碼地址

總結

本文介紹了Redis的內存回收策略及Redis近似LRU算法的原理和實現。從Redis對LRU算法的優化中,咱們能夠發現一個好的設計,並非說要有多麼準確,有時候能夠在準確性和性能之間作權衡,以知足本身真正想要實現的功能。

相關文章
相關標籤/搜索