1. 10、Redis缓存淘汰策略

1.1. Redis缓存有哪些淘汰策略

Redis提供了8中缓存淘汰策略

在设置了过期时间的数据中淘汰

  • volatile-ttl 在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
  • volatile-random 就像它的名称一样,在设置了过期时间的键值对中,进行随机删除。
  • volatile-lru 会使用 LRU 算法筛选设置了过期时间的键值对。
  • volatile-lfu 会使用 LFU 算法选择设置了过期时间的键值对。

在所有数据中淘汰

  • allkeys-random 策略,从所有键值对中随机选择并删除数据;
  • allkeys-lru 策略,使用 LRU 算法在所有数据中进行筛选
  • allkeys-lfu 策略,使用 LFU 算法在所有数据中进行筛选。

1.2. LRU算法

概念:LRU 算法的全称是 Least Recently Used,从名字上就可以看出,这是按照最近最少使用的原则来筛选数据,最不常用的数据会被筛选出来,而最近频繁使用的数据会留在缓存中。

过程:LRU会把所有数据组织成一个链表,链表头和和链表尾分别表示MRU端和LRU端,分别表示最近最常使用和最近不常用的数据,当缓存满了或者到达阈值,就将LRU端的数据淘汰。

缺点:LRU算法实现时,需要使用链表管理所有缓存数据,这会带来额外的空间开销。大量的数据访问,会频繁的进行移动链表操作,很耗时,降低了Redis缓存性能。

Redis简化采用方案:Redis默认记录每个数据最近一次访问时间戳(RedisObject中的lru字段记录),在 redis决定进行淘汰数据时,随机选择N个数据作为候选集,redis会 比较候选集合中lru字段的大小,将最小的字段从缓存中淘汰出去。

这样一来,Redis缓存不用为所有数据维护一个链表,也不用每次访问数据时都移动链表,提升了缓存性能。

建议策略

  • 优先使用allkeys-lru策略
  • 数据访问频率相差不大,没有冷热数据区分,建议使用allkeys-random策略,随机淘汰即可。
  • 置顶需求,可以使用volititle-lru策略,对置顶数据不设置过期时间,其它数据设置过期时间,根据lru策略进行淘汰

1.3. LUR算法实现

public class LRUCache {
    class DLinkedNode {
        int key;
        int value;
        DLinkedNode prev;
        DLinkedNode next;
        public DLinkedNode() {}
        public DLinkedNode(int _key, int _value) {key = _key; value = _value;}
    }

    private Map<Integer, DLinkedNode> cache = new HashMap<Integer, DLinkedNode>();
    private int size;
    private int capacity;
    private DLinkedNode head, tail;

    public LRUCache(int capacity) {
        this.size = 0;
        this.capacity = capacity;
        // 使用伪头部和伪尾部节点
        head = new DLinkedNode();
        tail = new DLinkedNode();
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 如果 key 存在,先通过哈希表定位,再移到头部
        moveToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        DLinkedNode node = cache.get(key);
        if (node == null) {
            // 如果 key 不存在,创建一个新的节点
            DLinkedNode newNode = new DLinkedNode(key, value);
            // 添加进哈希表
            cache.put(key, newNode);
            // 添加至双向链表的头部
            addToHead(newNode);
            ++size;
            if (size > capacity) {
                // 如果超出容量,删除双向链表的尾部节点
                DLinkedNode tail = removeTail();
                // 删除哈希表中对应的项
                cache.remove(tail.key);
                --size;
            }
        }
        else {
            // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
            node.value = value;
            moveToHead(node);
        }
    }

    private void addToHead(DLinkedNode node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    private void removeNode(DLinkedNode node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    private void moveToHead(DLinkedNode node) {
        removeNode(node);
        addToHead(node);
    }

    private DLinkedNode removeTail() {
        DLinkedNode res = tail.prev;
        removeNode(res);
        return res;
    }
}
© gaohueric all right reserved,powered by Gitbook文件修订时间: 2021-12-08 23:22:22

results matching ""

    No results matching ""