Redis的高性能网络IO模型

Redis的高性能网络IO模型_Lucifer Zhao的博客-CSDN博客

Redis的持久化机制

Redis的持久化机制_Lucifer Zhao的博客-CSDN博客

Redis的过期策略

为了保证内存利用率,会把设置了过期时间并且过期的数据进行删除

惰性过期(被动过期): 通过key拿取数据时才判断是否过期

定期过期: 定期扫描,删除过期的数据

Redis的内存淘汰策略

数据全放在内存中,内存的容量是有限的,有可能存在一些无效的缓存(就是长时间不使用的缓存),如果没有设置过期时间,这些无效的缓存不光会占用内存,导致物理内存不够,还会降低IO性能,时间复杂度增加,所以针对这些问题,Redis一般通过以下方案解决:

  1. 设置过期时间
  2. 增加内存
  3. 设置内存淘汰策略

Redis安装好之后默认64位,内存没有最大限制,如果是32位系统,默认3GB

当内存达到上限时,设置值会报OOM错误,如下所示:

// 获取最大内存设置,如果返回0则代表没有限制,单位是byte
config get maxmemory
// 通过下面命令设置最大内存,如4G
config set maxmemory 4294967296

当内存达到上限时,可以设置LRU算法来释放无效的key

从设置了过期时间的key里扫描,以hash桶为维度扫描,最多扫20个key,一旦扫到某个hash桶必须扫完这个桶里的所有key,如果删除比例超过10%,会继续扫描并删除过期的key,但是最多扫描16次

Redis中提供了8种内存淘汰策略:

  • volatile-lru:针对设置了过期时间的key,使用LRU算法进行淘汰
  • allkeys-lru:针对所有key使用LRU算法进行淘汰
  • volatile-lfu:针对设置了过期时间的key,使用LFU算法进行淘汰
  • allkeys-lfu:针对所有key使用LFU算法进行淘汰
  • volatile-random: 从设置了过期时间的key中随机删除
  • allkeys-random: 从所有key中随机删除
  • volatile-ttl:删除生存时间最近的一个键
  • noeviction(默认策略):不删除键,返回错误OOM,只能读取不能写入

通过修改 maxmemory-policy 配置来设置淘汰策略,默认noeviction

核心本质:选择哪些数据进行清理,选择什么方式去清理数据 

LRU算法

Least Recently Used(最近很少使用)

LRU算法简单逻辑图

双向链表存放很久没有使用的key,hashmap定位某个节点,用来做索引方便查询;随机采集5个key,将5个key加入到双向链表中,链表不停滚动,根据key的使用频率进行排序,最少使用的key就会被移动到队尾从而移除。

LRU算法简单实现

public class LRUCache {
    private Node head = new Node();;
    private Node tail = new Node();;
    private HashMap<String, Node> nodeHashMap = new HashMap<>();;
    private final int capacity;//容量

    public LRUCache(int capacity) {
        this.capacity = capacity;
        head.next = tail;
        tail.prev = head;
    }
    public String get(String key) {
        Node node = nodeHashMap.get(key);
        if (null == node) {
            return null;
        }
        // 刷新当前key的位置,尾部是将被移除的很少使用的,头部是热点访问数据
        moveNodeToHead(node);
        return node.value;
    }

    // 在链表中移动node节点,可以先删除节点,然后重新添加
    private void moveNodeToHead(Node node) {
        removeNode(node);
        addNodeToHead(node);
    }
    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) {
        head.prev = node;
        node.next = head;
        node.prev = null;
        head = node;
    }
    public void put(String key, String value) {
        Node node = nodeHashMap.get(key);
        if (null == node) { //如果node不存在,则添加到链表
            if (nodeHashMap.size() >= capacity) { //如果hashmap已存数据长度大于容量,则移除尾部不常访问数据
                removeNode(tail); //移除尾部节点
                nodeHashMap.remove(tail.key); //将key从hashmap中移除
            }
            // 添加新的节点到头部
            node = new Node(key, value);
            nodeHashMap.put(key, node);
            addNodeToHead(node);
        } else { //如果存在,更新数据,并将热点数据移动到链表头部
            node.value = value;
            moveNodeToHead(node);
        }
    }

    class Node {
        private String key;
        private String value;
        Node prev;
        Node next;
        public Node() {}
        public Node(String key, String value) {
            this.key = key;
            this.value = value;
        }
    }
}

Redis中的LRU算法

Redis3.0之后优化了LRU算法,有以下四种情况:

  1. 回收池已满,新采集的key与回收池中的key相比,空闲时间最小,则不做任何处理
  2. 回收池未满,新采集的key空闲时间比回收池中部分元素大,则插入到对应位置,后面元素往后移动一个位置
  3. 回收池未满,新采集的key空闲时间最小,则插入到尾部
  4. 回收池已满,新采集的key空闲时间比回收池中部分元素大,则移除头部元素,将新采集key插入到对应位置,前面的元素往前移动一个位置

LFU算法

Least Frequently Used(最少使用),与LRU算法相比,引入了访问次数,但是存在时效性问题

上面的都是Hashmap,用来定位节点位置;下面是一个横向+纵向的双向链表,横向是根据key访问次数排序的双向链表,每个横向节点也是一个双向链表,用来保存相同访问次数的元素

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐