【Redis】Redis 的过期策略以及内存淘汰机制详解

news2024/11/23 6:07:10

Redis 的过期策略以及内存淘汰机制详解

  • 1. Redis 的过期策略
    • 1.1 如何设置 key 的过期时间?
    • 1.2 key 设置且到了过期时间后,该 key 保存的数据还占据内存么?
    • 1.3 Redis 如何删除过期的数据
      • 1.3.1 定期删除
      • 1.3.2 惰性删除
  • 2. Redis 的内存淘汰机制
    • 2.1 Redis 最大内存淘汰机制有哪些?
    • 2.2 如何设置 Redis 最大内存?
    • 2.3 如何设置 Redis 内存淘汰机制?
  • 3. LRU 和 LFU 算法
    • 3.1 概念
      • 3.1.1 LRU(Least Recently Used)算法
      • 3.1.2 LFU(Least Frequently Used)算法
    • 3.2 区别
    • 3.3 实现
      • 3.3.1 LRU
      • 3.3.2 LFU
  • 4. 其他场景对过期 key 的处理
    • 4.1 快照生成 RDB 文件时
    • 4.2 服务重启载入 RDB 文件时
    • 4.3 AOF 文件写入时
    • 4.4 重写 AOF 文件时
    • 4.5 主从同步时

1. Redis 的过期策略

Redis 在存储数据时,如果指定了过期时间,缓存数据到了过期时间就会失效,那么 Redis 是如何处理这些失效的缓存数据呢?这就用到了 Redis 的过期策略 - 定期删除 + 惰性删除。下面我们带着问题一起来学习 Redis 的过期策略。

1.1 如何设置 key 的过期时间?

  • Redis 在指定 expire 时间后自动移除给定的键
设置 key 秒级精度的过期时间
EXPIRE key seconds
# 设置 key 毫秒级精度的过期时间
PEXPIRE key milliseconds
  • Redis 在指定 unix 时间来临之后自动移除给定的键
设置 key 秒级精度的过期时间戳(unix timestamp)
EXPIREAT key seconds_timestamp
# 设置 key 毫秒级精度的过期时间戳(unix timestamp) 以毫秒计
PEXPIREAT key milliseconds_timestamp

1.2 key 设置且到了过期时间后,该 key 保存的数据还占据内存么?

当 key 过期后,该 key 保存的数据还是会占据内存的。

因为每当我们设置一个 key 的过期时间时,Redis 会将该键带上过期时间存放到一个过期字典中。当 key 过期后,如果没有触发 Redis 的删除策略的话,过期后的数据依然会保存在内存中的,这时候即便这个 key 已经过期,我们还是能够获取到这个 key 的数据。

1.3 Redis 如何删除过期的数据

Redis 使用:“定期删除 + 惰性删除” 策略删除过期数据。

1.3.1 定期删除

Redis 默认每隔 100ms 就随机抽取部分设置了过期时间的 key,检测这些 key 是否过期,如果过期了就将其删除。

  1. 100ms 是怎么来的?

定期任务是 Redis 服务正常运行的保障,它的执行频率由 hz 参数的值指定,默认为10,即每秒执行10次。

hz 10

5.0 之前的 Redis 版本,hz 参数一旦设定之后就是固定的了。hz 默认是 10。这也是官方建议的配置。如果改大,表示在 Redis 空闲时会用更多的 CPU 去执行这些任务。官方并不建议这样做。但是,如果连接数特别多,在这种情况下应该给与更多的 CPU 时间执行后台任务。

Redis 5.0之后,有了 dynamic-hz 参数,默认就是打开。当连接数很多时,自动加倍 hz,以便处理更多的连接。

dynamic-hz yes
  1. 为什么是随机抽取部分 key,而不是全部 key?

因为如果 Redis 里面有大量 key 都设置了过期时间,全部都去检测一遍的话 CPU 负载就会很高,会浪费大量的时间在检测上面,甚至直接导致 Redis 挂掉。所有只会抽取一部分而不会全部检查。

随机抽取部分检测,部分是多少?是由 redis.conf 文件中的 maxmemory-samples 属性决定的,默认为 5。

# The default of 5 produces good enough results. 10 Approximates very closely
# true LRU but costs more CPU. 3 is faster but not very accurate.
#
# maxmemory-samples 5

正因为定期删除只是随机抽取部分 key 来检测,这样的话就会出现大量已经过期的 key 并没有被删除,这就是为什么有时候大量的 key 明明已经过了失效时间,但是 Redis 的内存还是被大量占用的原因 ,为了解决这个问题,Redis 又引入了"惰性删除策略"。

1.3.2 惰性删除

惰性删除不是去主动删除,而是在你要获取某个 key 的时候,Redis 会先去检测一下这个 key 是否已经过期,如果没有过期则返回给你,如果已经过期了,那么 Redis 会删除这个 key,不会返回给你。

定期删除 + 惰性删除” 就能保证过期的 key 最终一定会被删掉 ,但是只能保证最终一定会被删除,要是定期删除遗漏的大量过期 key,我们在很长的一段时间内也没有再访问这些 key,那么这些过期 key 不就一直会存在于内存中吗?不就会一直占着我们的内存吗?这样不还是会导致 Redis 内存耗尽吗?由于存在这样的问题,所以 Redis 又引入了"内存淘汰机制"来解决。

2. Redis 的内存淘汰机制

2.1 Redis 最大内存淘汰机制有哪些?

  1. volatile-lru:当内存不足执行写入操作时,在设置了过期时间的键空间中,移除最近最少(最长时间)使用的 key。

  2. allkeys-lru:当内存不足执行写入操作时,在整个键空间中,移除最近最少(最长时间)使用的 key。(这个是最常用的)

  3. volatile-lfu:当内存不足执行写入操作时,在设置了过期时间的键空间中,移除最不经常(最少次)使用的key。

  4. allkeys-lfu:当内存不足执行写入操作时,在整个键空间中,移除最不经常(最少次)使用的key。

  5. volatile-random -> 当内存不足执行写入操作时,在设置了过期时间的键空间中,随机移除某个 key。

  6. allkeys-random -> 当内存不足执行写入操作时,在整个键空间中,随机移除某个 key。

  7. volatile-ttl -> 当内存不足执行写入操作时,在设置了过期时间的键空间中,优先移除过期时间最早(剩余存活时间最短)的 key。

  8. noeviction:不删除任何 key, 只是在内存不足写操作时返回一个错误。(默认选项,一般不会选用)

2.2 如何设置 Redis 最大内存?

redis.conf 配置文件中的 maxmemory 属性限定了 Redis 最大内存使用量,当占用内存大于 maxmemory 的配置值时会执行内存淘汰机制。

# maxmemory <bytes>

当达到设置的内存使用限制时,Redis 将根据选择的内存淘汰机制(maxmemory-policy)删除 key。

2.3 如何设置 Redis 内存淘汰机制?

内存淘汰机制由 redis.conf 配置文件中的 maxmemory-policy 属性设置,没有配置时默认为 noeviction:不删除任何 策略

# maxmemory-policy noeviction

3. LRU 和 LFU 算法

3.1 概念

3.1.1 LRU(Least Recently Used)算法

@百度百科

LRU:最近最少使用淘汰算法(Least Recently Used)。LRU 是淘汰最近最久未使用的数据。

3.1.2 LFU(Least Frequently Used)算法

@百度百科

LFU:最不经常使用淘汰算法(Least Frequently Used)。LFU是淘汰一段时间内,使用次数最少的数据。

3.2 区别

LRU 关键是看最后一次被使用到发生替换的时间长短,时间越长,就会被淘汰;而 LFU 关键是看一定时间段内被使用的频率(次数),使用频率越低,就会被淘汰。

LRU 算法适合:较大的文件比如游戏客户端(最近加载的地图文件);

LFU 算法适合:较小的文件和教零碎的文件比如系统文件、应用程序文件。

LRU 消耗 CPU 资源较少,LFU 消耗 CPU 资源较多。

3.3 实现

3.3.1 LRU

这里用 leetcode 中一道面试题:LRU 缓存 来设计和构建一个 “最近最少使用” 缓存。

在这里插入图片描述

  • 关键实现:

    • LRUCache(int capacity):容量,大于容量选择最久未使用资源淘汰。

    • get(key):如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。

    • put(key, value):如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

public class LRUCache {
    private class Node{
        Node prev;
        Node next;
        int key;
        int value;

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

    private int capacity;
    private HashMap<Integer, Node> hs = new HashMap<Integer, Node>();
    private Node head = new Node(-1, -1);
    private Node tail = new Node(-1, -1);
    // @param capacity, an integer
    public LRUCache(int capacity) {
        // write your code here
        this.capacity = capacity;
        tail.prev = head;
        head.next = tail;
    }

    // @return an integer
    public int get(int key) {
        // write your code here
        if( !hs.containsKey(key)) {
            return -1;
        }

        // remove current
        Node current = hs.get(key);
        current.prev.next = current.next;
        current.next.prev = current.prev;

        // move current to tail
        move_to_tail(current);

        return hs.get(key).value;


    }

    // @param key, an integer
    // @param value, an integer
    // @return nothing
    public void set(int key, int value) {
        // write your code here
        if( get(key) != -1) {
            hs.get(key).value = value;
            return;
        }

        if (hs.size() == capacity) {
            hs.remove(head.next.key);
            head.next = head.next.next;
            head.next.prev = head;
        }

        Node insert = new Node(key, value);
        hs.put(key, insert);
        move_to_tail(insert);
    }

    private void move_to_tail(Node current) {
        current.prev = tail.prev;
        tail.prev = current;
        current.prev.next = current;
        current.next = tail;
    }

    public static void main(String[] as) throws Exception {
        LRUCache cache = new LRUCache(3);
        cache.set(2, 2);
        cache.set(1, 1);

        System.out.println(cache.get(2));
        System.out.println(cache.get(1));
        System.out.println(cache.get(2));

        cache.set(3, 3);
        cache.set(4, 4);

        System.out.println(cache.get(3));
        System.out.println(cache.get(2));
        System.out.println(cache.get(1));
        System.out.println(cache.get(4));

    }
}

3.3.2 LFU

这里用 leetcode 中一道面试题:最不经常使用 LFU 缓存 算法设计并实现数据结构。

在这里插入图片描述

  • 关键实现

    • LFUCache(int capacity) - 用数据结构的容量 capacity 初始化对象。

    • int get(int key) - 如果键 key 存在于缓存中,则获取键的值,否则返回 -1 。

    • void put(int key, int value) - 如果键 key 已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量 capacity 时,则应该在插入新项之前,移除最不经常使用的项。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近最久未使用 的键。

public class LFUCache {
    private static final int DEFAULT_MAX_SIZE = 3;
    private int capacity = DEFAULT_MAX_SIZE;
    //保存缓存的访问频率和时间
    private final Map<Integer, HitRate> cache = new HashMap<Integer, HitRate>();
    //保存缓存的KV
    private final Map<Integer, Integer> KV = new HashMap<Integer, Integer>();

    // @param capacity, an integer
    public LFUCache(int capacity) {
        this.capacity = capacity;
    }

    // @param key, an integer
    // @param value, an integer
    // @return nothing
    public void set(int key, int value) {
        Integer v = KV.get(key);
        if (v == null) {
            if (cache.size() == capacity) {
                Integer k = getKickedKey();
                KV.remove(k);
                cache.remove(k);
            }
            cache.put(key, new HitRate(key, 1, System.nanoTime()));
        } else { //若是key相同只增加频率,更新时间,并不进行置换
            HitRate hitRate = cache.get(key);
            hitRate.hitCount += 1;
            hitRate.lastTime = System.nanoTime();
        }
        KV.put(key, value);
    }

    public int get(int key) {
        Integer v = KV.get(key);
        if (v != null) {
            HitRate hitRate = cache.get(key);
            hitRate.hitCount += 1;
            hitRate.lastTime = System.nanoTime();
            return v;
        }
        return -1;
    }
    // @return 要被置换的key
    private Integer getKickedKey() {
        HitRate min = Collections.min(cache.values());
        return min.key;
    }

    class HitRate implements Comparable<HitRate> {
        Integer key;
        Integer hitCount; // 命中次数
        Long lastTime; // 上次命中时间

        public HitRate(Integer key, Integer hitCount, Long lastTime) {
            this.key = key;
            this.hitCount = hitCount;
            this.lastTime = lastTime;
        }

        public int compareTo(HitRate o) {
            int hr = hitCount.compareTo(o.hitCount);
            return hr != 0 ? hr : lastTime.compareTo(o.lastTime);
        }
    }

    public static void main(String[] as) throws Exception {
        LFUCache cache = new LFUCache(3);
        cache.set(2, 2);
        cache.set(1, 1);

        System.out.println(cache.get(2));
        System.out.println(cache.get(1));
        System.out.println(cache.get(2));

        cache.set(3, 3);
        cache.set(4, 4);

        System.out.println(cache.get(3));
        System.out.println(cache.get(2));
        System.out.println(cache.get(1));
        System.out.println(cache.get(4));

    }
}

4. 其他场景对过期 key 的处理

4.1 快照生成 RDB 文件时

过期的 key 不会被保存在 RDB 文件中。

4.2 服务重启载入 RDB 文件时

Master 载入 RDB 时,文件中的未过期的键会被正常载入,过期键则会被忽略。Slave 载入 RDB 时,文件中的所有键都会被载入,当主从同步时,再和 Master 保持一致。

4.3 AOF 文件写入时

因为 AOF 保存的是执行过的 Redis 命令,所以如果 Redis 还没有执行 del,AOF 文件中也不会保存 del 操作,当过期 key 被删除时,DEL 命令也会被同步到 AOF 文件中去。

4.4 重写 AOF 文件时

执行 BGREWRITEAOF 时 ,过期的 key 不会被记录到 AOF 文件中。

4.5 主从同步时

Master 删除 过期 Key 之后,会向所有 Slave 服务器发送一个 DEL 命令,Slave 收到通知之后,会删除这些 Key。

Slave 在读取过期键时,不会做判断删除操作,而是继续返回该键对应的值,只有当 Master 发送 DEL 通知,Slave 才会删除过期键,这是统一、中心化的键删除策略,保证主从服务器的数据一致性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/366261.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能新冠疫苗接种助手管理系统

项目背景介绍 近几年来,网络事业&#xff0c;特别是Internet发展速度之快是任何人都始料不及的。目前&#xff0c;由于Internet表现出来的便捷&#xff0c;快速等诸多优势&#xff0c;已经使它成为社会各行各业&#xff0c;甚至是平民大众工作&#xff0c;生活不可缺少的一个重…

【UE4 制作自己的载具】2-导入UE

在上一篇博客中&#xff08;【UE4 制作自己的载具】1-使用3dsmax制作载具&#xff09;已经完成了载具模型的制作&#xff0c;本篇需要完成的是将模型导入UE中并进行一些调整。本篇制作步骤&#xff1a;新建一个UE工程&#xff0c;可以添加一个载具模板创建一个“Vehicle”文件夹…

windows服务器上传文件解决方案

1.说明 1.如果上传到linux系统&#xff0c;通常使用ftp相关技术&#xff0c;配合windows端的ftp客户端工具比如FileZilla等进行大文件的上传工作。 2.同理windows服务器也可以开启ftp服务用来传输大文件。 3.本文介绍偷懒方式&#xff08;常规是开启windows的ftp服务&#xff0…

案例|山东省中医院基于ZABBIX构建网络设备监控预警平台

作者介绍&#xff1a;董晨&#xff0c;山东省中医院信息科机房运维管理 本文从背景、演进、成效来分享建设过程&#xff0c;最终得出结论&#xff0c;类似Zabbix的国产成熟产品市场价值动辄上百万&#xff0c;而我们以极小成本&#xff0c;为医院节省了大量资金&#xff0c;取…

Java多线程(一)--多线程基础知识

1. 为什么要使用并发编程提升多核CPU的利用率&#xff1a;一般来说一台主机上的会有多个CPU核心&#xff0c;我们可以创建多个线程&#xff0c;理论上讲操作系统可以将多个线程分配给不同的CPU去执行&#xff0c;每个CPU执行一个线程&#xff0c;这样就提高了CPU的使用效率&…

通过NPM生态系统中的依赖树揭开脆弱性传播及其演化的神秘面纱

通过NPM生态系统中的依赖树揭开脆弱性传播及其演化的神秘面纱 本文实现了一个依赖约束解析器来解决NPM依赖约束的多样性&#xff0c;并在此基础上构建了一个完整的依赖漏洞知识图&#xff08;DVGraph&#xff09;&#xff0c;以捕获所有NPM包之间的依赖关系。 https://www.se…

关于thumb2指令下函数运行地址对齐问题及验证固件分析

近期&#xff0c;在分析Thumb2指令的一个固件文件时&#xff0c;发现Thumb2指令集下执行函数的运行地址不对齐&#xff1f; 于是&#xff0c;为了分析一下原因&#xff0c;找了手头上现有的一款基于Cortex3内核的一块板子来实际执行看一下&#xff0c;结合编译后的Hex文件&…

【性能测试】loadrunner(一)知识准备

【性能测试】loadrunner&#xff08;一&#xff09;知识准备 目录&#xff1a;导读 1.0. 前言 1.1 性能测试术语介绍 1.2 性能测试分类 1.3 HTTP我们需要知道的 1.4 Loadrunner 12.55安装 1.0. 前言 ​ 在性能测试中&#xff0c;牵扯到了许多比较杂的知识点&#xff0c;…

Java文件IO操作:File类的相关内容

Java文件IO操作一、File类1.相对路径和绝对路径2.路径分隔符&#xff08;同一路径下、多个路径下&#xff09;3.实例化4.常见方法一、File类 File类继承自Object类&#xff0c;实现了Serializable接口和Comparable接口&#xff1b; File类属于java.io包&#xff1b; File类是文…

高通平台开发系列讲解(WIFI篇)802.11 基本概念

文章目录 一、WLAN概述二、802.11发展历程三、802.11基本概念沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本文将基于高通平台介绍802.11基本概念。 一、WLAN概述 WLAN是Wireless Local Area Network的简称,指应用无线通信技术将计算机设备互联起来,构成可以互相通…

Python计算 -- 内附蓝桥题:相乘,三角回文数

计算 ~~不定时更新&#x1f383;&#xff0c;上次更新&#xff1a;2023/02/23 &#x1f5e1;常用函数&#xff08;方法&#xff09; 1. 求一个整数的最末位 举个栗子&#x1f330; n int(input()) end n % 10蓝桥例题1 - 相乘✖️ 题目描述 本题为填空题&#xff0c;…

【python学习笔记】:输出与输入

01 输出方式 表达式语句、print()函数和使用文件对象的write()方法。 02 输出形式 格式化输出str.format()函数、转成字符串可以使用repr()或str()函数来实现。 (1)repr()&#xff1a;产生一个解释器易读的表达形式&#xff0c;便于字符串的拼接。 例&#xff1a;输出平方与…

OpenGL超级宝典学习笔记:着色器存储区块、原子内存操作、内存屏障

前言 本篇在讲什么 本篇为蓝宝书学习笔记 着色器存储区块 原子内存操作 内存屏障 本篇适合什么 适合初学Open的小白 本篇需要什么 对C语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 具有全流程的图文教学 重…

五月天演唱会门票开售,retry了一小时也没re进去!到底是什么高科技在作祟?!

等待了三年&#xff0c;大型线下演唱会终于回归了&#xff01;前段时间&#xff0c;文化和旅游部市场管理司发布通知称&#xff0c;自2023年2月16日起&#xff0c;各地文化和旅游行政部门恢复对涉港澳台营业性演出的受理和审批。据中国演出行业协会在业内调研了解&#xff0c;周…

Python Pytorch开发环境搭建(Windows和Ubuntu)

Python Pytorch开发环境搭建&#xff08;Windows和Ubuntu&#xff09; 目录 Pytorch开发环境搭建 1. 安装cuda cudnn (1)Windows安装方法 (2)Ubuntu18.04安装方法 2. 安装Python(推荐使用Anaconda) (1)Windows安装方法 (2)Ubuntu18.04安装方法 3. Pytorch安装 4. 安装…

渗透测试之端口探测实验

渗透测试之端口探测实验实验目的一、实验原理1.1 端口1.2 服务二、实验环境2.1 操作机器2.2 实验工具三、实验步骤1. 使用netstat手动探测指定服务2. 使用namp工具进行端口扫描2. 使用ssh命令总结实验目的 了解端口、服务的基本概念熟悉手工探测方式netstat命令的使用掌握扫描…

分享app的测试技巧

前言 今天笔者想和大家来唠唠app测试&#xff0c;现在的app有非常的多&#xff0c;这些app都是需要经过测试之后才能发布到应用市场中&#xff0c;app已经成为了我们日常生活中不可或缺的一部分了&#xff0c;但它的功能必须强大&#xff0c;才能受到消费者的重视&#xff0c;…

Python开发——做一个简单的【表白墙】网站

前言 大家早好、午好、晚好吖 ❤ ~ 今天我们要用Python做Web开发&#xff0c;做一个简单的【表白墙】网站。 众所周知表白墙的功能普遍更多的是发布找人&#xff0c;失物招领&#xff0c; 还是一个大家可以跟自己喜欢的人公开表白的平台 Tornado框架简单介绍 在Python当中&…

Go全栈学习(一)基础语法

Go语言基础语法 文章目录Go语言基础语法注释变量变量的定义变量的交换理解变量&#xff08;内存地址&#xff09;匿名变量变量的作用域常量2023.2.4日 总结// 关于Goland 中 执行的问题// 1、包下执行 &#xff08;一个 main 函数来执行&#xff0c;如果有多个&#xff0c;无法…

【Elasticsearch】安装配置与使用

一、下载与安装Elasticsearch下载地址&#xff1a;https://www.elastic.co/cn/downloads/elasticsearch#ga-release前端管理界面&#xff1a;https://github.com/mobz/elasticsearch-head这两个文件都是解压即可。二、配置与启动1.elasticsearch6以上的版本已经内置jdk&#xf…