文章目录

  • 缓存淘汰策略
  • LRU
    • 核心结构
    • 核心操作流程
    • 局限性
  • 源码走读
    • Add
    • Get

缓存淘汰策略

缓存淘汰策略的存在是为了解决 缓存容量有限性高缓存命中率 之间的矛盾。其核心目标是在有限的缓存空间内,尽可能提高缓存命中率


  • 缓存容量有限性:缓存(例如进程的内存缓存)的空间是有限的。当缓存空间被填满,又来了新数据时,需要淘汰一些老数据,给新数据腾出空间
  • 高缓存命中率:决定要淘汰哪些数据,对于提高缓存命中率至关重要。如果选择淘汰热数据,那么缓存命中率就低。反之如果淘汰冷数据,缓存命中率就高

常见的缓存淘汰策略有LRU,2q,LFU,tinyLFU等。本文介绍第一种:LRU

LRU

其核心思想是:当缓存空间不足时,优先淘汰最久未被访问的数据。它基于“时间局部性”原理,即假设最近被访问的数据更有可能在未来被再次访问

核心结构

LRU的核心结构为 一个哈希表 + 一个双向链表

  • 双向链表:按访问时间顺序维护缓存项,链表头部是最近使用的项,尾部是最久未使用的项(淘汰候选)

    • 链表的每个节点entry包含以下字段:key,value,prev(链表上一个节点),next(链表下一个节点)
  • 哈希表:存储键(Key)到链表节点(Node)的映射

在这里插入图片描述

核心操作流程

  1. 访问数据(Get):
    1. 通过哈希表在 O(1) 时间内找到对应链表节点。
    2. 将该节点从链表中删除(O(1)),并重新插入到链表头部(O(1))。
    3. 返回节点值。
  2. 插入数据(Put):
    1. 如果键已存在:更新值,并像 Get 一样将节点移动到头部。
    2. 如果键不存在:
      1. 创建新节点,插入链表头部(O(1))。
      2. 将键和节点存入哈希表(O(1))。
      3. 如果缓存已满,删除链表尾部节点(O(1)),并同步删除哈希表中对应的键。

可以看出通过哈希表和双向链表的配合,Get和Put的时间复杂度都是O(1)非常高效


一些设计上的关键问题:

  • 为啥不用单链表,要用双向链表?
    • 拿到要删除的节点后,单链表无法在 O(1) 时间内删除一个节点
  • 为啥节点需要存储key?
    • 当某个节点被淘汰时,可以O(1)时间根据key去哈希表中进行删除操作

局限性

上面介绍的LRU有下面的局限性:

  • 突发流量污染:如果某个很少访问的项在短时间内被突然大量访问(即使之后不再访问),它会长时间占据缓存头部,挤掉可能更热(访问频率更高但近期未被访问)的项。
  • 没有考虑缓存项的频率:例如一个只访问一次但刚好是最近访问的项,会排在访问了十次但稍早访问的项前面。但这在大多数场景下是不符合预期的

这两个问题在2q,LFU,tinyLFU会得到解决

源码走读

下面将针对一个经典的开源库https://github.com/hashicorp/golang-lru的LRU实现进行源码走读,版本:v2.0.7

数据结构如下:

type LRU[K comparable, V any] struct {// 表示缓存的最大容量size int// 双向链表,用于维护缓存中条目的访问顺序evictList *internal.LruList[K, V]// 是一个哈希表(字典),将键(K)映射到对应的缓存条目(*internal.Entry) items   map[K]*internal.Entry[K, V]onEvict EvictCallback[K, V]
}

每个entry的核心字段如下:
type Entry[K comparable, V any] struct {next, prev *Entry[K, V]// 属于哪个链表list *LruList[K, V]Key KValue V
}

Add

func (c *LRU[K, V]) Add(key K, value V) (evicted bool) {// 检查是否已存在相同键的条目if ent, ok := c.items[key]; ok {// 如果存在,则将该条目移动到双向链表的最前面(表示最近使用),并更新其值c.evictList.MoveToFront(ent)ent.Value = valuereturn false}// 将新的键值对插入到双向链表的头部,表示这是最新的访问项ent := c.evictList.PushFront(key, value)// 同时将该条目加入到哈希表 items 中,以便后续快速查找c.items[key] = ent// 判断是否超出容量限制evict := c.evictList.Length() > c.sizeif evict {// 移除最老的元素c.removeOldest()}return evict
}

移除最老元素流程如下:

func (c *LRU[K, V]) removeOldest() {// 找到双向链表中最老的元素entif ent := c.evictList.Back(); ent != nil {c.removeElement(ent)}
}func (c *LRU[K, V]) removeElement(e *internal.Entry[K, V]) {// 从双向链表中移除c.evictList.Remove(e)// 从哈希表中删除delete(c.items, e.Key)if c.onEvict != nil {c.onEvict(e.Key, e.Value)}
}

Get

func (c *LRU[K, V]) Get(key K) (value V, ok bool) {// 如果存在,将其移动到链表头部,标识最近访问if ent, ok := c.items[key]; ok {c.evictList.MoveToFront(ent)return ent.Value, true}return
}

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

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

相关文章

什么是 Bootloader?怎么把它移植到 STM32 上?

一、Bootloader 是啥?它都干了些啥?想象一下你的 MCU(比如 STM32)是一个小机器人,上电之后第一件事,它不会立马开始“干正事”(运行你的主程序),而是先去运行一个“开场引…

无人机避障——感知篇(Ego_Planner_v2中的滚动窗口实现动态实时感知建图grid_map ROS节点理解与参数调整影响)

处理器:Orin nx 双目视觉传感器:ZED2 实时感知建图方法:Vins Fusion Raycast (VIO与射线投影法感知定位加建图方法) 项目地址:https://github.com/ZJU-FAST-Lab/EGO-Planner-v2 【注意】:建…

26-计组-寻址方式

指令寻址与PC自增一、指令寻址方式定义:寻找下一条将要执行的指令地址的过程。 核心部件:程序计数器(PC),用于指示待执行指令的地址。 执行流程:CPU根据PC值从主存取指令。取指后,PC自动自增&am…

生成式对抗网络(GAN)模型原理概述

生成对抗网络(Generative Adversarial Network, GAN)是一种通过对抗训练生成数据的深度学习模型,由生成器(Generator)和判别器(Discriminator)两部分组成,其核心思想源于博弈论中的零…

Vue和Element的使用

文章目录1.vue 脚手架创建步骤2.vue项目开发流程3.vue路由4.Element1.vue 脚手架创建步骤 创建一个文件夹 vue双击进入文件夹,在路径上输入cmd输入vue ui, 目的:调出图形化用户界面点击创建 9. 10.在vscode中打开 主要目录介绍 src目录介绍 vue项目启动 图形化界面中没有npm…

如何设置直播间的观看门槛,让直播间安全有效地运行?

文章目录前言一、直播间观看门槛有哪几种形式?二、设置直播间的观看门槛,对直播的好处是什么三、如何一站式实现上述功能?总结前言 打造一个安全、高效、互动良好的直播间并非易事。面对海量涌入的观众,如何有效识别并阻挡潜在的…

【SkyWalking】配置告警规则并通过 Webhook 推送钉钉通知

🧭 本文为 【SkyWalking 系列】第 3 篇 👉 系列导航:点击跳转 【SkyWalking】配置告警规则并通过 Webhook 推送钉钉通知 简介 介绍 SkyWalking 告警机制、告警规则格式以及如何通过 webhook 方式将告警信息发送到钉钉。 引入 服务响应超时…

关于 验证码系统 详解

验证码系统的目的是:阻止自动化脚本访问网页资源,验证访问者是否为真实人类用户。它通过各种测试(图像、行为、计算等)判断请求是否来自机器人。一、验证码系统的整体架构验证码系统通常由 客户端 服务端 风控模型 数据采集 四…

微服务集成snail-job分布式定时任务系统实践

前言 从事开发工作的同学,应该对定时任务的概念并不陌生,就是我们的系统在运行过程中能够自动执行的一些任务、工作流程,无需人工干预。常见的使用场景包括:数据库的定时备份、文件系统的定时上传云端服务、每天早上的业务报表数…

依赖注入的逻辑基于Java语言

对于一个厨师,要做一道菜。传统的做法是:你需要什么食材,就自己去菜市场买什么。这意味着你必须知道去哪个菜市场、怎么挑选食材、怎么讨价还价等等。你不仅要会做菜,还要会买菜,职责变得复杂了。 而依赖注入就像是有一…

skywalking镜像应用springboot的例子

目录 1、skywalking-ui连接skywalking-oap服务失败问题 2、k8s环境 检查skywalking-oap服务状态 3、本地iidea启动服务连接skywalking oap服务 4、基于apache-skywalking-java-agent-9.4.0.tgz构建skywalking-agent镜像 4.1、Dockerfile内容如下 4.2、AbstractBuilder.M…

3. java 堆和 JVM 内存结构

1. JVM介绍和运行流程-CSDN博客 2. 什么是程序计数器-CSDN博客 3. java 堆和 JVM 内存结构-CSDN博客 4. 虚拟机栈-CSDN博客 5. JVM 的方法区-CSDN博客 6. JVM直接内存-CSDN博客 7. JVM类加载器与双亲委派模型-CSDN博客 8. JVM类装载的执行过程-CSDN博客 9. JVM垃圾回收…

UnityShader——SSAO

目录 1.是什么 2.原理 3.各部分解释 2.1.从屏幕空间到视图空间 2.2.以法线半球为基,获取随机向量 2.3.应用偏移,并将其转换为uv坐标 2.4.获取深度 2.5.比较并计算贡献 2.6.最后计算 4.改进 4.1.平滑过渡 4.2.模糊 5.变量和语句解释 5.1._D…

【设计模式】外观模式(门面模式)

外观模式(Facade Pattern)详解一、外观模式简介 外观模式(Facade Pattern) 是一种 结构型设计模式,它为一个复杂的子系统提供一个统一的高层接口,使得子系统更容易使用。 外观模式又称为门面模式&#xff0…

【6.1.1 漫画分库分表】

漫画分库分表 “数据量大了不可怕,可怕的是不知道如何优雅地拆分。” 🎭 人物介绍 架构师老王:资深数据库架构专家,精通各种分库分表方案Java小明:对分库分表充满疑问的开发者ShardingSphere师傅:Apache S…

Tomcat问题:启动脚本startup.bat中文乱码问题解决

一、问题描述 我们第一次下载或者打开Tomcat时可能在控制台会出现中文乱码问题二、解决办法 我的是8.x版本的tomcat用notepad打开:logging.properties 找到:java.util.logging.ConsoleHandler.encoding设置成GBK,重启tomcat即可

Linux中Gitee的使用

一、Gitee简介:Gitee(码云)是中国的一个代码托管和协作开发平台,类似于GitHub或GitLab,主要面向开发者提供代码管理、项目协作及开源生态服务。适用场景个人开发者:托管私有代码或参与开源项目。中小企业&a…

Oracle大表数据清理优化与注意事项详解

一、性能优化策略 1. 批量处理优化批量大小选择: 小批量(1,000-10,000行):减少UNDO生成,但需要更多提交次数中批量(10,000-100,000行):平衡性能与资源消耗大批量(100,000行):适合高配置环境,但需监控资源使…

Anaconda及Conda介绍及使用

文章目录Anaconda简介为什么选择 Anaconda?Anaconda 安装Win 平台macOS 平台Linux 平台Anaconda 界面使用Conda简介Conda下载安装conda 命令环境管理包管理其他常用命令Jupyter Notebook(可选)Anaconda简介 Anaconda 是一个数据科学和机器学…

外包干了一周,技术明显退步

我是一名本科生,自2019年起,我便在南京某软件公司担任功能测试的工作。这份工作虽然稳定,但日复一日的重复性工作让我逐渐陷入了舒适区,失去了前进的动力。两年的时光匆匆流逝,我却在原地踏步,技术没有丝毫…