使用Redis作為LRU緩存
原文鏈接 譯者:boyhood (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服務被填充了指定數量的鍵。鍵被從頭訪問到尾,所以第一個鍵是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>命令在生產環境上試驗各種不同的采樣大小值是很簡單的。
原創文章,轉載請注明: 轉載自并發編程網 – ifeve.com