深入理解Redis内存淘汰策略

目录
  • 一、内存回收
  • 二、设置内存
  • 三、内存淘汰策略
  • 四、LRU
    • 4.1 LRU算法
    • 4.2 redis中的LRU算法
  • 五、LFU

一、内存回收

长时间不使用的缓存

  • 降低IO性能
  • 物理内存不够

很多人了解了Redis的好处之后,于是把任何数据都往Redis中放,如果使用不合理很容易导致数据超过Redis的内存,这种情况会出现什么问题呢?

  • Redis中有很多无效的缓存,这些缓存数据会降低数据IO的性能,因为不同的数据类型时间复杂度算法不同,数据越多可能会造成性能下降
  • 随着系统的运行,redis的数据越来越多,会导致物理内存不足。通过使用虚拟内存(VM),将很少访问的数据交换到磁盘上,腾出内存空间的方法来解决物理内存不足的情况。虽然能够解决物理内存不足导致的问题,但是由于这部分数据是存储在磁盘上,如果在高并发场景中,频繁访问虚拟内存空间会严重降低系统性能。

所以遇到这类问题的时候,我们一般有几种方法。

  • 对每个存储到redis中的key设置过期时间,这个根据实际业务场景来决定。否则,再大的内存都会随着系统运行被消耗完
  • 增加内存
  • 使用内存淘汰策略

二、设置内存

在实际生产环境中,服务器不仅仅只有Redis,为了避免Redis内存使用过多对其他程序造成影响,我们一般会设置最大内存。Redis默认的最大内存 maxmemory=0 ,表示不限制Redis内存的使用。我们可以修改 redis.conf 文件,设置Redis最大使用的内存。

# 单位为byte
maxmemory <bytes> 2147483648(2G)

如何查看当前Redis最大内存设置呢,进入到Redis-Cli控制台,输入下面这个命令。

config get maxmemory

当Redis中存储的内存超过maxmemory时,会怎么样呢?下面我们做一个实验

在redis-cli控制台输入下面这个命令,把最大内存设置为1个字节。

config set maxmemory 1

通过下面的命令存储一个string类型的数据

set name mvp

此时,控制台会得到下面这个错误信息

(error) OOM command not allowed when used memory > 'maxmemory'.

三、内存淘汰策略

设置了maxmemory的选项,redis内存使用达到上限。可以通过设置LRU算法来删除部分key,释放空间。默认是按照过期时间的,如果set时候没有加上过期时间就会导致数据写满maxmemory。Redis中提供了一种内存淘汰策略,当内存不足时,Redis会根据相应的淘汰规则对key数据进行淘汰。Redis一共提供了8种淘汰策略,默认的策略为noeviction,当内存使用达到阈值的时候,所有引起申请内存的命令会都会报错。

  • volatile-lru,针对设置了过期时间的key,使用lru算法进行淘汰。
  • allkeys-lru,针对所有key使用lru算法进行淘汰。
  • volatile-lfu,针对设置了过期时间的key,使用lfu算法进行淘汰。
  • allkeys-lfu,针对所有key使用lfu算法进行淘汰。
  • volatile-random,从所有设置了过期时间的key中使用随机淘汰的方式进行淘汰。
  • allkeys-random,针对所有的key使用随机淘汰机制进行淘汰。
  • volatile-ttl,针对设置了过期时间的key,越早过期的越先被淘汰。
  • noeviction,不会淘汰任何数据,当使用的内存空间超过 maxmemory 值时,再有写请求来时返回错误。

前缀为volatile-和allkeys-的区别在于二者选择要清除的键时的字典不同,volatile-前缀的策略代表从redisDb中的expire字典中选择键进行清除;allkeys-开头的策略代表从dict字典中选择键进行清除。
内存淘汰算法的具体工作原理是:

  • 客户端执行一条新命令,导致数据库需要增加数据(比如set key value)
  • Redis会检查内存使用情况,如果内存使用超过 maxmemory,就会按照内存淘汰策略删除一些 key
  • 新的命令执行成功

四、LRU

4.1 LRU算法

LRU是Least Recently Used的缩写,也就是表示最近很少使用,也可以理解成最久没有使用。也就是说当内存不够的时候,每次添加一条数据,都需要抛弃一条最久时间没有使用的旧数据。标准的LRU算法为了降低查找和删除元素的时间复杂度,一般采用Hash表和双向链表结合的数据结构,hash表可以赋予链表快速查找到某个key是否存在链表中,同时可以快速删除、添加节点,如下图所示。

双向链表的查找时间复杂度是O(n),删除和插入是O(1),借助HashMap结构,可以使得查找的时间复杂度变成O(1)。
Hash表用来查询在链表中的数据位置,链表负责数据的插入,当新数据插入到链表头部时有两种情况。

  • 链表满了,把链表尾部的数据丢弃掉,新加入的缓存直接加入到链表头中。
  • 当链表中的某个缓存被命中时,直接把数据移到链表头部,原本在头节点的缓存就向链表尾部移动。

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

import java.util.HashMap;

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<>();
        tail=new Node();
        head=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 moveNodeToHead(Node node){
        removeNode(node);
        addNodeToHead(node);
    }
    public String get(String key){
        Node node=nodeHashMap.get(key);
        if(node==null){
            return null;
        }
        //刷新当前key的位置
        moveNodeToHead(node);
        return node.value;
    }
    public void put(String key,String value){
        Node node=nodeHashMap.get(key);
        if(node==null){ //如果不存在,则添加到链表
            if(nodeHashMap.size()>=capacity){ //大于容量,则需要移除老的数据
                removeNode(tail); //移除尾部节点(tail节点是属于要被淘汰数据)
                nodeHashMap.remove(tail.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;
        }
    }
}

4.2 redis中的LRU算法

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

  • 随机采集淘汰的key,每次随机选出5个key
  • 然后淘汰这5个key中最少使用的key

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

maxmemory-samples 5

当近似LRU算法取值越大的时候就会越接近真实的LRU算法,因为取值越大获取的数据越完整,淘汰中的数据就更加接近最少使用的数据。这里其实涉及一个权衡问题,如果需要在所有的数据中搜索最符合条件的数据,那么一定会增加系统的开销,Redis是单线程的,所以耗时的操作会谨慎一些。为了在一定成本内实现相对的LRU,早期的Redis版本是基于采样的LRU,也就是放弃了从所有数据中搜索解改为采样空间搜索最优解。Redis3.0版本之后,Redis作者对于基于采样的LRU进行了一些优化:

  • Redis中维护一个大小为16的候选池,当第一次随机选取采用数据时,会把数据放入到候选池中,并且候选池中的数据会根据key的空闲时间进行排序。
  • 当第二次以后选取数据时,只有大于候选池内最小空闲时间的key才会被放进候选池。
  • 当候选池的数据满了之后,那么空闲时间最大的key就会被挤出候选池。当执行淘汰时,直接从候选池中选取空闲时间最大的key进行淘汰。

如下图所示,首先从目标字典中采集出maxmemory-samples个键,缓存在一个samples数组中,然后从samples数组中一个个取出来,和回收池中的键进行键的空闲时间比较,从而更新回收池。在更新过程中,首先利用遍历找到的每个键的实际插入位置x,然后根据不同情况进行处理。

  • 回收池满了,并且当前插入的key的空闲时间最小(也就是回收池中的所有key都比当前插入的key的空闲时间都要大),则不作任何操作。
  • 回收池未满,并且插入的位置x没有键,则直接插入即可
  • 回收池未满,且插入的位置x原本已经存在要淘汰的键,则把第x个以后的元素都往后挪一个位置,然后再执行插入操作。
  • 回收池满了,将当前第x个以前的元素往前挪一个位置(实际就是淘汰了),然后执行插入操作。

这样做的目的是能够选出最真实的最少被访问的key,能够正确选择不常使用的key。因为在Redis3.0之前是随机选取样本,这样的方式很有可能不是真正意义上的最少访问的key。LRU算法有一个弊端,假如一个key值访问频率很低,但是最近一次被访问到了,那LRU会认为它是热点数据,不会被淘汰。同样,经常被访问的数据,最近一段时间没有被访问,这样会导致这些数据被淘汰掉,导致误判而淘汰掉热点数据,于是在Redis 4.0中,新加了一种LFU算法。

五、LFU

LFU(Least Frequently Used),表示最近最少使用,它和key的使用次数有关,其思想是:根据key最近被访问的频率进行淘汰,比较少访问的key优先淘汰,反之则保留。LFU的原理是使用计数器来对key进行排序,每次key被访问时,计数器会增大,当计数器越大,意味着当前key的访问越频繁,也就是意味着它是热点数据。 它很好的解决了LRU算法的缺陷:一个很久没有被访问的key,偶尔被访问一次,导致被误认为是热点数据的问题。LFU的实现原理如下图所示,LFU维护了两个链表,横向组成的链表用来存储访问频率,每个访问频率的节点下存储另外一个具有相同访问频率的缓存数据。具体的工作原理是:

  • 当添加元素时,找到相同访问频次的节点,然后添加到该节点的数据链表的头部。如果该数据链表满了,则移除链表尾部的节点
  • 当获取元素或者修改元素时,都会增加对应key的访问频次,并把当前节点移动到下一个频次节点

添加元素时,访问频率默认为1,随着访问次数的增加,频率不断递增。而当前被访问的元素也会随着频率增加进行移动。

到此这篇关于深入理解Redis内存淘汰策略的文章就介绍到这了,更多相关Redis内存淘汰内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Redis 的内存淘汰策略和过期删除策略的区别

    目录 前言 过期删除策略 如何设置过期时间? 如何判定 key 已过期了? 过期删除策略有哪些? Redis 过期删除策略是什么? 内存淘汰策略 如何设置 Redis 最大运行内存? Redis 内存淘汰策略有哪些? LRU 算法和 LFU 算法有什么区别? 总结 前言 Redis 是可以对 key 设置过期时间的,因此需要有相应的机制将已过期的键值对删除,而做这个工作的就是过期键值删除策略. Redis 的「内存淘汰策略」和「过期删除策略」,很多小伙伴容易混淆,这两个机制虽然都是做删除的操作,

  • 浅谈Redis中的内存淘汰策略和过期键删除策略

    目录 8种淘汰策略 过期键的删除策略 总结 redis是我们现在最常用的一个工具,帮助我们建设系统的高可用,高性能. 而且我们都知道redis是一个完全基于内存的工具,这也是redis速度快的一个原因,当我们往redis中不断缓存数据的时候,其内存总有满的时候(而且内存是很贵的东西,尽量省着点用),所以尽可能把有用的数据,或者使用频繁的数据缓存在redis中,物尽其用. 那么如果正在使用的redis内存用完了,我们应该怎么取舍redis中已存在的数据和即将要存入的数据呢,我们要怎么处理呢? re

  • 浅谈Redis 中的过期删除策略和内存淘汰机制

    目录 前言 Redis 中 key 的过期删除策略 1.定时删除 2.惰性删除 3.定期删除 Redis 中过期删除策略 从库是否会脏读主库创建的过期键 内存淘汰机制 内存淘汰触发的最大内存 有哪些内存淘汰策略 内存淘汰算法 LRU LFU 为什么数据删除后内存占用还是很高 内存碎片如何产生 碎片率的意义 如何清理内存碎片 总结 参考 前言 Redis 中的 key 设置一个过期时间,在过期时间到的时候,Redis 是如何清除这个 key 的呢? 这来分析下 Redis 中的过期删除策略和内存淘

  • 深入理解Redis内存淘汰策略

    目录 一.内存回收 二.设置内存 三.内存淘汰策略 四.LRU 4.1 LRU算法 4.2 redis中的LRU算法 五.LFU 一.内存回收 长时间不使用的缓存 降低IO性能 物理内存不够 很多人了解了Redis的好处之后,于是把任何数据都往Redis中放,如果使用不合理很容易导致数据超过Redis的内存,这种情况会出现什么问题呢? Redis中有很多无效的缓存,这些缓存数据会降低数据IO的性能,因为不同的数据类型时间复杂度算法不同,数据越多可能会造成性能下降 随着系统的运行,redis的数据

  • Redis过期删除策略与内存淘汰策略

    目录 过期删除策略 设置Redis中key的过期时间 (单位:秒) 常见的三种过期删除策略 Redis使用用的过期删除策略 Redis的定期删除的流程 内存淘汰策略 设置Redis最大运行内存 Redis 内存淘汰策略有哪些? LRU 算法和 LFU 算法有什么区别? Redis 是如何实现 LRU 算法的? 什么是 LFU 算法? Redis 是如何实现 LFU 算法的? 过期删除策略 过期删除策略: redis可以对key设置过期时间,因此要有相应的机制将已过期的键值对删除. 设置Redis

  • 关于redis Key淘汰策略的实现方法

    1 配置文件中的最大内存删除策略 在redis的配置文件中,可以设置redis内存使用的最大值,当redis使用内存达到最大值时(如何知道已达到最大值?),redis会根据配置文件中的策略选取要删除的key,并删除这些key-value的值.若根据配置的策略,没有符合策略的key,也就是说内存已经容不下新的key-value了,但此时有不能删除key,那么这时候写的话,将会出现写错误. 1.1 最大内存参数设置 若maxmemory参数设置为0,则分两种情况: *在64位系统上,表示没有限制.

  • Redis内存回收策略

    目录 概述 maxmemory-policy 参数 主动清理策略 策略选择 maxmemory-sample 概述 Redis也会因为内存不足而产生错误 , 也可能因为回收过久而导致系统长期的停顿,因此掌握执行回收策略十分有必要.在 Redis 的配置文件中,当 Redis 的内存达到规定的最大值时,允许配置 6 种策略中的一种进行淘汰键值,并且将一些键值对进行回收. maxmemory-policy 参数 # Set a memory usage limit to the specified

  • Redis 缓存淘汰策略和事务实现乐观锁详情

    目录 缓存淘汰策略 标题LRU原理 标题Redis缓存淘汰策略 设置最大缓存 淘汰策略 Redis事务 Redis事务介绍 MULTI EXEC DISCARD WATCH Redis 不支持事务回滚(为什么呢) Redis乐观锁 Redis乐观锁实现秒杀 缓存淘汰策略 标题LRU原理 LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”. 最常见的实现是使用一个链表保存缓存数据,

  • Redis深入了解内存淘汰与事务操作

    目录 Redis内存淘汰策略 六种淘汰策略 Redis中的自动过期机制 Redis中的事务操作 watch和Multi的区别 Redis内存淘汰策略 为什么要有淘汰策略? 答:将Redis用作缓存时,Redis数据存在内存中,如果内存空间用满,就会自动驱逐老的数据. redis配置文件:可以配置redis存放数据的阈值(例如:100mb),再配置淘汰策略. 六种淘汰策略 noeviction:当内存使用达到阈值的时候,所有引起申请内存的命令会报错. allkeys-lru:在主键空间中,优先移除

  • 浅谈Redis 中的过期删除策略和内存淘汰机制

    目录 前言 Redis 中 key 的过期删除策略 1.定时删除 2.惰性删除 3.定期删除 Redis 中过期删除策略 从库是否会脏读主库创建的过期键 内存淘汰机制 内存淘汰触发的最大内存 有哪些内存淘汰策略 内存淘汰算法 LRU LFU 为什么数据删除后内存占用还是很高 内存碎片如何产生 碎片率的意义 如何清理内存碎片 总结 参考 前言 Redis 中的 key 设置一个过期时间,在过期时间到的时候,Redis 是如何清除这个 key 的呢? 这来分析下 Redis 中的过期删除策略和内存淘

随机推荐