iis服务器助手广告广告
返回顶部
首页 > 资讯 > 精选 >Redis中怎么使用缓存替换策略
  • 215
分享到

Redis中怎么使用缓存替换策略

2023-06-20 17:06:02 215人浏览 泡泡鱼
摘要

Redis中怎么使用缓存替换策略,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1 概述在操作系统的页面管理中,内存会维护一部分数据以备进程使用,但是由于内存的大小必然是远远

Redis中怎么使用缓存替换策略,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。

1 概述

操作系统的页面管理中,内存会维护一部分数据以备进程使用,但是由于内存的大小必然是远远小于硬盘的,当某些进程访问到内存中没有的数据时,必然需要从硬盘中读进内存,所以迫于内存容量的压力下迫使操作系统将一些页换出,或者说踢出,而决定将哪些(个)页面踢出就是内存替换策略。

我们考虑内存中的页实际上是整个系统页的子集,所以内存可以当成系统中虚拟内存的缓存(Cache),所以页面置换算法就是缓存替换的方法。

一般意义下,选取页面置换算法即选取一个缓存命中率更高的或者说缺页率更低的算法,但实际上有时候随着算法缓存命中率提升,算法复杂度也在上升,所以带来的系统开销也是在上升的,所以我们不得不在系统开销和算法复杂程度中间取一个折中,比如Redis中采取的类LRU缓存置换策略实际上在大多数情况下比起理想LRU缓存命中率是低的,但理想LRU实现起来会给系统带来更大的开销,得不偿失。

2 页面置换算法

下面介绍最常见的五种置换策略,其中最佳置换算法是理论上很难实现的,其余的FIFO、LRU、LFU,其算法复杂度、开销是递增的,但是缺页率也是越来越低,或者说越来越接近最佳置换策略的。最后一种时钟置换算法是一种近似的LRU算法,是保证了低系统开销下同时较低的缺页率的一种折中选择。

2.1 最佳(Optimal)置换算法

最佳置换算法,其所选择的被淘汰的页面将是以后永不使用的,或是在最长(未来)时间内不再被访问的页面。听名字定义很显然页面未来的使用情况它就不可预测,所以最佳置换算法只是理论上的最优方法,实际上在大多数情况下并没法完成,所以更多的是作为衡量其他置换算法的标准。

2.2 先进先出(FIFO)置换算法

许多早期的操作系统为了保证算法的简单,避免高复杂度的算法,尝试了最简单的置换策略,即FIFO置换策略,顾名思义就是维护一个页的队列,最先进入内存的页最先被淘汰,这样的算法复杂度低,系统开销也极低,但是显然命中率会下降。

另外考虑一种情况,增大缓存时,一般意义下缓存的命中率必然会上升,但是FIFO算法则在有的时候则会下降,这种现象叫做Belady现象。原因是FIFO算法没有考虑到程序的局部性原则,踢出的页面很可能是程序经常性访问的页面。

Belady现象的实例分析可以参考belady

2.3 最近最少使用(Least Recently Used,LRU)置换算法

为了解决Belady现象,同时也是基于程序的局部性原则(Locality of reference,指的是在计算机科学计算机科学领域中应用程序在访问内存的时候,倾向于访问内存中较为靠近的值。)就有了LRU置换策略,从程序代码的角度理解局部性原则就是,程序一般倾向于频繁的访问某些代码(比如循环)或者数据结构(比如循环访问的数组),因此我们应该使用历史访问数据来决定,哪些页应该被踢出,哪些页不应该被踢出,具体的就是,最近没有被访问的页优先被踢出。这个其实不难理解,通过一个访问顺序的队列,每次访问某个页,就将此页移到队首,当内存(缓存)满了时优先从队尾踢出相应的页。(下面给出一个例子,表来自操作系统导论第22章)

Redis中怎么使用缓存替换策略

但是显然的是,LRU会带来更大的系统开销,因为我们需要频繁的将访问过的页置于访问序列的首部,这就需要对访问队列的内容进行频繁的增删,而FIFO只需要简单排列即可。

2.4 最不经常使用(Least Frequently Used,LFU)置换算法

如果说LRU是通过所有页面的最近使用情况或者说访问时间来看,那么LFU即通过访问频率来决定哪些页面需要被踢出,根据程序的局部性原则,显然LFU会带来更高的缓存命中率,但是考虑到需要记录频率并且频繁的调整队列,实际上带来的系统开销会比LRU更大。

下图描述了一个LFU的执行过程,大写字母为相应的页,圆圈中的数字代表访问频率,访问频率最低的在队列的最后,当需要踢出时,优先踢出队列末尾的页。

Redis中怎么使用缓存替换策略

2.5 时钟(CLOCK)置换算法

上文谈到了LRU和LFU会带来更高的缓存命中率,但是计算开销显然会更大,给系统带来更高的时间开销,而一些类LRU算法就出现了,比如时钟算法。

系统中的所有页都放在一个循环列表中。时钟指针(clock hand)开始时指向某个特定的页(哪个页不重要)。当必须进行页替换时,操作系统检查当前指向的页P的使用位是1还是0。如果是1,则意味着页面P最近被使用, 因此不适合被替换。然后,P的使用位设置为0,时钟指针递增到下一页(P+1)。该算法一直持续到找到一个使用位为 0 的页,使用位为 0 意味着这个页最近没有被使用过(在最坏的情况下,所有的页都已经被使用了,那么就将所有页的使用位都设置为 0)。

Redis中怎么使用缓存替换策略

3 朴素LRU的实现

LeetCode146. LRU 缓存机制为例,最直观朴素的LRU缓存机制可以使用哈希表以及双向链表实现,当然Java的集合LinkedHashMap即实现了一个哈希表+链表的组合,可以直接调用实现。

但为了更形象的理解LRU的机制,采用HashMap以及手写一个双向链表来实现。具体的结构如下图所示

Redis中怎么使用缓存替换策略

维护一个大小为缓存容量的map,key值为缓存数据的key,value存储指向相应节点的引用,数据链表使用双向链表便于增删,当访问到某条数据时,通过map以O(1)复杂度定位到数据的应用,然后

  • 删除访问节点

  • 添加访问节点到队首

当节点数量>缓存容量时,删除队尾元素,同时移除map中的数据,具体实现如下。

public class LRUCache {class DLinkednode {    int key, value;    DLinkedNode pre;    DLinkedNode next;    public DLinkedNode(){}    public DLinkedNode(int key, int value){this.key = key; this.value = value;}}    private Map<Integer, DLinkedNode> cache = new HashMap<>();    private int size;    private int cal;    private DLinkedNode head, tail;    public LRUCache(int capacity) {        size = 0;        cal = capacity;        head = new DLinkedNode();        tail = new DLinkedNode();        head.next = tail;        tail.pre = head;    }        public int get(int key) {        DLinkedNode node = cache.get(key);        if (node == null) {            return -1;        }        moveToHead(node);        return node.value;    }        public void put(int key, int value) {        DLinkedNode node = cache.get(key);        if (node == null) {            DLinkedNode newNode = new DLinkedNode(key, value);            cache.put(key, newNode);            size++;            addToHead(newNode);            if (size > cal) {                DLinkedNode tail = removeTail();                cache.remove(tail.key);                size--;            }        } else {            node.value = value;            moveToHead(node);        }    }    private void moveToHead(DLinkedNode node) {        removeNode(node);        addToHead(node);    }    private void removeNode(DLinkedNode node) {        node.pre.next = node.next;        node.next.pre = node.pre;    }    private void addToHead(DLinkedNode node) {        node.pre = head;        node.next = head.next;        head.next.pre = node;        head.next = node;    }    private DLinkedNode removeTail() {        DLinkedNode realTail = tail.pre;        removeNode(realTail);        return realTail;    }}

4 LRU的实际应用

4.1 以Redis为例

上面谈到要实现一个朴素的LRU算法,需要维护一个双向链表,存储前驱、后继指针,必然会在寸金寸土的缓存(内存)中带来不必要的开销。上述提到的时钟算法就是一种类LRU算法,用更少的系统开销带来了接近朴素LRU的命中率。而事实上,Redis中采取的LRU算法也是一种类LRU算法,它也带来了时钟算法类似的效果。具体的是Redis通过随机选取几个key值,淘汰时间戳最靠前的key,涉及到LRU的淘汰策略maxmemory_policy(这个字段可以选择不同的缓存淘汰策略,Redis一种提供了8种,本文只分析其中与LRU有关的)的赋值两种:

  • allkeys-lru: 对所有的键都采取LRU淘汰

  • volatile-lru: 仅对设置了过期时间的键采取LRU淘汰

可以根据实际情况选择上述两种LRU淘汰策略,在一般情况下,程序都存在局部性,或者说幂次分布(二八法则),即少数数据比其他大多数数据访问的更多,所以通常使用allkeys-lru策略,而当有需求需要长期保留一部分数据在内存中时选取volatile-lru即只规定部分需要淘汰的数据。

下面看一下具体是如何设置的,Redis整体上是一个大的字典,key是一个string,而value都会保存为一个robj(Redis Object),对于robj的定义如下

typedef struct redisObject {    unsigned type:4;    unsigned encoding:4;    unsigned lru:LRU_BITS;     int refcount;    void *ptr;} robj;

其中LRU_BITS即Redis为每个键值对配备的一个记录时间戳的bit位,Redis的LFU替换策略中也使用这个空间,创建对象时会写入到lru_clock这个字段中,访问对象时会对其进行更新,具体的空间的大小被定义为一个常量

#define REDIS_LRU_BITS 24

大小为24,即使用一个额外的24bit的空间记录相对时间戳(即对unix time取模之后的结果),默认的时间戳分辨率是1秒,在这种情况下,24bits的空间如果溢出的话需要194天,而作为频繁更新的缓存而言,这个空间够用了。

上面提到了Redis会随机采样,比较其访问时间哪个更靠前,当需要替换时优先踢出采样结果最靠前的键值对,具体的采样个数在最开始是选取3个key,但是效果并不好,后来增加到N个key,但是默认是5个,即默认随机选取5个key,最先淘汰掉这5个中距离上次访问时间最久的,Redis3.0中又改善了算法的性能,即提供了一个采样池(pool),候选采样池默认大小为16,能够填充16个key,更新时从Redis键空间随机选择N个key,分别计算它们的空闲时间idle,key只会在pool不满或者空闲时间大于pool里最小的时,才会进入pool,然后从pool中选择空闲时间最大的key淘汰掉。

具体的这个候选集合结构体如下

struct evictionPoolEntry {    unsigned long long idle; // 用于淘汰排序,在不同算法中意义不同优先淘汰值大的,单位是ms    sds key;  // 键的名字    // ...};

所以关键在pool中的idle字段的计算,实际上只需要使用当前的时间戳减去lru_clock即可,但是所记录的时间戳都是取模之后的结果,所以还需要比较当前计算出来的时间戳是否大于lru_clock,如果不是,则需要将当前

时间戳+194天(模数)再减去lru_clock。具体的计算过程如下

// 以秒为精度计算对象距离上一次访问的间隔时间,然后转化成毫秒返回unsigned long long estimateObjectIdleTime(robj *o) {    unsigned long long lruclock = LRU_CLOCK();    if (lruclock >= o->lru) {        return (lruclock - o->lru) * LRU_CLOCK_RESOLUTION;    } else {        return (lruclock + (LRU_CLOCK_MAX - o->lru)) *                    LRU_CLOCK_RESOLUTION;    }}

另外Redis官方文档里有对于候选数为5、10在redis2.8无侯选池,以及3.0加侯选池的对比。

Redis中怎么使用缓存替换策略

四张图依次是,理论LRU的使用情况、有pool采样数为10的候选情况、无pool采样数为5的情况、有pool采样数为10的情况。其中

  • 绿色部分是新添加的key

  • 灰色部分是最近使用的key浅

  • 灰色部分是替换的key

可以看出采取Redis3.0的采取维护一个候选淘汰池的方法已经能够接近全局比较情况下也即朴素LRU的结果。

详细的分析可以参考https://redis.io/topics/lru-cache

4.2 以MySQL的InnoDB引擎为例

此处InnoDB的缓存概念不做过多赘述,只简单介绍其中LRU的应用,InnoDB会把cpu频繁使用的数据存储在主存的缓冲池(Buffer Pool)中,鉴于Mysql在使用过程中存在着经常性的全表扫描,所以如果使用朴素LRU必然会频繁的大面积替换,造成极低的缓存命中率。

所以InnoDB采取了一种冷热分离的思路,即将整个缓冲池分为冷区和热区或者说年轻区(New Sublist)和老区(Old Sublist)

Redis中怎么使用缓存替换策略

默认情况下距离链表尾3/8以上的位置称为新子列表(热点区域),以下的位置称为旧子列表(冷区域),某个页面初次加载到缓冲池时,放在old区域的头部。在对某个处于old区域的缓冲页进行第一次访问时,就在它对应的控制块中记录下这个访问时间,如果后续的访问时间和第一次访问的时间在某个时间间隔内(默认为1000ms),那么该页面就不会从老的区域移动到年轻区域的头部,否则将他移动到年轻区域的头部。

而当缓冲池满时需要淘汰数据就从old区域的尾部进行淘汰,这样数据起码需要两次(一次为加载到内存,第二次为大于间隔时间的读取)合理的操作才能移动到年轻区域,有效的预防了全表扫描带来的命中率降低问题。

关于Redis中怎么使用缓存替换策略问题的解答就分享到这里了,希望以上内容可以对大家有一定的帮助,如果你还有很多疑惑没有解开,可以关注编程网精选频道了解更多相关知识。

--结束END--

本文标题: Redis中怎么使用缓存替换策略

本文链接: https://www.lsjlt.com/news/298556.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • Redis中怎么使用缓存替换策略
    Redis中怎么使用缓存替换策略,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。1 概述在操作系统的页面管理中,内存会维护一部分数据以备进程使用,但是由于内存的大小必然是远远...
    99+
    2023-06-20
  • 缓存替换策略及应用(以Redis、InnoDB为例)
    1 概述 在操作系统的页面管理中,内存会维护一部分数据以备进程使用,但是由于内存的大小必然是远远小于硬盘的,当某些进程访问到内存中没有的数据时,必然需要从硬盘中读进内存,所以迫于内存...
    99+
    2024-04-02
  • redis缓存失效策略怎么设置
    在Redis中设置缓存失效策略通常使用过期时间来实现。可以使用EXPIRE命令来设置缓存的过期时间,当缓存的过期时间到达时,缓存将自...
    99+
    2024-04-09
    redis
  • Python怎么使用LRU缓存策略进行缓存
    本文小编为大家详细介绍“Python怎么使用LRU缓存策略进行缓存”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python怎么使用LRU缓存策略进行缓存”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。一、Pyt...
    99+
    2023-07-06
  • LRU缓存替换策略及C#实现方法分享
    目录LRU缓存替换策略核心思想不适用场景算法基本实现算法优化优化思路:进一步优化BenchmarkLRU缓存替换策略 缓存是一种非常常见的设计,通过将数据缓存到访问速度更快的存储设备...
    99+
    2023-05-17
    基于无锁的C#并发队列 cas实现无锁队列 cas无锁技术的理解
  • Redis缓存的淘汰策略是什么
    这篇文章主要讲解了“Redis缓存的淘汰策略是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Redis缓存的淘汰策略是什么”吧!Redis(Remote...
    99+
    2024-04-02
  • Redis缓存中的淘汰策略有哪些
    本篇内容主要讲解“Redis缓存中的淘汰策略有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Redis缓存中的淘汰策略有哪些”吧!我们知道Redis缓存使用...
    99+
    2024-04-02
  • Python如何使用LRU缓存策略进行缓存
    本文小编为大家详细介绍“Python如何使用LRU缓存策略进行缓存”,内容详细,步骤清晰,细节处理妥当,希望这篇“Python如何使用LRU缓存策略进行缓存”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。一、Pyt...
    99+
    2023-06-30
  • Spring boot中mybatis的二级缓存怎么使用Redis集群进行替换
    这篇文章给大家介绍Spring boot中mybatis的二级缓存怎么使用Redis集群进行替换,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。1 . pom.xml添加相关依赖<parent> <...
    99+
    2023-05-31
    springboot mybatis redis
  • Redis中如何使用内存淘汰策略
    Redis中如何使用内存淘汰策略,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Redis配置内存1、通过配置文件配置 通过在R...
    99+
    2024-04-02
  • Python使用LRU缓存策略进行缓存的方法步骤
    目录一、Python 缓存① 缓存作用② 使用 Python 字典实现缓存二、深入理解 LRU 算法① 查看 LRU 缓存的特点② 查看 LRU 缓存的结构三、使用 lru_cach...
    99+
    2024-04-02
  • SpringBoot中怎么使用Redis做缓存
    在SpringBoot中使用Redis做缓存可以通过以下步骤实现: 添加依赖:首先在pom.xml文件中添加Spring Data...
    99+
    2024-04-09
    SpringBoot Redis
  • redis延迟双删策略怎么使用
    这篇文章主要讲解了“redis延迟双删策略怎么使用”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“redis延迟双删策略怎么使用”吧!在当前环境下,通常我们会首选redis缓存来减轻我们数据库...
    99+
    2023-07-06
  • Redis三种常用的缓存读写策略步骤详解
    目录一、Redis三种常用的缓存读写策略二、旁路缓存模式(Cache Aside Pattern)读写步骤写:读:自我思考缺点三、读写穿透(Read/Write Through Pa...
    99+
    2024-04-02
  • redis缓存清除策略及配置的方法是什么
    Redis缓存清除策略通常包括以下几种: 定时过期:设置键的过期时间,当键过期时自动清除。 惰性删除:在获取键时检查它是否过期,如...
    99+
    2024-04-02
  • odoo中怎么使用redis实现缓存
    本篇内容主要讲解“odoo中怎么使用redis实现缓存”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“odoo中怎么使用redis实现缓存”吧!Odoo中使用Redis实现缓存可以提高系统性能,避...
    99+
    2023-07-05
  • PHP 函数调用中的缓存优化策略
    为了优化 php 中经常调用的函数性能,可以通过缓存函数结果实现。有两种缓存策略:1. static 函数将结果存储在静态变量中;2. apc 扩展用于缓存字节码和函数结果。利用这些策略...
    99+
    2024-04-17
    php 缓存优化
  • Django中HTTP响应的缓存策略是什么?
    Django是一个流行的Python Web框架,它提供了许多功能来帮助开发人员快速构建高质量的Web应用程序。在Web开发中,HTTP响应的缓存策略对于提高Web应用程序的性能至关重要。在本文中,我们将讨论Django中HTTP响应的缓存...
    99+
    2023-07-07
    http 响应 django
  • redis中怎么设置淘汰策略
    在Redis中,可以通过配置maxmemory-policy参数来设置淘汰策略,具体的淘汰策略有以下几种: noeviction...
    99+
    2024-05-11
    redis
  • Spring Boot中怎么使用集中式缓存Redis
    本篇内容介绍了“Spring Boot中怎么使用集中式缓存Redis”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!动手试试User实体的定义...
    99+
    2023-06-27
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作