Redis系列--数据过期清除策略缓存淘汰策略

news2024/12/24 2:41:17

一、过期策略

一、前言

Redis 所有的数据结构都可以设置过期时间,时间一到,就会自动删除。可以想象里面有一个专门删除过期数据的线程,数据已过期就立马删除。这个时候可以思考一下,会不会因为同一时间太多的 key 过期,以至于线程忙不过来。同时因为 Redis 是单线程的,删除的时间也会占用线程的处理时间,如果删除的太过于繁忙,会不会导致线上读写指令出现卡顿。

二、立即删除

它会在设置键的过期时间的同时,创建一个定时器, 当键到了过期时间,定时器会立即对键进行删除。 这个策略能够保证过期键的尽快删除,快速释放内存空间。

1、优点:

        立即删除能保证内存中数据的最大新鲜度,因为它保证过期键值会在过期后马上被删除,其所占用的内存也会随之释放。对内存来说是非常友好的。

2、缺点:

        立即删除对cpu是最不友好的。因为删除操作会占用cpu的时间,如果刚好碰上了cpu很忙的时候,比如正在做交集或排序等计算的时候,就会给cpu造成额外的压力。

3、总结:立即删除对cpu不友好,但是对内存友好,实际性质就是用处理器性能换区内存空间。

三、惰性删除

数据到达过期时间,不做处理。等下次访问该数据时,如果未过期,返回数据 ;发现已过期,删除,返回不存在。

开启惰性删除:lazyfree-lazy-eviction=yes

1、优点 :

        对于cpu来说是非常友好的,减少了cpu资源的占有。

2、缺点:

        如果一个键已经过期,而这个键又仍然保留在redis中,那么只要这个过期键不被删除,它所占用的内存就不会释放。因此对于内存是很不友好的。

在使用惰性删除策略时,如果数据库中有非常多的过期键,而这些过期键又恰好没有被访问到的话,那么它们也许永远也不会被删除(除非用户手动执行FLUSHDB),我们甚至可以将这种情况看作是一种内存泄漏–无用的垃圾数据占用了大量的内存,而服务器却不会自己去释放它们,这对于运行状态非常依赖于内存的Redis服务器来说,肯定不是一个好消息

四、定期删除 

定期删除策略是前两种策略的折中:

定期删除策略每隔一段时间执行一次删除过期键操作并通过限制删除操作执行时长和频率来减少删除操作对CPU时间的影响。

 1、过期key的集合

redis 会将每个设置了过期时间的 key 放入到一个独立的字典中,以后会定时遍历这个 字典来删除到期的 key。除了定时遍历之外,它还会使用惰性策略来删除过期的 key。定期删除是集中处理,惰性删除是零散处理。

2、定时扫描策略

Redis 默认会每秒进行十次过期扫描,过期扫描不会遍历过期字典中所有的 key,而是 采用了一种简单的贪心策略。

1、从过期字典中随机 20 个 key;

2、删除这 20 个 key 中已经过期的 key;

3、如果过期的 key 比率超过 1/4,那就重复步骤 1;

于此同时为了保证过期扫描不会出现循环过度,导致线程卡死现象,算法还增加了扫描时间的上限,默认不会超过 25ms。

而这就会出现一种需要特别注意的情况:

        设想一个大型的 Redis 实例中所有的 key 在同一时间过期了。Redis 会持续扫描过期字典 (循环多次),直到过期字典中过期的 key 变得稀 疏,才会停止 (循环次数明显下降)。这就会导致线上读写请求出现明显的卡顿现象。导致这 种卡顿的另外一种原因是内存管理器需要频繁回收内存页,这也会产生一定的 CPU 消耗。

        即使算法还增加了扫描时间的上限,也是会出现卡顿现象。假如有 101 个客户端同时将请求发过来了,然后前 100 个请求的执行时间都是 25ms,那么第 101 个指令需要等待多久才能执行?2500ms,这个就是客户端的卡顿时间,是由服务器不间断的小卡顿积少成多导致的(假如每次都达到了扫描上线25ms)。

        所以开发的时候就需要特别注意,避免大量key在同一时间过期。可以给key在固定的过期时间上+随机范围的时间

3、定期删除注意事项:

  • 如果删除操作执行次数过多、执行时间太长,就会导致和定时删除同样的问题:占用大量cpu资源去进行删除操作
  • 如果删除操作次数太少、执行时间短,就会导致和惰性删除同样的问题:内存资源被持续占用,得不到释放。

所以定时删除最关键的就在于执行时长和频率的设置,可在redis的配置文件中配置

 对于hz参数,官方不建议超过100,否则会把cpu造成比较大的压力

二、缓存淘汰策略

1、当 Redis 内存超出物理内存限制时,内存的数据会开始和磁盘产生频繁的交换,交换会让 Redis 的性能急剧下降,对于访问量比较频繁的 Redis 来说,这样龟速的存取效率 基本上等于不可用。

2、一般生产上的redis内存都会设置一个内存上限(maxmemory),如果有许多没有加过期时间的数据,长期下来就会把redis内存打满,出现OOM异常。

3、定期删除是使用简单的贪心算法,会出现一些没有被抽查到的数据,而惰性删除也会出现一些长时间没有访问得数据,这就会导致大量过期的key堆积在内存中,导致redis内存空间紧张或者很快耗尽。所以必须要有一个兜底方案。这个方案就是缓存淘汰策略。

一、八种缓存淘汰策略

1、noeviction:不会继续服务写请求 (DEL 请求可以继续服务),读请求可以继续进行。这样 可以保证不会丢失数据,但是会让线上的业务不能持续进行。这是默认的淘汰策略。 

2、volatile-lru:尝试淘汰设置了过期时间的 key,最少使用的 key 优先被淘汰。没有设置过 期时间的 key: 不会被淘汰,这样可以保证需要持久化的数据不会突然丢失。

3、allkeys-lru: 区别于 volatile-lru,这个策略要淘汰的 key 对象是全体的 key 集合,而不 只是过期的 key 集合。这意味着没有设置过期时间的 key 也会被淘汰。

4、volatile-ttl: 跟上面一样,除了淘汰的策略不是 LRU,而是 key 的剩余寿命 ttl 的值,ttl 越小越优先被淘汰。

5、volatile-random:对所有设置了过期时间的key随机淘汰。

6、allkeys-random:对所有key随机淘汰

7、volatile-lfu:对设置了过期时间的key使用lfu算法进行删除

8、allkeys-lfu:对所有key使用lfu算法进行删除

总结:volatile-xxx: 策略只会针对带过期时间的 key 进行淘汰,allkeys-xxx 策略会对所有的 key 进行淘汰。如果你只是拿 Redis 做缓存,那应该使用 allkeys-xxx,客户端写缓存时 不必携带过期时间。如果你还想同时使用 Redis 的持久化功能,那就使用 volatile-xxx 策略,这样可以保留没有设置过期时间的 key,它们是永久的 key 不会被 LRU 算法淘 汰。

二、LRU和LFU

1、区别:

LRU:最近最少使用页面置换算法,淘汰最长时间未被使用的页面,看页面最后一次被使用到发生调度的时间长短,首先淘汰最长时间未被使用的页面。

LFU:最近最不常用页面置换算法,淘汰一定时期内被访问次数最少的页,看一定时间段内页面被使用的频率,淘汰一定时期内被访问次数最少的页

举个栗子

某次时期Time为10分钟,如果每分钟进行一次调页,主存块为3,若所需页面走向为2 1 2 1 2 3 4

假设到页面4时会发生缺页中断

若按LRU算法,应换页面1(1页面最久未被使用),但按LFU算法应换页面3(十分钟内,页面3只使用了一次)

可见LRU关键是看页面最后一次被使用到发生调度的时间长短,而LFU关键是看一定时间段内页面被使用的频率!

2、手写LRU算法 

底层shuju

1、使用LinkedHashMap实现

/**
 * @Description :
 * @Author : huangcong
 * @Date : 2023/6/28 9:48
 **/
public class LinkedHashMapLru<K, V> extends LinkedHashMap<K, V> {

    private Integer initialCapacity;

    public LinkedHashMapLru(Integer initialCapacity) {
        super(initialCapacity, 0.75F, Boolean.FALSE);
        this.initialCapacity = initialCapacity;
    }

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

    public Object getValue(Object key) {
        Object v = super.get(key);
        if (Objects.isNull(v)) {
            return -1;
        }
        return v;
    }

    public static void main(String[] args) {
        LinkedHashMapLru<Object, Object> hashMapLru = new LinkedHashMapLru<>(3);
        hashMapLru.put(1, "a");
        hashMapLru.put(2, "b");
        hashMapLru.put(3, "c");
        System.out.println(hashMapLru.entrySet());

        // key存在变更其数据
        hashMapLru.put(3, "l");
        System.out.println(hashMapLru.entrySet());

        // 当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值
        hashMapLru.put(4, "d");
        System.out.println(hashMapLru.entrySet());

        hashMapLru.put(5, "f");
        System.out.println(hashMapLru.entrySet());

        // 获取数据 get(key) - 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1
        Object value =hashMapLru.getValue(1);
        System.out.println(value);
    }
}

2、其他方式实现

/**
 * @Description : 构造一个node节点,承载数据
 * @Author : hc
 * @Date : 2023/6/28 12:14
 **/
public class Node<K, V> {

    K key;

    V value;

    Node<K, V> pre;

    Node<K, V> next;

    public Node() {
    }

    public Node(K key, V value) {
        this.key = key;
        this.value = value;
        this.pre = this.next = null;
    }

    public void setKey(K key) {
        this.key = key;
    }

    public void setValue(V value) {
        this.value = value;
    }
}
/**
 * @Description :双向链表
 * @Author : hc
 * @Date : 2023/6/28 12:23
 **/
public class LruCache <K,V>{

    Node<K,V> head;
    Node<K,V> tail;

    public LruCache() {
        head = new Node<>();
        tail = new Node<>();
        head.next = tail;
        tail.pre = head;
    }

    // 头插法,靠近头部的是最新的数据
    public void add(Node<K,V> node){
        node.next = head.next;
        node.pre = head;
        head.next.pre = node;
        head.next = node;
    }

    public void delete(Node<K,V> node){
        node.next.pre =node.pre;
        node.pre.next = node.next;
        node.next = null;
        node.pre = null;
    }

    public Node getNode(){
        return tail.pre;
    }

    // 打印链表
    public String getCache(){
        StringBuffer stringBuffer = new StringBuffer();
        Node<K, V> node = head.next;
        while (node != tail){
            stringBuffer.append(node.key+"="+node.value + "\r\n");
            node = node.next;
        }
        return stringBuffer.toString();
    }
}
/**
 * @Description :
 * hash表:通过hash函数计算出hash值,然后(hash值 % 数组大小)得到对应数组中的位置
 * @Author : hc
 * @Date : 2023/6/28 16:23
 **/
public class Lru {

    private Integer cacheSize; // 规定容器大小
    private Map<Integer,Node<Integer,Integer>> map; // hash表,方便查找
    private LruCache<Integer,Integer> doubleLinkedMap;// 双向链表,方便插入以及删除

    public Lru(Integer cacheSize) {
    this.cacheSize = cacheSize;
    map = new HashMap<>();
    doubleLinkedMap = new LruCache<>();
    }

    public int get(Integer key){
        if (!map.containsKey(key)){
            return -1;
        }
        Node<Integer, Integer> node = map.get(key);
        doubleLinkedMap.delete(node);
        doubleLinkedMap.add(node);
        return node.value;
    }

    public Integer put(Integer key,Integer value){
        // 如果哈希表中有,替换节点的value值
        if (map.containsKey(key)){
            Node<Integer, Integer> node = map.get(key);
            node.setValue(value);
            doubleLinkedMap.delete(node);
            doubleLinkedMap.add(node);
            return key;
        }
        Node<Integer, Integer> node = new Node<>(key, value);
        // 如果hash没有,且容器中数据已经达到了规定大小,删除最后一个数据,在头部添加一个最新数据
        if (map.size() >= cacheSize){
            Node lastNode = doubleLinkedMap.getNode();
            doubleLinkedMap.delete(lastNode);
            doubleLinkedMap.add(node);
            map.remove(key);
            map.put(key,node);
            return key;
        }
        // 如果hash没有,且容器中数据未达到了规定大小,直接在头部添加一个最新数据
        doubleLinkedMap.add(node);
        map.put(key,node);
        return key;
    }

    // 删除节点
    public Integer delete(Integer key){
        if (!map.containsKey(key)){
            return -1;
        }
        Node<Integer, Integer> node = map.get(key);
        doubleLinkedMap.delete(node);
        map.remove(key);
        return key;
    }

    public static void main(String[] args) {
        Lru lru = new Lru(3);
        lru.put(1,1);
        lru.put(2,2);
        lru.put(3,3);
        System.out.println(lru.doubleLinkedMap.getCache());

        lru.put(2,4);
        System.out.println(lru.doubleLinkedMap.getCache());

        lru.put(4,4);
        System.out.println(lru.doubleLinkedMap.getCache());

        int i = lru.get(3);
        System.out.println(i);
        System.out.println(lru.doubleLinkedMap.getCache());
    }
}

 

运行结果:

3=3
2=2
1=1

2=4
3=3
1=1

4=4
2=4
3=3

3
3=3
4=4
2=4

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

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

相关文章

windows电脑设置每天自动关机

有时候我们需要我们的笔记本或者电脑在每天固定的时间自动关机&#xff0c;但是windows本身是没有带这个设置的&#xff0c;下面记录下如何设置电脑每天自动关机&#xff0c;无需安装任何第三方软件&#xff1b; 文章目录 一、设置自动关机程序二、取消自动关机三、Windows任务…

chatgpt赋能python:Python连接表

Python连接表 Python作为一种高级编程语言&#xff0c;可以用于各种各样的任务。其中之一就是连接表格数据。连接表是在数据管理中非常重要的概念&#xff0c;因为它可以将不同表格中的数据合并在一起&#xff0c;从而使我们能够更好地分析和理解数据。在这篇文章中&#xff0…

蓝桥杯专题-试题版含答案-【猜算式】【排列序数】【还款计算】【滑动解锁】

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列点击跳转>蓝桥系列 &#x1f449;关于作者 专注于Android/Unity和各种游…

探索Gradio的CheckboxGroup模块:交互式多选框组件

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️ &#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

详解金融机构敏捷营销落地:体系架构、关键技术和实施方法

本文根据 2023 年金融营销科技价值发现论坛中&#xff0c;神策数据金融首席架构师王仕的主题演讲整理所得&#xff0c;聚焦营销 5.0 理念下的敏捷营销&#xff0c;详细讲解金融机构落地时涉及到的体系架构、关键技术及实施方法。 根据国家发改委官网披露的数据&#xff0c;2022…

macOS编译AirMap开源全景图源码image-processing

1.克隆源码 git clone --recursive https://github.com/airmap/image-processing.git 2. 使用CLion打开CMakeLists.txt并做为工程打开 2.默认配置名为Default,可修改,下面的所有配置项都可改 3.点击OK后会自动生成

青翼自研--4通道DAC播放子卡产品数据手册

FMC131是一款4通道3GSPS采样率或者2通道6GSPS采样率16位DA播放FMC子卡模块&#xff0c;该板卡为FMC标准&#xff0c;符合VITA57.4规范&#xff0c;可以作为一个理想的IO模块耦合至FPGA前端&#xff0c;16通道的JESD204B接口通过FMC连接器连接至FPGA的高速串行端口GTH。插值模式…

突破性5G NTN技术,美格智能携手高通发布卫星物联网连接方案

通信技术的快速发展&#xff0c;使得万物互联成为现实&#xff0c;物联网深刻影响我们的生活方式。目前&#xff0c;全球物联网连接主要由WiFi、蓝牙和蜂窝网络等几类技术支撑。数据显示&#xff0c;蜂窝基站的陆地覆盖率约为20%&#xff0c;而海洋覆盖率则不到5%。 这意味着陆…

十大编程算法

算法一&#xff1a;快速排序算法 快速排序是由东尼霍尔所发展的一种排序算法。在平均状况下&#xff0c;排序 n 个项目要Ο(n log n)次比较。在最坏状况下则需要Ο(n2)次比较&#xff0c;但这种状况并不常见。事实上&#xff0c;快速排序通常明显比其他Ο(n log n) 算法更快&a…

uni-app/vue 文字转语音朗读(附小程序语音识别和朗读)

语音播报的实现的方法有很多种&#xff0c;我这里介绍集中不引用百度、阿里或者迅飞的API的实现方式。 一、采用new SpeechSynthesisUtterance的方式 废话不多说直接上代码 data() {return {utterThis:null,} },//方法使用this.utterThis new SpeechSynthesisUtterance(); …

地下水管三维可视化综合管控平台提升政府服务质量

随着当前互联网技术数据的快速发展和增长&#xff0c;总数越来越大&#xff0c;结构越来越复杂。如果你想更清楚、更快地理解和理解数据&#xff0c;传统的二维平面图数据图性能不能满足需求。 什么是三维可视化? 三维可视化是一种利用计算机技术&#xff0c;再现三维世界中的…

命令模式(Command)

别名 动作&#xff08;Action&#xff09;事务&#xff08;Transaction&#xff09; 定义 命令是一种行为设计模式&#xff0c;它可将请求转换为一个包含与请求相关的所有信息的独立对象。该转换让你能根据不同的请求将方法参数化、延迟请求执行或将其放入队列中&#xff0c…

(1)深度学习学习笔记-数据操作和处理

文章目录 前言一、张量操作二、csv文件数据操作数据预处理(读入csv文件作为pytorch能处理的) 来源 前言 张量的一些处理和操作 csv文件数据操作 一、张量操作 [&#xff1a;&#xff0c;1]表示全部行 第二列 [&#xff1a;]是全取 [1:3,1&#xff1a;]&#xff1a;1:3表示1~3的…

Node.js搭建Https服务

要搭建一个接收HTTPS请求的Node.js服务器,可以按以下步骤操作: 1. 初始化项目和安装依赖: bashnpm init -y npm install expresslatest npm install httpslatest 2. 生成证书文件: 这里按照自己的需求 去买ssl证书 或者获取免费证书 直接百度 阿里云获取免费证书等关键词即可…

Prefix-Tuning 阅读笔记

《Prefix-Tuning: Optimizing Continuous Prompts for Generation》 核心思想&#xff1a; 微调的时候&#xff0c;把预训练好的大transformer固定住不训练&#xff0c;在大transformer前面拼接几个token的参数&#xff0c;只训练前面这几个token的参数。 为什么有效 因为虽…

金融科技领先者Broadridge选择CloudBees CI来加速软件交付

Broadridge公司是全球金融科技领先者&#xff0c;通过丰富客户参与度、控制风险、优化效率和创造收入增长的解决方案为客户推动业务转型。 借助CloudBees CI&#xff0c;Broadridge为所有使用Jenkins的开发团队提供了集中管理和自助服务的体验。Broadridge能够不断为客户提供新…

vue 中node-sass和sass版本问题解决

先开始使用 “node-sass”: “^7.0.1” “sass-loader”: “^10.0.5” 然后使用npm install报错 后面百度&#xff0c;搜到了是因为 node-sass和sass-loader版本不对应造成。 然后修改版本&#xff0c;增加sass引用 “node-sass”: “^7.0.1” “sass-loader”: “^7.3.1”, “…

Mysql实现按月份查询挂号统计数据(聚合函数的使用)

最近公司要实现一个通过年月来查询每个月的挂号统计&#xff08;当日挂号&#xff0c;预约挂号&#xff09;&#xff0c;因为医院每个月都要来问我们这边一次&#xff0c;每次都去数据库查询太麻烦了&#xff0c;所以就需要开发一个查询挂号统计的接口。 前面用了很多种方法,要…

计算机中的逻辑运算(与、或、非、异或、同或、与非、或非)

计算机中的逻辑运算又被称作为“布尔运算”&#xff0c;分别为&#xff1a;与、或&#xff0c;非&#xff0c;异或。此外在门电路中还有&#xff1a;同或、与非、或非。共七种。 这七种逻辑运算中&#xff0c;只有 逻辑非运算 是一元逻辑运算&#xff08;一个运算操作数&#…

微信公众号接收事件推送XML数据包解析成实体对象

1.从request获取推送xml包 String callBackXml testNoticeService.formatNoticeParams(request); public static String formatNoticeParams(HttpServletRequest request){try(ByteArrayOutputStream output new ByteArrayOutputStream();InputStream input request.getIn…