Redis缓存淘汰算法详解

文章目录

    • Redis缓存淘汰算法
      • 1. Redis缓存淘汰策略分类
      • 2. 会进行淘汰的7种策略
        • 2.1 基于过期时间的淘汰策略
        • 2.2 基于所有数据范围的淘汰策略
      • 3. LRU与LFU算法详解
      • 4. 配置与调整
      • 5. 实际应用场景
    • LRU算法以及实现样例
    • LFU算法实现
      • 1. 数据结构选择
      • 2. 访问频率更新
      • 3. 缓存淘汰
      • 4. 缓存插入
      • 5. 优化和变种
      • 6. 注意事项
      • 7. 算法的python实现

Redis缓存淘汰算法

Redis缓存淘汰算法是Redis内存管理机制中的重要组成部分,用于在Redis达到内存使用上限时,通过不同的策略选择部分数据进行删除,以腾出内存空间。Redis提供了多种缓存淘汰策略,这些策略可以根据业务需求和数据特点进行灵活配置。以下是对Redis缓存淘汰算法的详细解析:

1. Redis缓存淘汰策略分类

Redis的缓存淘汰策略可以分为两大类:

  • 不进行数据淘汰的策略:仅有一种,即noeviction。当缓存数据满了,有新的写请求进来时,Redis不再提供服务,而是直接返回错误。
  • 会进行淘汰的策略:共有7种,包括基于数据是否设置过期时间的两类策略。

2. 会进行淘汰的7种策略

2.1 基于过期时间的淘汰策略
  • volatile-random:在设置了过期时间的键值对中,进行随机删除。
  • volatile-ttl:根据过期时间的先后进行删除,越早过期的越先被删除。
  • volatile-lru:使用LRU(Least Recently Used,最近最少使用)算法筛选设置了过期时间的键值对。
  • volatile-lfu:使用LFU(Least Frequently Used,最近最少使用)算法选择设置了过期时间的键值对(Redis 4.0后新增)。
2.2 基于所有数据范围的淘汰策略
  • allkeys-random:从所有键值对中随机选择并删除数据。
  • allkeys-lru:使用LRU算法在所有数据中进行筛选。
  • allkeys-lfu:使用LFU算法在所有数据中进行筛选(Redis 4.0后新增)。

3. LRU与LFU算法详解

  • LRU(Least Recently Used):最近最少使用算法,它的基本思想是淘汰最近最少访问的数据。Redis实现的LRU算法是近似LRU,通过随机选择一定数量的键,并从中选择最不常使用的键进行淘汰。这种方式避免了遍历所有键的开销,但可能会牺牲一定的精确度。
  • LFU(Least Frequently Used):最近最少使用频率算法,它基于数据的访问频率进行淘汰。Redis使用近似计数器为每个键记录访问次数,当内存达到上限时,会优先淘汰访问次数较少的键。LFU算法通过log-log计数器实现,能够以较低的内存开销记录键的访问次数。

4. 配置与调整

  • 设置内存使用上限:通过maxmemory参数来设定Redis的内存使用上限。
  • 配置淘汰策略:通过maxmemory-policy参数来配置淘汰策略。
  • 调整采样数量:对于LRU和LFU算法,可以通过maxmemory-samples参数来控制每次随机选择的键的数量,以提高算法的精确度,但也会增加CPU开销。
  • 监控与评估:通过定期监控Redis的内存使用情况和命中率,可以评估当前的淘汰策略是否合适,并根据需要进行调整。

5. 实际应用场景

  • 缓存数据的重要性较高:适合使用LRU或LFU策略,以保留访问频繁的数据。
  • 数据具有明确的过期时间:适合使用volatile-ttl、volatile-random、volatile-lru或volatile-lfu策略。
  • 数据访问频率不均:适合使用allkeys-lfu或volatile-lfu策略,以提升缓存的命中率。
  • 对数据一致性要求非常高:适合使用noeviction策略,以确保不会随意删除数据。

综上所述,Redis的缓存淘汰算法为Redis的内存管理提供了灵活且强大的支持,通过合理的配置和调整,可以显著提高缓存的效率和性能。

LRU算法以及实现样例

LRU(Least Recently Used,最近最少使用)缓存淘汰算法是一种常用的页面置换算法,用于管理缓存中的数据,确保最近最少使用的数据被优先淘汰。在双向链表中实现LRU缓存是一种高效的方式,因为双向链表可以让我们在O(1)时间复杂度内完成节点的插入和删除操作。

下面是一个使用Python实现的基于双向链表的LRU缓存的简单示例:

class ListNode:
    def __init__(self, key=0, value=0):
        self.key = key
        self.value = value
        self.prev = None
        self.next = None

class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        # 初始化头尾节点
        self.head = ListNode()
        self.tail = ListNode()
        self.head.next = self.tail
        self.tail.prev = self.head
        self.cache = {}

    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1
        node = self.cache[key]
        # 将节点移动到链表头部
        self._move_to_head(node)
        return node.value

    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            # 如果key已存在,更新值并移动到头部
            node = self.cache[key]
            node.value = value
            self._move_to_head(node)
        else:
            # 如果key不存在,创建新节点
            newNode = ListNode(key, value)
            # 添加到哈希表中
            self.cache[key] = newNode
            # 添加到链表头部
            self._add_node(newNode)
            # 如果超出容量,删除链表尾部节点
            if len(self.cache) > self.capacity:
                tail = self.pop_tail()
                del self.cache[tail.key]

    def _move_to_head(self, node):
        # 移除节点
        self._remove_node(node)
        # 添加到头部
        self._add_node(node)

    def _remove_node(self, node):
        prev = node.prev
        next_node = node.next
        prev.next = next_node
        next_node.prev = prev

    def _add_node(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node

    def pop_tail(self):
        res = self.tail.prev
        self._remove_node(res)
        return res

# 使用示例
lru_cache = LRUCache(2)
lru_cache.put(1, 1)
lru_cache.put(2, 2)
print(lru_cache.get(1))  # 返回 1
lru_cache.put(3, 3)      # 淘汰 key 2
print(lru_cache.get(2))  # 返回 -1 (未找到)
lru_cache.put(4, 4)      # 淘汰 key 1
print(lru_cache.get(1))  # 返回 -1 (未找到)
print(lru_cache.get(3))  # 返回 3
print(lru_cache.get(4))  # 返回 4

这个实现中,LRUCache 类维护了一个双向链表和一个哈希表。哈希表用于快速查找节点,而双向链表则用于维护节点的使用顺序。每次访问或添加节点时,都会将其移动到链表的头部,表示最近使用过。当缓存达到容量上限时,会删除链表尾部的节点,即最近最少使用的节点。

LFU算法实现

LFU(Least Frequently Used)算法是一种基于访问频率的缓存淘汰算法,其核心思想是优先淘汰那些访问频率最低的数据项。以下是LFU算法实现的基本步骤和关键点:

1. 数据结构选择

LFU算法的实现通常需要结合多种数据结构来高效地管理缓存项及其访问频率。常见的组合包括哈希表(HashMap)和双向链表(Doubly Linked List)或优先队列(如最小堆Min-Heap):

  • 哈希表:用于存储缓存项及其对应的访问频率信息。键为缓存项的键,值为指向双向链表节点或堆中元素的指针,以及访问频率计数器。
  • 双向链表或优先队列:用于按访问频率排序缓存项。在双向链表中,相同访问频率的节点可以通过额外的链表组织在一起;在优先队列中,则可以直接根据频率进行排序。

2. 访问频率更新

每次缓存项被访问时,需要更新其访问频率:

  • 在哈希表中查找缓存项,获取其当前的访问频率和对应的双向链表节点或堆元素。
  • 将访问频率加1,并更新哈希表中的记录。
  • 如果使用双向链表,可能需要将节点从当前频率的链表中移除,并插入到更高频率的链表中(如果频率增加)。
  • 如果使用优先队列,则可能需要重新调整队列中的位置。

3. 缓存淘汰

当缓存达到容量上限且需要插入新数据时,执行缓存淘汰:

  • 如果使用双向链表,遍历最低频率的链表,找到最久未被访问或访问频率最低的节点进行淘汰。
  • 如果使用优先队列,则直接从队列中取出最小频率的节点进行淘汰。
  • 从哈希表中删除被淘汰的缓存项,并释放相应的内存。

4. 缓存插入

新数据插入时,首先检查缓存是否已满:

  • 如果未满,直接在哈希表中添加新项,并初始化其访问频率为1。如果使用双向链表,将其添加到最低频率的链表中;如果使用优先队列,则将其插入到适当的位置。
  • 如果已满,则执行缓存淘汰操作,然后插入新数据。

5. 优化和变种

  • LFU-Aging:在LFU的基础上增加“老化”机制,即定期降低所有缓存项的访问频率,以便更快地适应访问模式的变化。
  • Window-LFU:只记录过去一段时间内的访问历史,而不是所有数据的历史访问记录,以减少内存消耗和提高效率。
  • LFU*:只淘汰访问次数为1的缓存项,以减少缓存污染问题。

6. 注意事项

  • LFU算法在访问模式稳定时表现良好,但在访问模式频繁变化时可能会出现缓存污染问题。
  • 相比LRU算法,LFU算法需要更多的内存来记录访问频率信息,并且在访问频率更新和排序时可能会有更高的性能开销。

7. 算法的python实现

在Python中实现LFU(Least Frequently Used)缓存算法,我们可以使用collections模块中的OrderedDict来模拟双向链表的功能(虽然它实际上是一个有序字典),以及一个哈希表来记录每个键的访问频率。不过,为了更准确地实现LFU算法,特别是当需要频繁地根据访问频率进行排序时,我们可能需要一个更复杂的结构,比如使用heapq(最小堆)来优化查找最小频率项的过程。

然而,为了简化说明,这里我将提供一个基于OrderedDict和额外哈希表的LFU缓存实现,该实现将模拟LFU的行为,但可能不是最高效的(特别是在处理大量数据和频繁更新时)。

from collections import OrderedDict, defaultdict

class LFUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity
        self.cache = OrderedDict()  # 存储键和对应的值以及频率
        self.frequency = defaultdict(OrderedDict)  # 存储每个频率对应的键的集合

    def get(self, key: int) -> int:
        if key not in self.cache:
            return -1

        # 更新访问频率
        freq = self.cache[key][1]
        self.frequency[freq].pop(key)
        if not self.frequency[freq]:
            del self.frequency[freq]

        freq += 1
        self.cache[key] = (self.cache[key][0], freq)
        if freq not in self.frequency:
            self.frequency[freq] = OrderedDict()
        self.frequency[freq][key] = None

        # 将键移到OrderedDict的末尾,模拟最近访问
        self.cache.move_to_end(key)

        return self.cache[key][0]

    def put(self, key: int, value: int) -> None:
        if self.capacity <= 0:
            return

        if key in self.cache:
            # 更新现有键的值和频率
            self.cache[key] = (value, self.cache[key][1] + 1)
            freq = self.cache[key][1]
            self.frequency[self.cache[key][1] - 1].pop(key)
            if not self.frequency[self.cache[key][1] - 1]:
                del self.frequency[self.cache[key][1] - 1]
            if freq not in self.frequency:
                self.frequency[freq] = OrderedDict()
            self.frequency[freq][key] = None
            self.cache.move_to_end(key)
        else:
            # 插入新键
            if len(self.cache) >= self.capacity:
                # 淘汰最少使用的项
                min_freq = min(self.frequency.keys())
                oldest_key = next(iter(self.frequency[min_freq]))
                del self.cache[oldest_key]
                del self.frequency[min_freq][oldest_key]
                if not self.frequency[min_freq]:
                    del self.frequency[min_freq]

            # 插入新项
            self.cache[key] = (value, 1)
            if 1 not in self.frequency:
                self.frequency[1] = OrderedDict()
            self.frequency[1][key] = None

# 示例用法
lfu = LFUCache(2)
lfu.put(1, 1)
lfu.put(2, 2)
print(lfu.get(1))  # 返回 1
lfu.put(3, 3)      # 淘汰键 2
print(lfu.get(2))  # 返回 -1 (未找到)
lfu.put(4, 4)      # 淘汰键 1
print(lfu.get(1))  # 返回 -1 (未找到)
print(lfu.get(3))  # 返回 3
print(lfu.get(4))  # 返回 4

注意:这个实现虽然模拟了LFU的行为,但在处理大量数据和频繁更新时可能不是最高效的。特别是,它使用OrderedDict来模拟双向链表的行为,这在每次更新频率时都需要进行字典的插入和删除操作,这些操作的时间复杂度都是O(1)平均情况下,但在最坏情况下(即哈希冲突严重时)可能会退化。

为了获得更好的性能,特别是在需要频繁更新频率和根据频率进行排序时,可以考虑使用更复杂的数据结构,如平衡树或自定义的双向链表与哈希表的组合。然而,这些实现将更加复杂,并且可能需要更多的内存和代码来实现。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

9.26 Buu俩题解

[CISCN2019 华东北赛区]Web2 看wp写完之后写的 知识点 存储型XSS与过滤绕过sql注入 题解 好几个页面&#xff0c;存在登录框可以注册&#xff0c;存在管理员页面(admin.php) ->既然存在管理员页面&#xff0c;且直接访问admin.php提示我们 说明存在身份验证&#xff0…

K8S的Pod IP

pod 的ip 一般是提供给pod1与pod2之间的通信&#xff0c;它有两个特点 1. Pod IP会随着Pod实例 的创新创建&#xff08;重启&#xff09;发生变化&#xff1b; 2. Pod IP只在集群内节点可见&#xff0c;外部无法直接访问

椭圆距离计算的简单方法

分析发现找到点到椭圆的最近距离等价于求解一元四次方程。想象一下一个圆和一个椭圆最多相交四次。从这个观点出发,问题转化为找到与椭圆仅相交一次的圆。如果用四次方程表示,其中两个根将在交点处共享,而另外两个根将会是复数。 尽管四次方程的封闭解确实存在,但迭代方法更…

使用Python和OpenCV生成灰阶图像

代码如下&#xff1a; import cv2 import numpy as npimg np.zeros((256, 256), np.uint8)for i in range(0,16):for j in range(0,16):img[i*16:(i1)*16][j*16:(j1)*16]i*16jcv2.imwrite(result.jpg, img) 效果如下&#xff1a;

Power Automate 设置流Owner不生效的bug

在查找某个功能没生效时&#xff0c;定位到是一个Power automate的流停了&#xff0c;查看原因是因为创建流的owner被disable了 但是当把流的owner更新为可用的用户时&#xff0c;流依旧没被触发&#xff0c;触发的条件很简单&#xff0c;某个表的记录创建时&#xff0c;因为是…

操作系统与进程

1.操作系统 操作系统是计算机中的一个重要软件&#xff0c;它是一个专门进行管理的软件。操作系统可以通过驱动程序来间接管理外部硬件&#xff0c;也可以为计算机中的程序提供一个稳定的运行环境&#xff0c;从而来方便管理各种程序的运行&#xff0c;让程序之间的运行互不影…

传知代码-基于图神经网络的知识追踪方法(论文复现)

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 1.论文概述 论文链接提出了一种基于图神经网络的知识追踪方法&#xff0c;称为基于图的知识追踪&#xff08;GKT&#xff09;。将知识结构构建为图&#xff0c;其中节点对应于概念&#xff0c;边对应于它们之间的…

【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯&#xff0c;你们的点赞收藏是我前进最大的动力&#xff01;&#xff01;希望本文内容能够帮助到你&#xff01; 目录 一&#xff1a;单例模式&#xff08;singleton&#xff09; 1&#xff1a;概念 二&#xff1a;“饿汉模…

Enhancing Trust in LLMs: Algorithms for Comparing and Interpreting LLMs

文章目录 题目摘要引言透明度的必要性对信任的追求困惑度测量自然语言处理(NLP)评估指标零投学习绩效少量学习性能迁移学习对抗测试公平和偏见稳健性评估LLMMaps基准测试和排行榜分层分析布鲁姆分类法的可视化幻觉评分知识分层策略利用机器学习模型进行层级生成注意力可视化LLM…

css五种定位总结

在 CSS 中&#xff0c;定位&#xff08;Positioning&#xff09;主要有五种模式&#xff0c;每种模式的行为和特点不同&#xff0c;以下是 static、relative、absolute、fixed 和 sticky 五种定位方式的对比总结&#xff1a; 1. static&#xff08;默认定位&#xff09; 特性…

阿里云函数计算 x NVIDIA 加速企业 AI 应用落地

作者&#xff1a;付宇轩 前言 阿里云函数计算&#xff08;Function Compute, FC&#xff09;是一种无服务器&#xff08;Serverless&#xff09;计算服务&#xff0c;允许用户在无需管理底层基础设施的情况下&#xff0c;直接运行代码。与传统的计算架构相比&#xff0c;函数…

【2023工业3D异常检测文献】PointCore: 基于局部-全局特征的高效无监督点云异常检测器

PointCore: Efficient Unsupervised Point Cloud Anomaly Detector Using Local-Global Features 1、Background 当前的点云异常检测器可以分为两类&#xff1a; &#xff08;1&#xff09;基于重建的方法&#xff0c;通过自动编码器重建输入点云数据&#xff0c;并通过比较原…

07-阿里云镜像仓库

07-阿里云镜像仓库 注册阿里云 先注册一个阿里云账号&#xff1a;https://www.aliyun.com/ 进入容器镜像服务控制台 工作台》容器》容器服务》容器镜像服务 实例列表》个人实例 仓库管理》镜像仓库》命名空间》创建命名空间 仓库管理》镜像仓库》镜像仓库》创建镜像仓库 使…

【AI】深度学习的数学--核心公式

1 梯度下降 f ( x Δ x , y Δ y ) ≃ f ( x , y ) ∂ f ( x , y ) ∂ x Δ x ∂ f ( x , y ) ∂ y Δ y f(x\Delta x,y\Delta y) \simeq f(x,y)\frac{\partial f(x,y)}{\partial x}\Delta x\frac{\partial f(x,y)}{\partial y}\Delta y f(xΔx,yΔy)≃f(x,y)∂x∂f(x,y)​…

MySQL 性能剖析全攻略

在使用 MySQL 数据库的过程中&#xff0c;性能问题往往是让开发者和管理员头疼的难题。为了有效地解决这些问题&#xff0c;我们需要对 MySQL 进行性能剖析。那么&#xff0c;如何在 MySQL 中进行性能剖析呢&#xff1f;本文将为你详细介绍。 一、为什么要进行性能剖析&#x…

基于安卓开发大型体育场管理系统的设计与实现(源码+定制+讲解)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

《开题报告》基于SpringBoot框架的高校专业实习管理系统开题报告的设计与实现源码++学习文档+答辩讲解视频

开题报告 研究背景 在当今高等教育日益普及与深化的背景下&#xff0c;高校专业实习作为学生将理论知识转化为实践能力、提前适应社会工作环境的重要环节&#xff0c;其重要性不言而喻。然而&#xff0c;传统的高校专业实习管理模式往往存在信息不对称、流程繁琐、效率低下、…

SSM+Vue共享单车管理系统

目录 1 项目介绍2 项目截图3 核心代码3.1 Controller3.2 Service3.3 Dao3.4 spring-mybatis.xml3.5 spring-mvc.xml3.5 Vue 4 数据库表设计5 文档参考6 计算机毕设选题推荐7 源码获取 1 项目介绍 博主个人介绍&#xff1a;CSDN认证博客专家&#xff0c;CSDN平台Java领域优质创作…

代码随想录_刷题记录_第四次

二叉树 — 理论基础 种类&#xff1a; 满二叉树&#xff08;所有层的节点都是满的&#xff0c;k&#xff1a;深度 节点数量&#xff1a;2^k - 1&#xff09;完全二叉树&#xff08;除了最后一层&#xff0c;其余层全满&#xff0c;并且最后一层从左到右连续&#xff09;二叉搜…

信道衰落的公式

对于天线&#xff1a; 对于天线的面积计算&#xff1a; 天线的接收功率密度&#xff1a; 天线的接收功率&#xff1a; 移动无线信道&#xff08;I&#xff09; (xidian.edu.cn)https://web.xidian.edu.cn/zma/files/20150710_153736.pdf 更加常用的考虑了额外的信道衰落pathlo…