redis面试题redis的lru算法实现到手写lru算法
一、redis的缓存淘汰策略
1、redis的缓存淘汰策略回收策略
- noeviction:返回错误当内存限制达到并且客户端尝试执行会让更多内存被使用的命令(大部分的写入指令,但DEL和几个例外)
- allkeys-lru: 尝试回收最少使用的键(LRU),使得新添加的数据有空间存放。
- volatile-lru: 尝试回收最少使用的键(LRU),但仅限于在过期集合的键,使得新添加的数据有空间存放。
- allkeys-random: 回收随机的键使得新添加的数据有空间存放。
- volatile-random: 回收随机的键使得新添加的数据有空间存放,但仅限于在过期集合的键。
- volatile-ttl: 回收在过期集合的键,并且优先回收存活时间(TTL)较短的键,使得新添加的数据有空间存放。
一般默认是allkeys-lru
2、回收进程如何工作
理解回收进程如何工作是非常重要的:
- 一个客户端运行了新的命令,添加了新的数据。
- Redi检查内存使用情况,如果大于maxmemory的限制, 则根据设定好的策略进行回收。
- 一个新的命令被执行,等等。
- 所以我们不断地穿越内存限制的边界,通过不断达到边界然后不断地回收回到边界以下。
- 如果一个命令的结果导致大量内存被使用(例如很大的集合的交集保存到一个新的键),不用多久内存限制就会被这个内存使用量超越。
3、近似LRU算法
- Redis LRU 过期算法 Redis的过期算法是基于正经LRU的变种
- 之所以不使用正经LRU算法,是因为它需要消耗大量内存,对Redis现有数据结构有较大的改造。
- 这种变种算法是在现有的数据结构基础上使用随机采样方法来淘汰key。
- 他是这样操作的:给每个key增加一个额外的字段,这个字段占24bit,也就是最后一次被访问的时间戳。然后随机采样出5个key(通过maxmemory_samples来调整,采样数量越大越接近于正经的LRU算法,但是也带来了淘汰速率的问题)淘汰掉最旧的key,直到Redis占用内存小于maxmemory为止。
- 在3.0以后增加了LRU淘汰池,第一次采样的时候会把数据放到淘汰池中,之后每次采样都会和淘汰池中的数据做比较,进一步提高了与LRU算法的近似效果。
二、如何实现lru算法
可以使用java自带的linkedhashMap数据结构来实现lru;
LinkHashMap里面的entry对象除了有map结构自带的属性外,还增加了before
和after
字段;
然后除了基本的map结构外,还有一个链表结构,链表结构就是依靠这个before和after字段来链接的。
所以map的里面的每个对象都是都是链表的其中一个成员。
当我们初始化LinkHashMap的时候,会有一个参数accessOrder
,当该参数为true的时候,每次put一个key之后,就会调整链表,把key对应的entry调整到链表的头。
java代码实现
public class Lru<K,V> extends LinkedHashMap<K,V> {
Lru(int initialCapacity){
super(initialCapacity, 0.75f, true);
}
public static void main(String[] args) {
Lru<String,String > lru = new Lru(10);
lru.put("1","1");
lru.put("3","3");
lru.put("2","2");
lru.put("4","4");
lru.put("5","5");
lru.put("10","10");
System.out.println(lru);
}
protected boolean removeEldestEntry(Map.Entry eldest) {
return this.size() >=5;
}
}
要重写removeEldestEntry方法,用于判定删除键的时刻。
欢迎关注我的公众号:“从零开始的it转行生”
![](https://upload-images.jianshu.io/upload_images/22777058-d0ccf15d56f2b6c7.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)