工作3年的Java程序员,轻松拿到阿里P6Offer,只因为他搞明白了Redis这几个问题!! (3)

前缀为volatile-和allkeys-的区别在于二者选择要清除的键时的字典不同,volatile-前缀的策略代表从redisDb中的expire字典中选择键进行清除;allkeys-开头的策略代表从dict字典中选择键进行清除。

内存淘汰算法的具体工作原理是:

客户端执行一条新命令,导致数据库需要增加数据(比如set key value)

Redis会检查内存使用,如果内存使用超过 maxmemory,就会按照置换策略删除一些 key

新的命令执行成功

了解并手写LRU算法

LRU是Least Recently Used的缩写,也就是表示最近很少使用,也可以理解成最久没有使用。也就是说当内存不够的时候,每次添加一条数据,都需要抛弃一条最久时间没有使用的旧数据。

标准的LRU算法为了降低查找和删除元素的时间复杂度,一般采用Hash表和双向链表结合的数据结构,hash表可以赋予链表快速查找到某个key是否存在链表中,同时可以快速删除、添加节点,如图4-21所示。

双向链表的查找时间复杂度是O(n),删除和插入是O(1),借助HashMap结构,可以使得查找的时间复杂度变成O(1)

Hash表用来查询在链表中的数据位置,链表负责数据的插入,当新数据插入到链表头部时有两种情况。

链表满了,把链表尾部的数据丢弃掉,新加入的缓存直接加入到链表头中。

当链表中的某个缓存被命中时,直接把数据移到链表头部,原本在头节点的缓存就向链表尾部移动

这样,经过多次Cache操作之后,最近被命中的缓存,都会存在链表头部的方向,没有命中的,都会在链表尾部方向,当需要替换内容时,由于链表尾部是最少被命中的,我们只需要淘汰链表尾部的数据即可。

image-20210710205446429

图4-21

下面我们通过一段代码实现一个简单的LRU算法,加深大家对于LRU算法的理解。

public class LRUCache { private Node head; private Node tail; private final HashMap<String,Node> nodeHashMap; private int capacity; public LRUCache(int capacity){ this.capacity=capacity; nodeHashMap=new HashMap<>(); head=new Node(); tail=new Node(); head.next=tail; tail.prev=head; } private void removeNode(Node node){ if(node==tail){ tail=tail.prev; tail.next=null; }else if(node==head){ head=head.next; head.prev=null; }else { node.prev.next=node.next; node.next.prev=node.prev; } } private void addNodeToHead(Node node){ node.next=head.next; head.next.prev=node; node.prev=head; head.next=node; } private void addNodeToTail(Node node){ node.prev=tail.prev; node.prev.next=node; node.next=tail; tail.prev=node; } //当链表中的某个缓存被命中时,直接把数据移到链表头部,原本在头节点的缓存就向链表尾部移动 public void moveNodeToHead(Node node){ removeNode(node); addNodeToHead(node); } public String get(String key){ Node node=nodeHashMap.get(key); if(node==null){ return null; } //刷新当前节点的位置 moveNodeToHead(node); //返回value值 return node.value; } public void put(String key,String value){ Node node=nodeHashMap.get(key); if(node==null){ //不存在 //如果当前存储的数据量达到了阈值,则需要淘汰掉访问较少的数据 if(nodeHashMap.size()>=capacity){ removeNode(tail); //移除尾部节点 nodeHashMap.remove(tail.key); } node=new Node(key,value); nodeHashMap.put(key,node); addNodeToTail(node); }else{ node.value=value; //刷新当前节点的位置 moveNodeToHead(node); } } public static void main(String[] args) { LRUCache lruCache=new LRUCache(3); lruCache.put("1","1"); lruCache.put("2","2"); lruCache.put("3","3"); // lruCache.get("3"); // 增加一个访问次数之后,被清理的元素就会发生变化 System.out.println(lruCache.nodeHashMap); lruCache.put("4","4"); System.out.println(lruCache.nodeHashMap); } } class Node{ //双向链表中的节点类,存储key是因为我们在双向链表删除表尾的值时,只是返回了一个节点, //所以这个节点要包括key值,这样我们的哈希表才可以删除对应key值的映射 public String key; public String value; Node prev; Node next; public Node(){} public Node(String key, String value) { this.key = key; this.value = value; } } Redis中的LRU算法

实际上,Redis使用的LRU算法其实是一种不可靠的LRU算法,它实际淘汰的键并不一定是真正最少使用的数据,它的工作机制是:

随机采集淘汰的key,每次随机选出5个key

然后淘汰这5个key中最少使用的key

这5个key是默认的个数,具体的数值可以在redis.conf中配置

maxmemory-samples 5

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzxjsd.html