面试题:Redis内存淘汰策略(最大内存策略)有哪些?

内存淘汰策略(最大内存策略)是什么?

当Redis达到最大内存时,Redis 选择要删除内容的策略。

在Redis配置文件中,最大内存策略默认配置为 maxmemory-policy noeviction

  1. noeviction:不删除任何的key,但再次写入会报错。
  2. volatile-lru:对设置了过期时间的key使用LRU算法删除。
  3. allkeys-lru:对所有的key使用LRU算法删除。
  4. volatile-lfu:对设置了过期时间的key使用LFU算法删除。
  5. allkeys-lfu:对所有的key使用LFU算法删除。
  6. volatile-random:对设置了过期时间的key进行随机删除。
  7. allkeys-random:对所有的key进行随机删除。
  8. volatile-ttl:删除马上要过期的key。

LRU(Least Recently Used)最近最少使用

LRU算法的基本思想是,当缓存空间已满时,优先淘汰最近最少使用的数据。

以下是LRU算法的简要描述和实现思路:

  1. 维护一个缓存空间(哈希表),以及一个记录元素访问顺序的数据结构(双向链表)。
  2. 当需要访问一个元素时,首先在缓存空间中查找该元素:
    • 如果元素存在于缓存中,将其移动到数据结构的最前端,表示最近使用过。
    • 如果元素不存在于缓存中,需要将其添加到缓存中:
      • 如果缓存已满,移除数据结构最末尾的元素(表示最久未使用),然后将新元素添加到缓存和数据结构的最前端。
      • 如果缓存未满,直接将新元素添加到缓存和数据结构的最前端。
  3. 重复步骤2,直到所有元素访问完毕。

对应的LeetCode题目:146. LRU 缓存

使用java.util.LinkedHashMap

class LRUCache extends LinkedHashMap<Integer, Integer> {

    private int capacity;

    public LRUCache(int capacity) {
        super(capacity, 0.75f, true);
        this.capacity = capacity;
    }

    public int get(int key) {
        return super.getOrDefault(key, -1);
    }

    public void put(int key, int value) {
        super.put(key, value);
    }

    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        return super.size() > capacity;
    }
}

哈希表 + 双向链表

哈希表 + 双向链表

class LRUCache {

    private int capacity;
    private final Map<Integer, DoubleLinkedList.Node> map = new HashMap<>();
    private final DoubleLinkedList list = new DoubleLinkedList();

    public LRUCache(int capacity) {
        this.capacity = capacity;
    }

    public int get(int key) {
        DoubleLinkedList.Node node = map.get(key);
        // 没找到返回-1
        if (node == null) {
            return -1;
        }
        // 将使用到的节点移动到队头
        list.remove(node);
        list.addHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DoubleLinkedList.Node node = map.get(key);
        // key已经存在,更新value
        if (node != null) {
            node.value = value;
            list.remove(node);
            list.addHead(node);
            return;
        }
        // 空间不足,淘汰最后一个
        if (map.size() >= capacity) {
            DoubleLinkedList.Node last = list.getLast();
            map.remove(last.key);
            list.remove(last);
        }
        // 将新节点放入
        DoubleLinkedList.Node newKNode = new DoubleLinkedList.Node(key, value);
        map.put(key, newKNode);
        list.addHead(newKNode);
    }

    static class DoubleLinkedList {
        Node head;
        Node tail;

        public DoubleLinkedList() {
            // 创建虚拟的头尾节点
            this.head = new Node();
            this.tail = new Node();
            this.head.next = this.tail;
            this.tail.prev = this.head;
        }

		// 将元素添加到头结点后
        public void addHead(Node node) {
            node.next = head.next;
            node.prev = head;
            head.next.prev = node;
            head.next = node;
        }

		// 删除结点
        public void remove(Node node) {
            node.next.prev = node.prev;
            node.prev.next = node.next;
            node.prev = null;
            node.next = null;
        }

        public Node getLast() {
            return tail.prev;
        }

        static class Node {
            int key;
            int value;
            Node prev;
            Node next;

            public Node() {
                this.prev = null;
                this.next = null;
            }

            public Node(int key, int value) {
                this.key = key;
                this.value = value;
                this.prev = null;
                this.next = null;
            }
        }
    }
}

LFU(Least Frequently Used)最不经常使用

LFU算法的基本思想是,当缓存空间已满时,优先淘汰最不经常使用的数据。

以下是LFU算法的简要描述和实现思路:

维护一个缓存空间(可以是数组、链表或哈希表),以及一个记录元素访问频率的计数器。

每次访问一个元素时,首先在缓存空间中查找该元素:

  1. 如果元素存在于缓存中,将其访问频率加一。
  2. 如果元素不存在于缓存中,需要将其添加到缓存中:
    • 如果缓存已满,找到访问频率最低的元素,如果有多个元素的访问频率相同,则选择最早加入缓存的元素进行淘汰。
    • 如果缓存未满,直接将新元素添加到缓存,并将其访问频率设置为1。
  3. 重复步骤2,直到所有元素访问完毕。

对应的LeetCode题目:460. LFU 缓存

多个哈希表

class LFUCache {
    private int capacity;
    private int minFrequency;
    private Map<Integer, Integer> keyToValue;
    // key->使用频率
    private Map<Integer, Integer> keyToFrequency;
    // 使用频率->key
    private Map<Integer, LinkedHashSet<Integer>> frequencyToKeys;

    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.minFrequency = 0;
        this.keyToValue = new HashMap<>();
        this.keyToFrequency = new HashMap<>();
        this.frequencyToKeys = new HashMap<>();
    }

    public int get(int key) {
        if (!keyToValue.containsKey(key)) {
            return -1;
        }

        int value = keyToValue.get(key);
        int frequency = keyToFrequency.get(key);
        updateFrequency(key, frequency);
        return value;
    }

    public void put(int key, int value) {
        if (capacity <= 0) {
            return;
        }

        if (keyToValue.containsKey(key)) {
            keyToValue.put(key, value);
            int frequency = keyToFrequency.get(key);
            updateFrequency(key, frequency);
        } else {
            if (keyToValue.size() >= capacity) {
                evict();
            }

            keyToValue.put(key, value);
            keyToFrequency.put(key, 1);
            frequencyToKeys.computeIfAbsent(1, k -> new LinkedHashSet<>()).add(key);
            minFrequency = 1;
        }
    }

    private void updateFrequency(int key, int frequency) {
        int newFrequency = frequency + 1;
        keyToFrequency.put(key, newFrequency);
        frequencyToKeys.get(frequency).remove(key);

        if (frequencyToKeys.get(frequency).isEmpty() && frequency == minFrequency) {
            minFrequency = newFrequency;
        }

        frequencyToKeys.computeIfAbsent(newFrequency, k -> new LinkedHashSet<>()).add(key);
    }

    private void evict() {
        LinkedHashSet<Integer> keys = frequencyToKeys.get(minFrequency);
        int evictKey = keys.iterator().next();
        keys.remove(evictKey);
        keyToValue.remove(evictKey);
        keyToFrequency.remove(evictKey);
    }
}

哈希表 + 平衡二叉树

class LFUCache {
    // 缓存容量,时间戳
    int capacity, time;
    Map<Integer, Node> key_table;
    TreeSet<Node> S;

    public LFUCache(int capacity) {
        this.capacity = capacity;
        this.time = 0;
        key_table = new HashMap<Integer, Node>();
        S = new TreeSet<Node>();
    }
    
    public int get(int key) {
        if (capacity == 0) {
            return -1;
        }
        // 如果哈希表中没有键 key,返回 -1
        if (!key_table.containsKey(key)) {
            return -1;
        }
        // 从哈希表中得到旧的缓存
        Node cache = key_table.get(key);
        // 从平衡二叉树中删除旧的缓存
        S.remove(cache);
        // 将旧缓存更新
        cache.cnt += 1;
        cache.time = ++time;
        // 将新缓存重新放入哈希表和平衡二叉树中
        S.add(cache);
        key_table.put(key, cache);
        return cache.value;
    }
    
    public void put(int key, int value) {
        if (capacity == 0) {
            return;
        }
        if (!key_table.containsKey(key)) {
            // 如果到达缓存容量上限
            if (key_table.size() == capacity) {
                // 从哈希表和平衡二叉树中删除最近最少使用的缓存
                key_table.remove(S.first().key);
                S.remove(S.first());
            }
            // 创建新的缓存
            Node cache = new Node(1, ++time, key, value);
            // 将新缓存放入哈希表和平衡二叉树中
            key_table.put(key, cache);
            S.add(cache);
        } else {
            // 这里和 get() 函数类似
            Node cache = key_table.get(key);
            S.remove(cache);
            cache.cnt += 1;
            cache.time = ++time;
            cache.value = value;
            S.add(cache);
            key_table.put(key, cache);
        }
    }
}

class Node implements Comparable<Node> {
    int cnt, time, key, value;

    Node(int cnt, int time, int key, int value) {
        this.cnt = cnt;
        this.time = time;
        this.key = key;
        this.value = value;
    }

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof Node) {
            Node rhs = (Node) anObject;
            return this.cnt == rhs.cnt && this.time == rhs.time;
        }
        return false;
    }

    public int compareTo(Node rhs) {
        return cnt == rhs.cnt ? time - rhs.time : cnt - rhs.cnt;
    }

    public int hashCode() {
        return cnt * 1000000007 + time;
    }
}

参考

力扣官方题解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、什么是 Redis? 2、Redis 相比 memcached 有哪些优势? 3、Redis 支持哪几种数据类型? 4、Redis 主要消耗什么物理资源? 5、Redis 的全称是什么? 6、Redis 有哪几种数据淘汰策略? 7、Redis 官方为什么不提供 Windows 版本? 8、一个字符串类型的值能存储最大容量是多少? 9、为什么 Redis 需要把所有数据放到内存中? 10、Redis 集群方案应该怎么做?都有哪些方案? 11、Redis 集群方案什么情况下会导致整个集群不可用? 12、MySQL 里有 2000w 数据,Redis 中只存 20w 的数据, 如何保证 Redis 中的数据都是热点数据? 13、Redis 有哪些适合的场景? 14、Redis 支持的 Java 客户端都有哪些?官方推荐用哪个? 15、RedisRedisson 有什么关系? 16、Jedis 与 Redisson 对比有什么优缺点? 17、Redis 如何设置密码及验证密码? 18、说说 Redis 哈希槽的概念? 19、Redis 集群的主从复制模型是怎样的? 20、Redis 集群会有写操作丢失吗?为什么? 21、Redis 集群之间是如何复制的? 22、Redis 集群最大节点个数是多少? 23、Redis 集群如何选择数据库? 24、怎么测试 Redis 的连通性? 25、Redis 中的管道有什么用? 26、怎么理解 Redis 事务? 27、Redis 事务相关的命令有哪几个? 28、Redis key 的过期时间和永久有效分别怎么设置? 29、Redis 如何做内存优化? 30、Redis 回收进程如何工作的? 31、Redis 回收使用的是什么算法? 32、Redis 如何做大量数据插入? 33、为什么要做 Redis 分区? 34、你知道有哪些 Redis 分区实现方案? 35、Redis 分区有什么缺点? 36、Redis 持久化数据和缓存怎么做扩容? 37、分布式 Redis 是前期做还是后期规模上来了再做好?为 什么? 38、Twemproxy 是什么? 39、支持一致性哈希的客户端有哪些? 40、Redis 与其他 key-value 存储有什么不同? 41、Redis内存占用情况怎么样? 42、都有哪些办法可以降低 Redis内存使用情况呢? 43、查看 Redis 使用情况及状态信息用什么命令? 44、Redis内存用完了会发生什么? 45、Redis 是单线程的,如何提高多核 CPU 的利用率? 46、一个 Redis 实例最多能存放多少的 keys?List、Set、 Sorted Set 他们最多能存放多少元素? 47、Redis 常见性能问题和解决方案? 48、Redis 提供了哪几种持久化方式? 49、如何选择合适的持久化方式? 50、修改配置不重启 Redis 会实时生效吗?

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值