redis使用内存保存数据,使用redis可以避免从数据库读取,提高响应速度。但内存大小有限,当数据过多时,缓存不可避免的会被写满。针对该情况,redis提供对应缓存淘汰策略,了解各种缓存淘汰策略,在使用时合理设置缓存淘汰策略可以避免缓存被写满,提高缓存命中率,提升系统性能。
当数据达到maxmemory后redis会对缓存淘汰。目前最新版本6.2.5提供8种缓存淘汰策略,这些缓存淘汰策略可以根据淘汰类型分为不淘汰数据,设置过期时间的数据中淘汰,所有数据中淘汰这三类。
不淘汰数据
noeviction:该策略为默认策略,数据不删除,内存写满后对客户端返回error信息。
设置过期时间的数据中淘汰
对于设置过期时间的数据,其淘汰时机不止数据达到maxmemory,当数据达到过期时间也会触发淘汰策略。
volatile-random:设置过期时间的数据中随机删除
volatile-ttl:设置过期时间的数据中根据过期时间的先后顺序删除,越早过期的越先被删除。
volatile-lru:设置过期时间的数据中使用lru算法删除。
volatile-lfu:设置过期时间的数据中使用lfu算法删除。
所有数据中淘汰
allkeys-lru:所有数据中使用lru算法删除。
allkeys-random:所有数据随机删除。
allkeys-lfu:所有数据中使用lfu算法删除。
lru算法
lru( Least Recently Used)根据最近最少使用的原则筛选数据。lru用链表管理所有数据。链表头为mru端存放最近刚刚访问的数据。链表尾为lru端存放不常访问数据。其工作流程如下
当前链表中有5条数据
请求访问4这条数据后
新增数据6访问
由于lru使用链表管理所有数据,会占用一定内存空间,当数据量较大时链表移动也比较耗时,降低redis性能。所以redis采用变种lru算法,避免降低性能。
redis中lru算法主要依赖于redisObject中lru字段记录的时间戳(对于redisObject不了解的可以点这看一下redisObject)。其淘汰过程可分为如下几步。
1.在开始淘汰数据时,随机取maxmemory-samples个数据组成一个数组,然后将数组中lru最小的数据淘汰。
2.下次淘汰时从数组外选择小于数组中最小lru的数据放入数组,淘汰时依旧将lru最小的数据淘汰。
3.当数组外没有小于数组内最小lru的数据则清空数组重新执行步骤1。
lfu算法
介绍lfu算法前,简单说下缓存污染。缓存污染指一些数据被访问次数很少,甚至只被访问一次。当这些数据使用完后如果还留在内存中,则会浪费空间。对于这类缓存lru,random等淘汰策略效果不明显,ttl也只能淘汰指定过期时间的数据。所以redis4.0之后引入lfu算法。
lfu算法基于lru算法,将redisObject中lru字段分为ldt与counter两部分。ldt记录访问的时间戳占用16b,counter记录访问次数占用8b。由于lfu基于lru,淘汰阶段构建数组流程不变,只是在比较时需要淘汰的数据时先比较counter,当counter相同时比较ldt。
由于counter只占用8b,8b记录的最大值是255。所以redis官方对counter计数做了优化。其步骤如下:
1.随机生成0,1区间的数R。
2.将原counter值*lfu_log_factor+1,然后取其倒数记为P。P=1/(old_value*lfu_log_factor+1)
3.比较P与R当R
redis官方使用该计算规则解决了counter值最大只能为255的问题。并且对于该值如何设定,官方也提供指导方案。
通过上图可以看到,当lfu_log_factor为0时访问1000次counter值会达到255,当lfu_log_factor为100时可以支持10M次访问。redis默认lfu-log-factor为10,可通过 config get lfu-log-factor查看。
通过counter累加可以解决一部分缓存污染问题,但假如碰到某一时间某些数据被大量访问,导致counter值达到255,之后这些就数据不再被访问了。根据lfu淘汰规则这些数据可能永远也不会被删除。为解决该问题redis提供了counter对应衰减机制。
redis官方使用lfu_decay_time衰减因子控制counter衰减值。redis后台计算当前时间与数据最近一次访问时间的差值后换算成分钟为单位,然后将该差值除以 lfu_decay_time 值,得到的结果就是数据 counter 要衰减的值。
关于淘汰策略更多信息可以查看 redis.conf配置文件或访问官网Redis as an LRU cache