Redis对象头中的lru字段,在LRU模式下和LFU模式下使用方式并不相同。
2.1 LRU实现方式
在LRU模式,lru字段存储的是key被访问时Redis的时钟server.lrulock(Redis为了保证核心单线程服务性能,缓存了Unix操作系统时钟,默认每毫秒更新一次,缓存的值是Unix时间戳取模2^24)。当key被访问的时候,Redis会更新这个key的对象头中lru字段的值。
因此在LRU模式下,Redis可以根据对象头中的lru字段记录的值,来比较最后一次key的访问时间。
用Java代码演示一个简单的Redis-LRU算法:
- Redis对象头
package com.lizba.redis.lru;
/**
*
* Redis对象头
*
* @Author: Liziba
* @Date: 2021/9/22 22:40
*/
public class RedisHead {
/** 时间 */
private Long lru;
/** 具体数据 */
private Object body;
public RedisHead setLru(Long lru) {
this.lru = lru;
return this;
}
public RedisHead setBody(Object body) {
this.body = body;
return this;
}
public Long getLru() {
return lru;
}
public Object getBody() {
return body;
}
}
- Redis LRU实现代码
package com.lizba.redis.lru;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
/**
*
* Redis中LRU算法的实现demo
*
* @Author: Liziba
* @Date: 2021/9/22 22:36
*/
public class RedisLruDemo {
/**
* 缓存容器
*/
private ConcurrentHashMap<String, RedisHead> cache;
/**
* 初始化大小
*/
private int initialCapacity;
public RedisLruDemo(int initialCapacity) {
this.initialCapacity = initialCapacity;
this.cache = new ConcurrentHashMap<>(initialCapacity);
;
}
/**
* 设置key/value 设置的时候更新LRU
* @param key
* @param body
*/
public void set(String key, Object body) {
// 触发LRU淘汰
synchronized (RedisLruDemo.class) {
if (!cache.containsKey(key) && cache.size() >= initialCapacity) {
this.flushLruKey();
}
}
RedisHead obj = this.getRedisHead().setBody(body).setLru(System.currentTimeMillis());
cache.put(key, obj);
}
/**
* 获取key,存在则更新LRU
* @param key
* @return
*/
public Object get(String key) {
RedisHead result = null;
if (cache.containsKey(key)) {
result = cache.get(key);
result.setLru(System.currentTimeMillis());
}
return result;
}
/**
* 清除LRU key
*/
private void flushLruKey() {
List sortData = cache.keySet()
.stream()
.sorted(Comparator.comparing(key -> cache.get(key).getLru()))
.collect(Collectors.toList());
String removeKey = sortData.get(0);
System.out.println( "淘汰 -> " + "lru : " + cache.get(removeKey).getLru() + " body : " + cache.get(removeKey).getBody());
cache.remove(removeKey);
if (cache.size() >= initialCapacity) {
this.flushLruKey();
}
return;
}
/**
* 获取所有数据测试用
* @return
*/
public List getAll() {
return cache.keySet().stream().map(key -> cache.get(key)).collect(Collectors.toList());
}
private RedisHead getRedisHead() {
return new RedisHead();
}
}
- 测试代码
package com.lizba.redis.lru;
import java.util.Random;
import java.util.concurrent.TimeUnit;
/**
*
* 测试LRU
*
* @Author: Liziba
* @Date: 2021/9/22 22:51
*/
public class TestRedisLruDemo {
public static void main(String[] args) throws InterruptedException {
RedisLruDemo demo = new RedisLruDemo(10);
// 先加入10个key,此时cache达到容量,下次加入会淘汰key
for (int i = 0; i < 10; i++) {
demo.set(i + “”, i);
}
// 随机访问前十个key,这样可以保证下次加入时随机淘汰
for (int i = 0; i < 20; i++) {
int nextInt = new Random().nextInt(10);
TimeUnit.SECONDS.sleep(1);
demo.get(nextInt + “”);
}
// 再次添加5个key,此时每次添加都会触发淘汰
for (int i = 10; i < 15; i++) {
demo.set(i + “”, i);
}
System.out.println(“-------------------------------------------”);
demo.getAll().forEach( redisHead -> System.out.println("剩余 -> " + "lru : " + redisHead.getLru() + " body : " + redisHead.getBody()));
}
}
- 测试结果
2.2 LFU实现方式
在LFU模式下,Redis对象头的24bit lru字段被分成两段来存储,高16bit存储ldt(Last Decrement Time),低8bit存储logc(Logistic Counter)。
2.2.1 ldt(Last Decrement Time)
高16bit用来记录最近一次计数器降低的时间,由于只有8bit,存储的是Unix分钟时间戳取模2^16,16bit能表示的最大值为65535(65535/24/60≈45.5),大概45.5天会折返(折返指的是取模后的值重新从0开始)。
Last Decrement Time计算的算法源码:
/* Return the current time in minutes, just taking the least significant
* 16 bits. The returned time is suitable to be stored as LDT (last decrement
* time) for the LFU implementation. */
// server.unixtime是Redis缓存的Unix时间戳
// 可以看出使用的Unix的分钟时间戳,取模2^16
unsigned long LFUGetTimeInMinutes(void) {
return (server.unixtime/60) & 65535;
}
/* Given an object last access time, compute the minimum number of minutes
* that elapsed since the last access. Handle overflow (ldt greater than
* the current 16 bits minutes time) considering the time as wrapping
* exactly once. */
unsigned long LFUTimeElapsed(unsigned long ldt) {
// 获取系统当前的LFU time
unsigned long now = LFUGetTimeInMinutes();
// 如果now >= ldt 直接取差值
if (now >= ldt) return now-ldt;
// 如果now < ldt 增加上65535
// 注意Redis 认为折返就只有一次折返,多次折返也是一次,我思考了很久感觉这个应该是可以接受的,本身Redis的淘汰算法就带有随机性
return 65535-ldt+now;
}
2.2.2 logc(Logistic Counter)
低8位用来记录访问频次,8bit能表示的最大值为255,logc肯定无法记录真实的Rediskey的访问次数,其实从名字可以看出存储的是访问次数的对数值,每个新加入的key的logc初始值为5(LFU_INITI_VAL),这样可以保证新加入的值不会被首先选中淘汰;logc每次key被访问时都会更新;此外,logc会随着时间衰减。
2.2.3 logc 算法调整
redis.conf 提供了两个配置项,用于调整LFU的算法从而控制Logistic Counter的增长和衰减。
- lfu-log-factor 用于调整Logistic Counter的增长速度,lfu-log-factor值越大,Logistic Counter增长越慢。
Redis Logistic Counter增长的源代码:
/* Logarithmically increment a counter. The greater is the current counter value
* the less likely is that it gets really implemented. Saturate it at 255. */
uint8_t LFULogIncr(uint8_t counter) {
// Logistic Counter最大值为255
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性问题
希望大家在今年一切顺利,进到自己想进的公司,共勉!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
,可以扫码获取!!(备注:Android)**
最后
最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上相关的我搜集整理的24套腾讯、字节跳动、阿里、百度2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了视频和PDF(实际上比预期多花了不少精力),包知识脉络 + 诸多细节。
还有 高级架构技术进阶脑图 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
Android 基础知识点
Java 基础知识点
Android 源码相关分析
常见的一些原理性问题
[外链图片转存中…(img-NvESFkro-1713406664451)]
希望大家在今年一切顺利,进到自己想进的公司,共勉!
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!