LRU(Least Recently Used) 最近最少使用算法是眾多置換算法中的一種。
Redis中有一個 maxmemory
概念,主要是為了將使用的內存限定在一個固定的大小。Redis 用到的 LRU 算法,是一種近似的LRU算法。
1、設置 maxmemory
上面已經說過 maxmemory
是為了限定 Redis 最大內存使用量。有多種方法設定它的大小。其中一種方法是通過 CONFIG SET
設定,如下:
127.0.0.1:6379> CONFIG GET maxmemory 1) "maxmemory" 2) "0" 127.0.0.1:6379> CONFIG SET maxmemory 100MB OK 127.0.0.1:6379> CONFIG GET maxmemory 1) "maxmemory" 2) "104857600"
另一種方法是修改配置文件 redis.conf
:
maxmemory 100mb
注意,在 64bit 系統下,maxmemory
設置為 0 表示不限制 Redis 內存使用,在 32bit 系統下,maxmemory
隱式不能超過 3GB。
當 Redis 內存使用達到指定的限制時,就需要選擇一個置換的策略。
2、置換策略
當 Redis 內存使用達到 maxmemory
時,需要選擇設置好的 maxmemory-policy
進行對老數據的置換。
下面是可以選擇的置換策略:
- noeviction: 不進行置換,表示即使內存達到上限也不進行置換,所有能引起內存增加的命令都會返回error
- allkeys-lru: 優先刪除掉最近最不經常使用的key,用以保存新數據
- volatile-lru: 只從設置失效(expire set)的key中選擇最近最不經常使用的key進行刪除,用以保存新數據
- allkeys-random: 隨機從all-keys中選擇一些key進行刪除,用以保存新數據
- volatile-random: 只從設置失效(expire set)的key中,選擇一些key進行刪除,用以保存新數據
- volatile-ttl: 只從設置失效(expire set)的key中,選出存活時間(TTL)最短的key進行刪除,用以保存新數據
設置 maxmemory-policy
的方法 和 設置 maxmemory
方法類似,通過 redis.conf
或是通過 CONFIG SET
動態修改。
如果沒有匹配到可以刪除的 key,那么 volatile-lru
、volatile-random
和 volatile-ttl
策略和 noeviction
替換策略一樣——不對任何 key 進行置換。
選擇合適的置換策略是很重要的,這主要取決於你的應用的訪問模式,當然你也可以動態的修改置換策略,並通過用 Redis 命令——INFO
去輸出 cache 的命中率情況,進而可以對置換策略進行調優。
一般來說,有這樣一些常用的經驗:
- 在所有的 key 都是最近最經常使用,那么就需要選擇 allkeys-lru 進行置換最近最不經常使用的 key,如果你不確定使用哪種策略,那么推薦使用 allkeys-lru
- 如果所有的 key 的訪問概率都是差不多的,那么可以選用 allkeys-random 策略去置換數據
- 如果對數據有足夠的了解,能夠為 key 指定 hint(通過expire/ttl指定),那么可以選擇 volatile-ttl 進行置換
volatile-lru
和 volatile-random
經常在一個Redis實例既做cache又做持久化的情況下用到,然而,更好的選擇使用兩個Redis實例來解決這個問題。
設置是失效時間 expire
會占用一些內存,而采用 allkeys-lru
就沒有必要設置失效時間,進而更有效的利用內存。
3、置換策略是如何工作的
理解置換策略的執行方式是非常重要的,比如:
- 客戶端執行一條新命令,導致數據庫需要增加數據(比如set key value)
- Redis會檢查內存使用,如果內存使用超過 maxmemory,就會按照置換策略刪除一些 key
- 新的命令執行成功
我們持續的寫數據會導致內存達到或超出上限 maxmemory
,但是置換策略會將內存使用降低到上限以下。
如果一次需要使用很多的內存(比如一次寫入一個很大的set),那么,Redis 的內存使用可能超出最大內存限制一段時間。
4、近似 LRU 算法
Redis 中的 LRU 不是嚴格意義上的LRU算法實現,是一種近似的 LRU 實現,主要是為了節約內存占用以及提升性能。Redis 有這樣一個配置 —— maxmemory-samples
,Redis 的 LRU 是取出配置的數目的key,然后從中選擇一個最近最不經常使用的 key 進行置換,默認的 5,如下:
maxmemory-samples 5
可以通過調整樣本數量來取得 LRU 置換算法的速度或是精確性方面的優勢。
Redis 不采用真正的 LRU 實現的原因是為了節約內存使用。雖然不是真正的 LRU 實現,但是它們在應用上幾乎是等價的。下圖是 Redis 的近似 LRU 實現和理論 LRU 實現的對比:
測試開始首先在 Redis 中導入一定數目的 key,然后從第一個 key 依次訪問到最后一個key,因此根據 LRU 算法第一個被訪問的 key 應該最新被置換,之后再增加 50% 數目的 key,導致 50% 的老的 key 被替換出去。
在上圖中你可以看到三種類型的點,組成三種不同的區域:
- 淡灰色的是被置換出去的key
- 灰色的是沒有被置換出去的key
- 綠色的是新增加的key
理論 LRU 實現就像我們期待的那樣,最舊的 50% 數目的 key 被置換出去,Redis 的 LRU 將一定比例的舊 key 置換出去。
可以看到在樣本數為 5 的情況下,Redis3.0 要比 Redis2.8 做的好很多,Redis2.8 中有很多應該被置換出去的數據沒有置換出去。在樣本數為10的情況下,Redis3.0 很接近真正的 LRU 實現。
LRU 是一個預測未來我們會訪問哪些數據的模型,如果我們訪問數據的形式接近我們預想——冪律,那么近似 LRU 算法實現將能處理的很好。
在模擬測試中我們可以發現,在冪律訪問模式下,理論 LRU 和 Redis 近似 LRU 的差距很小或者就不存在差距。
如果你將 maxmemory-samples
設置為 10,那么 Redis 將會增加額外的 CPU 開銷以保證接近真正的 LRU 性能,可以通過檢查命中率來查看有什么不同。
通過 CONFIG SET maxmemory-samples <count>
動態調整樣本數大小,做一些測試驗證你的猜想。
參考:
http://redis.io/topics/lru-cache