當 Redis 作為緩存使用時,當你添加新的數據時,有時候很方便使 Redis 自動回收老的數據。LRU 實際上是被唯一支持的數據移除方法。Redis 的 maxmemory 指令,用於限制記憶體使用到一個固定的容量,也包含深入探討 Redis 使用的 LRU 演算法,一個近似準確的 LRU。maxme...
當 Redis 作為緩存使用時,當你添加新的數據時,有時候很方便使 Redis 自動回收老的數據。LRU 實際上是被唯一支持的數據移除方法。Redis 的 maxmemory 指令,用於限制記憶體使用到一個固定的容量,也包含深入探討 Redis 使用的 LRU 演算法,一個近似準確的 LRU。
maxmemory 配置指令(configuration directive)
maxmemory 配置指令是用來配置 Redis 為數據集使用指定的記憶體容量大小。可以使用 redis.conf 文件來設置配置指令,或者之後在運行時使用 CONFIG SET 命令。
例如,為了配置記憶體限製為 100MB,可以在 redis.conf 文件中使用以下指令
maxmemory 100mb
設置 maxmemory 為 0,表示沒有記憶體限制。這是 64 位系統的預設行為,32 位的系統則使用 3G 大小作為隱式的記憶體限制。
當指定的記憶體容量到達時,需要選擇不同的行為,即策略。Redis 可以只為命令返回錯誤,這樣將占用更多的記憶體,或者每次添加新數據時,回收掉一些舊的數據以避免記憶體限制。
回收策略(Eviction policies)
當 maxmemory 限制到達的時候,Redis 將採取的準確行為是由 maxmemory-policy 配置指令配置的。
以下策略可用:
- noeviction:當到達記憶體限制時返回錯誤。當客戶端嘗試執行命令時會導致更多記憶體占用(大多數寫命令,除了 DEL 和一些例外)。
- allkeys-lru:回收最近最少使用(LRU)的鍵,為新數據騰出空間。
- volatile-lru:回收最近最少使用(LRU)的鍵,但是只回收有設置過期的鍵,為新數據騰出空間。
- allkeys-random:回收隨機的鍵,為新數據騰出空間。
- volatile-random:回收隨機的鍵,但是只回收有設置過期的鍵,為新數據騰出空間。
- volatile-ttl:回收有設置過期的鍵,嘗試先回收離 TTL 最短時間的鍵,為新數據騰出空間。
當沒有滿足前提條件的話,volatile-lru,volatile-random 和 volatile-ttl 策略就表現得和 noeviction 一樣了。
選擇正確的回收策略是很重要的,取決於你的應用程式的訪問模式,但是,你可以在程式運行時重新配置策略,使用 INFO 輸出來監控緩存命中和錯過的次數,以調優你的設置。
一般經驗規則:
- M 如果你期待你的用戶請求呈現冪律分佈(power-law distribution),也就是,你期待一部分子集元素被訪問得遠比其他元素多,可以使用 allkeys-lru 策略。在你不確定時這是一個好的選擇。
- 如果你是迴圈周期的訪問,所有的鍵被連續掃描,或者你期待請求正常分佈(每個元素以相同的概率被訪問),可以使用 allkeys-random 策略。
- 如果你想能給 Redis 提供建議,通過使用你創建緩存對象的時候設置的 TTL 值,確定哪些對象應該被過期,你可以使用 volatile-ttl 策略。
當你想使用單個實例來實現緩存和持久化一些鍵,allkeys-lru 和 volatile-random 策略會很有用。但是,通常最好是運行兩個 Redis 實例來解決這個問題。
另外值得註意的是,為鍵設置過期時間需要消耗記憶體,所以使用像 allkeys-lru 這樣的策略會更高效,因為在記憶體壓力下沒有必要為鍵的回收設置過期時間。
回收過程 (Eviction process)
理解回收的過程是這麼運作的非常的重要:
- 一個客戶端運行一個新命令,添加了新數據。
- Redis 檢查記憶體使用情況,如果大於 maxmemory 限制,根據策略來回收鍵。
- 一個新的命令被執行,如此等等。
通過檢查,然後回收鍵以返回到限制以下,來連續不斷的穿越記憶體限制的邊界。
如果一個命令導致大量的記憶體被占用 (像一個很大的集合交集保存到一個新的鍵),一會功夫記憶體限制就會被這個明顯的記憶體量所超越。
近似的 LRU 演算法(Approximated LRU algorithm)
Redis 的 LRU 演算法不是一個精確的實現。這意味著 Redis 不能選擇最佳候選鍵來回收,也就是最久錢被訪問的那些鍵。相反,會嘗試運營一個近似的 LRU 演算法,通過採樣一小部分鍵,然後在採樣鍵中回收最適合(擁有最久訪問時間)的那個。
Redis 的 LRU 演算法有一點很重要,你可以調整演算法的精度,通過改變每次回收時檢查的採樣數量。這個參數可以通過如下配置指令
maxmemory-samples 5
Redis 沒有使用真實的 LRU 實現的原因,是因為這會消耗更多的記憶體。然而,近似值對使用 Redis 的應用來說基本上也是等價的。為 Redis 使用的 LRU 近似值和真實 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 命令在生產環境上試驗各種不同的採樣大小值是很簡單的。