前言

Redis缓存淘汰策略与Redis键的过期删除策略并不完全相同,前者是在Redis内存使用超过一定值的时候(一般这个值可以配置)使用的淘汰策略;而后者是通过定期删除+惰性删除两者结合的方式进行内存淘汰的。
这里参照官方文档的解释重新叙述一遍过期删除策略:当某个key被设置了过期时间之后,客户端每次对该key的访问(读写)都会事先检测该key是否过期,如果过期就直接删除;但有一些键只访问一次,因此需要主动删除,默认情况下redis每秒检测10次,检测的对象是所有设置了过期时间的键集合,每次从这个集合中随机检测20个键查看他们是否过期,如果过期就直接删除,如果删除后还有超过25%的集合中的键已经过期,那么继续检测过期集合中的20个随机键进行删除。这样可以保证过期键最大只占所有设置了过期时间键的25%。

ZERO、Redis内存不足的缓存淘汰策略

  • noeviction:当内存使用超过配置的时候会返回错误,不会驱逐任何键
  • allkeys-lru:加入键的时候,如果过限,首先通过LRU算法驱逐最久没有使用的键
  • volatile-lru:加入键的时候如果过限,首先从设置了过期时间的键集合中驱逐最久没有使用的键
  • allkeys-random:加入键的时候如果过限,从所有key随机删除
  • volatile-random:加入键的时候如果过限,从过期键的集合中随机驱逐
  • volatile-ttl:从配置了过期时间的键中驱逐马上就要过期的键
  • volatile-lfu:从所有配置了过期时间的键中驱逐使用频率最少的键
  • allkeys-lfu:从所有键中驱逐使用频率最少的键

一、LRU

1、Java中的LRU实现方式

在Java中LRU的实现方式是使用HashMap结合双向链表,HashMap的值是双向链表的节点,双向链表的节点也保存一份key value。

  • 新增key value的时候首先在链表结尾添加Node节点,如果超过LRU设置的阈值就淘汰队头的节点并删除掉HashMap中对应的节点。
  • 修改key对应的值的时候先修改对应的Node中的值,然后把Node节点移动队尾。
  • 访问key对应的值的时候把访问的Node节点移动到队尾即可。

2、Redis中LRU的实现

  • Redis维护了一个24位时钟,可以简单理解为当前系统的时间戳,每隔一定时间会更新这个时钟。每个key对象内部同样维护了一个24位的时钟,当新增key对象的时候会把系统的时钟赋值到这个内部对象时钟。比如我现在要进行LRU,那么首先拿到当前的全局时钟,然后再找到内部时钟与全局时钟距离时间最久的(差最大)进行淘汰,这里值得注意的是全局时钟只有24位,按秒为单位来表示才能存储194天,所以可能会出现key的时钟大于全局时钟的情况,如果这种情况出现那么就两个相加而不是相减来求最久的key。
struct redisServer {pid_t pid; char *configfile; //全局时钟unsigned lruclock:LRU_BITS; ...
};
typedef struct redisObject {unsigned type:4;unsigned encoding:4;/* key对象内部时钟 */unsigned lru:LRU_BITS;int refcount;void *ptr;
} robj;
  • Redis中的LRU与常规的LRU实现并不相同,常规LRU会准确的淘汰掉队头的元素,但是Redis的LRU并不维护队列,只是根据配置的策略要么从所有的key中随机选择N个(N可以配置)要么从所有的设置了过期时间的key中选出N个键,然后再从这N个键中选出最久没有使用的一个key进行淘汰。
  • 下图是常规LRU淘汰策略与Redis随机样本取一键淘汰策略的对比,浅灰色表示已经删除的键,深灰色表示没有被删除的键,绿色表示新加入的键,越往上表示键加入的时间越久。从图中可以看出,在redis 3中,设置样本数为10的时候能够很准确的淘汰掉最久没有使用的键,与常规LRU基本持平。

    二、LFU

LFU是在Redis4.0后出现的,LRU的最近最少使用实际上并不精确,考虑下面的情况,如果在|处删除,那么A距离的时间最久,但实际上A的使用频率要比B频繁,所以合理的淘汰策略应该是淘汰B。LFU就是为应对这种情况而生的。

A~~A~~A~~A~~A~~A~~A~~A~~A~~A~~~|
B~~~~~B~~~~~B~~~~~B~~~~~~~~~~~B|
  • LFU把原来的key对象的内部时钟的24位分成两部分,前16位还代表时钟,后8位代表一个计数器。16位的情况下如果还按照秒为单位就会导致不够用,所以一般这里以时钟为单位。而后8位表示当前key对象的访问频率,8位只能代表255,但是redis并没有采用线性上升的方式,而是通过一个复杂的公式,通过配置两个参数来调整数据的递增速度。
    下图从左到右表示key的命中次数,从上到下表示影响因子,在影响因子为100的条件下,经过10M次命中才能把后8位值加满到255.
# +--------+------------+------------+------------+------------+------------+
# | factor | 100 hits   | 1000 hits  | 100K hits  | 1M hits    | 10M hits   |
# +--------+------------+------------+------------+------------+------------+
# | 0      | 104        | 255        | 255        | 255        | 255        |
# +--------+------------+------------+------------+------------+------------+
# | 1      | 18         | 49         | 255        | 255        | 255        |
# +--------+------------+------------+------------+------------+------------+
# | 10     | 10         | 18         | 142        | 255        | 255        |
# +--------+------------+------------+------------+------------+------------+
# | 100    | 8          | 11         | 49         | 143        | 255        |
# +--------+------------+------------+------------+------------+------------+
  uint8_t LFULogIncr(uint8_t counter) {if (counter == 255) return 255;double r = (double)rand()/RAND_MAX;double baseval = counter - LFU_INIT_VAL;if (baseval < 0) baseval = 0;double p = 1.0/(baseval*server.lfu_log_factor+1);if (r < p) counter++;return counter;}
lfu-log-factor 10
lfu-decay-time 1
  • 上面说的情况是key一直被命中的情况,如果一个key经过几分钟没有被命中,那么后8位的值是需要递减几分钟,具体递减几分钟根据衰减因子lfu-decay-time来控制
unsigned long LFUDecrAndReturn(robj *o) {unsigned long ldt = o->lru >> 8;unsigned long counter = o->lru & 255;unsigned long num_periods = server.lfu_decay_time ? LFUTimeElapsed(ldt) / server.lfu_decay_time : 0;if (num_periods)counter = (num_periods > counter) ? 0 : counter - num_periods;return counter;
}
lfu-log-factor 10
lfu-decay-time 1
  • 上面递增和衰减都有对应参数配置,那么对于新分配的key呢?如果新分配的key计数器开始为0,那么很有可能在内存不足的时候直接就给淘汰掉了,所以默认情况下新分配的key的后8位计数器的值为5(应该可配资),防止因为访问频率过低而直接被删除。
  • 低8位我们描述完了,那么高16位的时钟是用来干嘛的呢?目前我的理解是用来衰减低8位的计数器的,就是根据这个时钟与全局时钟进行比较,如果过了一定时间(做差)就会对计数器进行衰减。

redis LRU和LFU相关推荐

  1. redis lru和lfu的实现

    在redis的lru的实现与传统的lru实现不同. 具体实现在evict.c文件中,当redis需要通过释放缓存的key来释放空间时,将会通过ecict.c的freeMemoryIfNeeded()函 ...

  2. Redis 之 LRU 与 LFU

    一.前述 Redis 一直在 Nosql 中占据着很重要的地位,阅读官方文档以及 github 源代码,是一种非常好的能够帮助提升的方式,本系列博文主要参考官网翻译.Github 源代码以及部分自己的 ...

  3. Redis LRU 淘汰原理

    思考(作业):基于一个数据结构做缓存,怎么实现LRU--最长时间不被访问的元素在超过容量时删除? 问题:如果基于传统LRU 算法实现Redis LRU 会有什么问题? 需要额外的数据结构存储,消耗内存 ...

  4. 十二、Redis LRU算法详述(Least Recently Used - 最近最少使用)

    简介 Redis是基于内存存储的 key-value 数据库.我们都知道,内存虽然快但空间大小有限,当物理内存达到上限时,系统就会跑的很慢,这是因为 swap 机制会将部分内存的数据转移到swap分区 ...

  5. 缓存淘汰策略:LRU、LFU、FIFO 算法原理

    通常来说,Redis 一共有 6 种缓存淘汰策略,其中,常用的 allkeys-lru 和 volatile-lru 里面都提到了 LRU 的概念,实际上 LRU 就是缓存淘汰策略的基础算法.现在,就 ...

  6. LRU和LFU算法解析

    文章目录 LRU和LFU算法解析 LRU LRU概念 LRU算法实现 LRU算法描述 LRU算法图示 LRU C++代码 代码测试 LFU LFU概念 LFU算法实现 LFU算法描述 LFU算法图示 ...

  7. 常见缓存算法和LRU与LFU的c++实现

    目录 常见的缓存算法 LRU缓存 LRU Cache具备的操作: LRU的c++实现 双链表节点的定义: 指定容量大小 删除操作 插入操作 获取操作 插入新节点 LRU完整C++代码实现 LRU和LF ...

  8. LRU和LFU的区别

    一.概念介绍 LRU和LFU都是内存管理的页面置换算法. LRU,即:最近最少使用淘汰算法(Least Recently Used).LRU是淘汰最长时间没有被使用的页面. LFU,即:最不经常使用淘 ...

  9. Cache replacement policies(缓存替换策略)/ LRU 和 LFU等算法

    缓存是一个计算机思维,对于重复的计算,缓存其结果,下次再算这个任务的时候,不去真正的计算,而是直接返回结果,能加快处理速度.当然有些会随时间改变的东西,缓存会失效,得重新计算. 在计算中,缓存算法(通 ...

  10. java lru lfu_内存淘汰机制——LRU与LFU

    内存淘汰机制之LRU与LFU LRU(Least Recently Used):淘汰 近期最不会访问的数据 LFU(Least Frequently Used):淘汰 最不经常使用(访问次数少) 所谓 ...

最新文章

  1. c语言综合性实验数字益智游戏排行榜,C语言综合性实验报告1.doc
  2. 产品待办列表的几个最佳实践
  3. auot lisp 选择集处理_离散量的计算机处理64_1Cvs
  4. Spring Boot:构建一个RESTful Web应用程序
  5. 炸裂!万字长文拿下HTTP!
  6. [复变函数]第17堂课 5 解析函数的 Laurent 展式与孤立奇点 5. 1 解析函数的 Laurent 展式...
  7. ajax 中$.each(json,function(index,item){ }); 中的2个参数表示什么意思?
  8. 选择开源项目什么最重要?许可证排第一
  9. Windows7中右键菜单无新建文本文档选项的解决办法(注册表)
  10. error: dst ref refs/heads/zhCN_v0.13.1 receives from more than one src.
  11. Kafka实战之整合Flume和Kafka完成实时数据采集
  12. C++-从cpp文件到exe文件的过程
  13. 计算机怎么找不到视频文件格式,电脑打不开mp4格式的视频怎么办
  14. Blender建模(三)
  15. Java基础算法,获得相反数
  16. 中小企业建站方案和资源
  17. 使用 CNN 进行面部表情检测
  18. 20230216 作业
  19. cocos2dX 之音乐与音效
  20. 饥荒搜索服务器未响应,饥荒联机版代码用不了?东西无法捡起,怪物打不了?试试这个方法...

热门文章

  1. mysql_affected_rows()、mysql_fetch_row、mysql_fetch_assoc
  2. linq 根据指定条件返回集合中不重复的元素
  3. dropify插件的字符串
  4. [读书]看看你有没有忽视
  5. Dijkstra(堆优)模板
  6. Cocos2dx 链接Socket服务器
  7. 一、struts入门
  8. 小记tensorflow-1:tf.nn.conv2d 函数介绍
  9. python的paramiko模块
  10. ElasticSearch 5学习(2)——Kibana+X-Pack介绍使用(全)