思考(作业):基于一个数据结构做缓存,怎么实现LRU——最长时间不被访问的元素在超过容量时删除?

问题:如果基于传统LRU 算法实现Redis LRU 会有什么问题?

需要额外的数据结构存储,消耗内存。

Redis LRU 对传统的LRU 算法进行了改良,通过随机采样来调整算法的精度。

如果淘汰策略是LRU,则根据配置的采样值maxmemory_samples(默认是5 个),随机从数据库中选择m 个key, 淘汰其中热度最低的key 对应的缓存数据。所以采样参数m 配置的数值越大, 就越能精确的查找到待淘汰的缓存数据,但是也消耗更多的CPU 计算,执行效率降低。

问题:如何找出热度最低的数据?

Redis 中所有对象结构都有一个lru 字段, 且使用了unsigned 的低24 位,这个字段用来记录对象的热度。对象被创建时会记录lru 值。在被访问的时候也会更新lru 的值。但是不是获取系统当前的时间戳,而是设置为全局变量server.lruclock 的值。

源码:server.h

typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or* LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */int refcount;void *ptr;
} robj;

server.lruclock 的值怎么来的?

Redis 中有个定时处理的函数serverCron , 默认每100 毫秒调用函数updateCachedTime 更新一次全局变量的server.lruclock 的值,它记录的是当前unix时间戳。

源码:server.c

void updateCachedTime(void) {time_t unixtime = time(NULL);atomicSet(server.unixtime,unixtime);server.mstime = mstime();struct tm tm;localtime_r(&server.unixtime,&tm);server.daylight_active = tm.tm_isdst;
}

问题:为什么不获取精确的时间而是放在全局变量中?不会有延迟的问题吗?

这样函数lookupKey 中更新数据的lru 热度值时,就不用每次调用系统函数time,可以提高执行效率。

OK,当对象里面已经有了LRU 字段的值,就可以评估对象的热度了。

函数estimateObjectIdleTime 评估指定对象的lru 热度,思想就是对象的lru 值和全局的server.lruclock 的差值越大(越久没有得到更新), 该对象热度越低。

源码evict.c

/* Given an object returns the min number of milliseconds the object was never
* requested, using an approximated LRU algorithm. */
unsigned long long estimateObjectIdleTime(robj *o) {unsigned long long lruclock = LRU_CLOCK();if (lruclock >= o->lru) {return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;} else {return (lruclock + (LRU_CLOCK_MAX - o->lru)) *LRU_CLOCK_RESOLUTION;}
}

server.lruclock 只有24 位,按秒为单位来表示才能存储194 天。当超过24bit 能表示的最大时间的时候,它会从头开始计算。

server.h

#define LRU_CLOCK_MAX ((1<<LRU_BITS)-1) /* Max value of obj->lru */

在这种情况下,可能会出现对象的lru 大于server.lruclock 的情况,如果这种情况出现那么就两个相加而不是相减来求最久的key。

为什么不用常规的哈希表+双向链表的方式实现?需要额外的数据结构,消耗资源。而Redis LRU 算法在sample 为10 的情况下,已经能接近传统LRU 算法了。

https://redis.io/topics/lru-cache

问题:除了消耗资源之外,传统LRU 还有什么问题?

如图,假设A 在10 秒内被访问了5 次,而B 在10 秒内被访问了3 次。因为B 最后一次被访问的时间比A 要晚,在同等的情况下,A 反而先被回收。

问题:要实现基于访问频率的淘汰机制,怎么做?

LFU

server.h

typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or* LFU data (least significant 8 bits frequency* and most significant 16 bits access time). */int refcount;void *ptr;
} robj;

当这24 bits 用作LFU 时,其被分为两部分:

高16 位用来记录访问时间(单位为分钟,ldt,last decrement time)

低8 位用来记录访问频率,简称counter(logc,logistic counter)

counter 是用基于概率的对数计数器实现的,8 位可以表示百万次的访问频率。

对象被读写的时候,lfu 的值会被更新。

db.c——lookupKey

void updateLFU(robj *val) {unsigned long counter = LFUDecrAndReturn(val);counter = LFULogIncr(counter);val->lru = (LFUGetTimeInMinutes()<<8) | counter;
}

增长的速率由,lfu-log-factor 越大,counter 增长的越慢

redis.conf 配置文件

# lfu-log-factor 10

如果计数器只会递增不会递减,也不能体现对象的热度。没有被访问的时候,计数器怎么递减呢?

减少的值由衰减因子lfu-decay-time(分钟)来控制,如果值是1 的话,N 分钟没有访问就要减少N。

redis.conf 配置文件

# lfu-decay-time 1

Redis LRU 淘汰原理相关推荐

  1. node怎么把token放到redis_从零开始手写 redis(八)朴素 LRU 淘汰算法性能优化

    前言 java从零手写实现redis(一)如何实现固定大小的缓存? java从零手写实现redis(三)redis expire 过期原理 java从零手写实现redis(三)内存数据如何重启不丢失? ...

  2. mysql缓存淘汰机制_聊聊缓存淘汰算法-LRU 实现原理

    前言 我们常用缓存提升数据查询速度,由于缓存容量有限,当缓存容量到达上限,就需要删除部分数据挪出空间,这样新数据才可以添加进来.缓存数据不能随机删除,一般情况下我们需要根据某种算法删除缓存数据.常用淘 ...

  3. Redis内存淘汰策略LRU、LFU详解

    Redis内存淘汰原因 Redis是一种内存数据库,redis的容量往往有限,无法存放所有的数据.当内存满了的时候,并且这个时候还需要往Redis中放入新的数据,就需要将Redis中的一部分数据淘汰了 ...

  4. redis基础和原理全覆盖

    Redis 是什么 Redis: REmote DIctionary Server(远程字典服务器) 完全开源免费,C语言编写遵守BSD协议,一个高性能的(key/value)分布式内存数据库.基于内 ...

  5. Redis缓存淘汰策略

    文章目录 noeviction allkeys-lru allkeys-lfu volatile-lru volatile-lfu allkeys-random volatile-random vol ...

  6. Redis LRU算法

    一.配置Redis内存淘汰策略 maxmemory 100mbmaxmemory-policy allkeys-lrumaxmemory-samples 5 注意:Redis的LRU算法并非完整的实现 ...

  7. linux页面算法源码,LRU算法原理解析

    LRU是Least Recently Used的缩写,即最近最少使用,常用于页面置换算法,是为虚拟页式存储管理服务的. 现代操作系统提供了一种对主存的抽象概念虚拟内存,来对主存进行更好地管理.他将主存 ...

  8. redis的淘汰策略

    在 redis 中,对于已经过期的数据,Redis 采用两种策略来处理这些数据,分别是惰性删除和定期删除 惰性删除 惰性删除不会去主动删除数据,而是在访问数据的时候,再检查当前键值是否过期,如果过期则 ...

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

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

最新文章

  1. linux检查正则表达式,正则表达式及Linux文本检查工具
  2. PowerShell runspace 的创建,使用和查错
  3. 【Linux】 iptables vs firewalld
  4. scanf与gets的区分
  5. python汉诺塔问题_Python汉诺塔问题
  6. 前端学习(549):node的 http模块
  7. Snap Shots 出了新东西
  8. Vmware工作笔记-通过光驱位与虚拟机(Vmware)共享数据【含iso制作】
  9. 游泳后精疲力尽_精疲力尽的编程后如何重回正轨
  10. mysql查询缓存优化配置_mysql 优化之查询高速缓冲配置 小记
  11. 转码器ffmpeg安装
  12. com.alibaba.fastjson.JSONException: can‘t create non-static inner class inst
  13. Android报错:FAILED:_nl_intern_locale_data: ?? ‘cnt < (sizeof (_nl_value_type_LC_TIME)
  14. Perl脚本语言学习1:
  15. 身居乱世之中,重新审视“活法
  16. 《缠中说禅108课》15:没有趋势,没有背驰
  17. Android 集成支付宝第三方登录
  18. Busting Frame Busting
  19. Java ZIP压缩 ZipArchiveEntry实现ZIP高效、Java多线程压缩、可控CPU使用率 Apache commons-compress
  20. php中的preg_replace函数,PHP正则替换preg_replace函数如何使用

热门文章

  1. os.path python使用遍历文件夹文件
  2. iOS FMDB官方使用文档 G-C-D的使用 提高性能(翻译)(转)
  3. 【新东方老师推荐】老师推荐--听说——这是全球最值得听的、最好听的100首英文歌...
  4. 【JDK源码】java.io包常用类详解
  5. 基于数据库的分布式锁实现
  6. 【Android QR Code】开源项目:ZXing(一)导入项目
  7. 记录一下pom文件scope各种配置所作的行为
  8. config对象的使用及常用方法
  9. IIS服务中五种身份验证的灵活运用-转
  10. 读取速度贼快的省市区地址库