用redis当作LRU缓存
原文地址:https://redis.io/topics/lru-cache
Redis可以用来作缓存,他可以很方便的淘汰(删除)旧数据添加新数据,类似memcached。LRU只是其中的一种置换算法,这篇文章介绍了maxmemory配置命令和LRU算法的一些深入讨论,这里的LRU只是一种近似LRU(并不是严格的把最老的数据淘汰,而是使用随机采样的方式)。从4.0开始Redis引入了一种新的淘汰算法LFU(Least Frequently Used)。
maxmemory配置命令
maxmemory配置命令用来指定Redis存储数据的大小,可以在redis.conf里配置,也可以运行的时候用CONFIG SET配置。比如配置成使用100M内存,配置命令如下
|
如果把maxmemory设置成0就是不对内存使用量做限制。在64位系统上默认是设置成0,在32位系统上默认设置成3GB。如果达到了限制内存,会根据配置采取不同的策略,可以给相应的命令返回错误,也可以淘汰旧数据来存入新数据。
淘汰策略
在配置文件里使用maxmemory-policy命令指定淘汰策略,取值如下
- noeviction:达到限制内存以后再存新数据会返回出错。
- allkeys-lru:淘汰最近没使用的数据。
- volatile-lru:在设置了过期值(expire)的数据里淘汰最近没使用的数据。
- allkeys-random:随机淘汰数据。
- volatile-random:在设置了过期值(expire)的数据里随机淘汰数据。
- volatile-ttl:在设置了过期值(expire)的数据里淘汰快要过期的数据。
volatile-lru,volatile-random和volatile-ttl在没有可淘汰的数据的时候也会像noeviction一样返回出错。怎样选择淘汰策略取决于你的程序,也可以在使用的过程中用INFO命令观察命中率再做调整。一般情况按如下方法选择淘汰策略:
- 如果所存储的数据访问量成幂律分布的,也就是说一部分数据的访问量明显多于其他数据,那么就使用allkeys-lru。
- 如果所存储的数据周期访问的,或者被访问的概率大致相同,那么就是用allkeys-random。
- 如果所存储的数据设置了不同的过期时间,可以用volatile-ttl。
如果在一个redis实例里存储了两种数据(永久数据和带过期时间的数据),使用volatile-lru和volatile-random是比较好的选择。当然更好的办法是把这两种数据分别存在一个redis实例里。给数据设置过期时间是需要消耗内存的,所以使用allkeys-lru会更节省内存。
淘汰数据的过程
- 客户端请求添加数据。
- redis检查内存是否超过了maxmemory限制,如果超过了就根据策略淘汰旧数据。
- 添加客户端要求的添加的数据。
所以内存使用量会一直在maxmemory上下徘徊,如果某个命令要求添加一个很大的数据很可能造成redis使用的内存明显超过了maxmemory限制。
近似LRU算法
redis的LRU没有严格的实现LRU,也就是说redis不是淘汰最佳(最久没访问)的数据。redis会做一个随机采样,淘汰样本里最佳数据。3.0以后redis会使用备选池做淘汰,提升了性能,准确性更高,更接近LRU算法(代码里提升性能很明显,准确率更高这点看不出来,可能官方做统计得出的结论)。可以设置样本大小调整算法的准确率。
|
redis没用真正的LRU实现是因为这正的LRU太消耗内存了,要遍历排序,会慢很多。下图是近似LRU算法和真正的LRU算法的测试对比图
测试的时候先生成一些数据。从第一个数据开始存储,一直到最后一个,所以第一个数据是LRU算法的最佳淘汰对象。最后又添加50%的数据用来淘汰旧数据。上面的三个色带分别是:
- 浅灰色是淘汰的数据
- 灰色是没有淘汰的数据
- 绿色是新添加的数据。
理论上最先添加的一半数据应该淘汰,redis的LRU算法只是概率性的淘汰这些旧数据。可以看出来在maxmemory-samples设置成5的时候3.0版本比2.8版本更精确(相对来说更接近理论值)。maxmemory-samples设置成10的时候3.0版本已经相对接近理论值了。
LRU只是预测将来一段时间的数据访问模型,如果你的数据成幂律分布redis的LRU也能非常好的处理这个模型。 redis的LRU和真正的LRU差距非常小甚至没差距。如果想要更接近LRU可以提高样本采集量把maxmemory-samples设置成10。
新的近似LFU模式
从4.0开始redis引入了一个新的淘汰模型LFU(Least Frequently Used)。一些情况下这个模型在命中率上更会准确。LFU会统计数据的使用率,使用率低的会被淘汰,使用率高的留下。LRU会保留最近访问了但是平常访问率很低的数据,风险就是淘汰了一个平常访问率高但是最近没访问的数据。LFU不会存在这样的问题,所以LFU更适用于各种不同的访问模型。LFU策略配置如下:
- volatile-lfu:在设置了过期时间的数据里使用近似LFU淘汰算法。
- allkeys-lfu:在所有的数据上使用LFU淘汰算法。
LFU和LRU类似也是一种概率计算,LFU使用一个叫morris counter的概率统计方法,一个数据只使用几个比特。和一个叫过期时间(decay period)的数结合使用。统计值(counter)随着时间减小。LFU也是使用采样的方式淘汰数据。LFU有更多的调整选项,4.0默认的配置
- 一百万访问量会使统计值(counter)变为最大
- 衰败期是一分钟
这些是经过测试比较好用的,用户也可以自己设置。配置命令如下:
|
一个比较特殊的用法是衰败期设置成0,每次统计都会衰败,这是个比较少用的方法。指数因子用来指定多大的访问量会是统计值变为最大,统计值范围0-255。指数因子越大需要越多的访问量使统计值变为最大,指数因子越小访问量低的时候分辨率越高。
|
所以指数因子需要在分辨率和高访问之间做一个折中。redis.conf里有更详细的说明。
近似LFU算法
- 取一个0到1之间的随机值R
- 计算P=1/(old_value*lfu_log_factor+1),这里的old_value就是上面说的counter值。
- 当R<P的时候counter加一。
不是学数学的不会证明这个算法,但是看起来这个算法本身不难,也是用的概率的方法,访问量和counter值之间大概是指数关系,用的时候参考上面那个表就行了。