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 = keyself.value = valueself.prev = Noneself.next = Noneclass LRUCache:def __init__(self, capacity: int):self.capacity = capacity# 初始化头尾节点self.head = ListNode()self.tail = ListNode()self.head.next = self.tailself.tail.prev = self.headself.cache = {}def get(self, key: int) -> int:if key not in self.cache:return -1node = self.cache[key]# 将节点移动到链表头部self._move_to_head(node)return node.valuedef put(self, key: int, value: int) -> None:if key in self.cache:# 如果key已存在,更新值并移动到头部node = self.cache[key]node.value = valueself._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.prevnext_node = node.nextprev.next = next_nodenext_node.prev = prevdef _add_node(self, node):node.prev = self.headnode.next = self.head.nextself.head.next.prev = nodeself.head.next = nodedef pop_tail(self):res = self.tail.prevself._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, defaultdictclass LFUCache:def __init__(self, capacity: int):self.capacity = capacityself.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 += 1self.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:returnif 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] = Noneself.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.mzph.cn/bicheng/55352.shtml

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

相关文章

如何从huggingface下载

我尝试了一下若干步骤&#xff0c;莫名奇妙就成功了 命令行代理 如果有使用魔法上网&#xff0c;可以使用命令行代码&#xff0c;解决所有命令行连不上外网的问题&#xff1a; #配置http git config --global http.proxy 127.0.0.1:xxxx git config --global https.proxy 127…

移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——15.红黑树

1.红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff0c;…

专访 Bitlayer 联合创始人 Charlie:探索比特币 Layer2 技术的未来

整理&#xff1a;Tia&#xff0c;Techub News 在加密货币行业经历了近 10 年的风雨历程后&#xff0c;Bitlayer 联合创始人 Charlie Hu 凭借其在以太坊、波卡等顶级项目中的深厚经验&#xff0c;重新聚焦比特币生态&#xff0c;他与 Bitlayer 的另外一位联合创始人 Kevin He 通…

k8s搭建双主的mysql8集群---无坑

《k8s搭建一主三从的mysql8集群---无坑-CSDN博客》通过搭建一主三从&#xff0c;我们能理解到主节点只有1个&#xff0c;那么承担增删改主要还是主节点&#xff0c;如果你在从节点上去操作增删改操作&#xff0c;数据不会同步到其他节点。本章我们将实现多主&#xff08;双主&a…

YOLO11关键改进与网络结构图

目录 前言&#xff1a;一、YOLO11的优势二、YOLO11网络结构图三、C3k2作用分析四、总结 前言&#xff1a; 对于一个科研人来说&#xff0c;发表论文水平的高低和你所掌握的信息差有着极大的关系&#xff0c;所以趁着YOLO11刚刚发布&#xff0c;趁热了解&#xff0c;先人一步对…

Linux-基础实操篇-组管理和权限管理(上)

Linux 组基本介绍 在 linux 中的每个用户必须属于一个组&#xff0c;不能独立于组外。在 linux 中每个文件 有所有者、所在组、其它组的概念。 用户和组的基本概念&#xff1a; 用户名&#xff1a;用来识别用户的名称&#xff0c;可以是字母、数字组成的字符串&#xff0…

(Kafka源码五)Kafka服务端处理消息

Kafka 服务端&#xff08;Broker&#xff09;采用 Reactor 的架构思想&#xff0c;通过1 个 Acceptor&#xff0c;N 个 Processor(N默认为3)&#xff0c;M 个 KafkaRequestHandler&#xff08;M默认为8&#xff09;&#xff0c;来处理客户端请求&#xff0c;这种模式结合了多线…

kubeadm部署k8s集群,版本1.23.6;并设置calico网络BGP模式通信,版本v3.25--未完待续

1.集群环境创建 三台虚拟机&#xff0c;一台master节点&#xff0c;两台node节点 (根据官网我们知道k8s 1.24版本之后就需要额外地安装cri-dockerd作为桥接才能使用Docker Egine。经过尝试1.24后的版本麻烦事很多&#xff0c;所以此处我们选择1.23.6版本) 虚拟机环境创建参考…

【Android】浅析六大设计原则

【Android】浅析六大设计原则 六大设计原则是软件开发中常用的设计原则&#xff0c;用来帮助开发者编写灵活、可维护、可扩展的代码。它们是面向对象设计&#xff08;OOD&#xff09;的核心&#xff0c;遵循这些原则能够避免代码中的常见问题&#xff0c;比如代码难以修改、难…

Vue页面,基础配置

最简单页面 日期范围及字符搜索&#xff0c;监听器处理日期范围搜索控件清空重置问题导出、导出文件文件名称带日期时间表格日期指定格式显示。。。 <template><div class"app-container"><el-form :model"queryParams" ref"queryForm…

YOLOv11改进策略【损失函数篇】| Shape-IoU:考虑边界框形状和尺度的更精确度量

一、本文介绍 本文记录的是改进YOLOv11的损失函数&#xff0c;将其替换成Shape-IoU。现有边界框回归方法通常考虑真实GT&#xff08;Ground Truth&#xff09;框与预测框之间的几何关系&#xff0c;通过边界框的相对位置和形状计算损失&#xff0c;但忽略了边界框本身的形状和…

uboot笔记(一):概括/移植等

目录 前言一、下载地址二、目录介绍三、编译四、移植/适配五、启动流程 前言 本笔记以u-boot-2024.10-rc4代码、在arm64平台运行为例对uboot的介绍、编译、适配移植、运行过程的关键流程等&#xff1b; 一、下载地址 https://ftp.denx.de/pub/u-boot/ 下载自己想要使用的版本即…

并发编程三大特性(原子性、可见性、有序性)

并发编程的三大特性实际是JVM规范要求的JVM实现必须保证的三大特性 不同的硬件和不同的操作系统在内存管理上有一定的差异&#xff0c;JAVA为了解决这种差异&#xff0c;使用JMM&#xff08;Java Memry Model&#xff09;来屏蔽各个操作系统之间的差异&#xff0c;使得java可以…

关于malloc,calloc,realloc

1.引用的头文件介绍&#xff1a; 这三个函数需要调用<stdlib.h>这个头文件 2.malloc 2.1 函数简单介绍&#xff1a; 首先这个函数是用于动态开辟一个空间&#xff0c;例如数组在c99标准之前是无法arr[N]的&#xff0c;这个时候就需要使用malloc去进行处理&#xff0c…

互斥量mutex、锁、条件变量和信号量相关原语(函数)----很全

线程相关知识可以看这里: 线程控制原语(函数)的介绍-CSDN博客 进程组、会话、守护进程和线程的概念-CSDN博客 1.同步概念 所谓同步&#xff0c;即同时起步&#xff0c;协调一致。不同的对象&#xff0c;对“同步”的理解方式略有不同。如&#xff0c;设备同步&#xff0c;是…

【C语言指南】数据类型详解(上)——内置类型

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《C语言指南》 期待您的关注 目录 引言 1. 整型&#xff08;Integer Types&#xff09; 2. 浮点型&#xff08;Floating-Point …

计算机毕业设计 基于Python高校岗位招聘和分析平台的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

Ruby基础语法

Ruby 是一种动态、反射和面向对象的编程语言&#xff0c;它以其简洁的语法和强大的功能而受到许多开发者的喜爱。以下是 Ruby 语言的一些基本语法&#xff1a; 1. 打印输出 puts "Hello, Ruby!" 变量赋值 x 10 name "John" 2. 数据类型 Ruby 有多种…

YOLOv8改进 ,YOLOv8改进主干网络为华为的轻量化架构GhostNetV1

摘要 摘要:将卷积神经网络(CNN)部署在嵌入式设备上是困难的,因为嵌入式设备的内存和计算资源有限。特征图的冗余是成功的 CNN 的一个重要特征,但在神经网络架构设计中很少被研究。作者提出了一种新颖的 Ghost 模块,用于通过廉价操作生成更多的特征图。基于一组内在特征图…

力扣(leetcode)每日一题 983 最低票价 |动态规划

983. 最低票价 题干 在一个火车旅行很受欢迎的国度&#xff0c;你提前一年计划了一些火车旅行。在接下来的一年里&#xff0c;你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。 火车票有 三种不同的销售方式 &#xff1a; 一张 为期一天 的通…