缓存的使用

news2025/1/24 22:28:14

文章目录

  • 1.为什么要有缓存?
  • 2.缓存使用场景
  • 3.缓存分类
  • 4.缓存使用模式
  • 5.淘汰策略
  • 6.缓存的崩溃与修复
  • 7.缓存最佳实践
  • 参考文献

1.为什么要有缓存?

数据访问具有局部性,符合二八定律:80% 的数据访问集中在 20% 的数据上,这部分数据也被称为热点数据。

不同层级的存储访问速率不同,内存读写速度快于磁盘,磁盘快于远端存储。基于内存的存储系统(如 Redis)高于基于磁盘的存储系统(如 MySQL)。

因为存在热点数据和存储访问速率的不同,我们可以考虑采用缓存。

缓存缓存一般使用内存作为本地缓存。

必要情况下,可以考虑多级缓存,如一级缓存采用本地缓存,二级缓存采用基于内存的存储系统(如 Redis、Memcache 等)。

缓存是原始数据的一个拷贝,其本质是空间换时间,主要为了解决高并发读。

2.缓存使用场景

缓存是空间换时间的艺术,使用缓存能提高系统的性能。“劲酒虽好,不要贪杯”,使用缓存的目的是为了提高性价比,而不是一上来就为了所谓的提高性能不计成本的使用缓存,而是要看场景。

适合使用缓存的场景,以之前参与的项目企鹅电竞为例:

(1)一旦生成后基本不会变化的数据:如企鹅电竞的游戏列表,在后台创建一个游戏之后基本很少变化,可直接缓存整个游戏列表;

(2)读密集型或存在热点的数据:典型的就是各种 App 的首页,如企鹅电竞首页直播列表;

(3)计算代价大的数据:如企鹅电竞的 Top 热榜视频,如 7 天榜在每天凌晨根据各种指标计算好之后缓存排序列表;

(4)千人一面的数据:同样是企鹅电竞的 Top 热榜视频,除了缓存的整个排序列表,同时直接在进程内按页缓存了前 N 页数据组装后的最终回包结果;

不适合使用缓存的场景:

(1)写多读少,更新频繁。

(2)对数据一致性要求严格。

3.缓存分类

(1)进程缓存

数据直接缓存在进程地址空间内,这可能是访问速度最快使用最简单的缓存方式了。主要缺点是受制于进程空间大小,能缓存的数据量有限,进程重启缓存数据会丢失。一般通常用于缓存数据量不大的场景。

(2)集中式缓存

缓存的数据集中在一台机器上,如共享内存。这类缓存容量主要受制于机器内存大小,而且进程重启后数据不丢失。常用的集中式缓存中间件有单机版 Redis、Memcache 等。

(3)分布式缓存

缓存的数据分布在多台机器上,通常需要采用特定算法(如 Hash)进行数据分片,将海量的缓存数据均匀的分布在每个机器节点上。常用的组件有:Memcache(客户端分片)、Codis(代理分片)、Redis Cluster(集群分片)。

(4)多级缓存

指在系统中的不同层级缓存数据,以提高访问效率和减少对后端存储系统的冲击。

4.缓存使用模式

关于缓存的使用,已经有人总结出了一些模式,主要分为 Cache-Aside 和 Cache-As-SoR 两类。其中 SoR(System-of-Record)表示记录系统,即数据源,而 Cache 正是 SoR 的拷贝。

  • Cache-Aside:旁路缓存

这应该是最常见的缓存模式了。对于读,首先从缓存读取数据,如果没有命中则回 SoR 读取并更新缓存。对于写操作,先写 SoR,再写缓存。

这种模式用起来简单,但对应用层不透明,需要业务代码完成读写逻辑。同时对于写来说,写数据源和写缓存不是一个原子操作,可能出现以下情况导致两者数据不一致。

(1)在并发写时,可能出现数据不一致。

如下图所示,user1 和 user2 几乎同时进行读写。在 t1 时刻 user1 写 db,t2 时刻 user2 写 db,紧接着在 t3 时刻 user2 写缓存,t4 时刻 user1 写缓存。这种情况导致 db 是 user2 的数据,缓存是 user1 的数据,出现数据不一致。

(2)先写数据源成功,但是接着写缓存失败,两者数据不一致。

对于这两种情况如果业务不能忍受,可简单的通过先 delete 缓存然后再写 db 解决,其代价就是下一次读请求的 cache miss。

  • Cache-as-SoR:缓存即数据源

该模式把 Cache 当作 SoR,所以读写操作都是针对 Cache,然后 Cache 再将读写操作委托给 SoR,即 Cache 是一个代理。

有三种实现方式:

(1)Read-Through:穿透读模式。首先查询 Cache,如果不命中则再由 Cache 回源到 SoR 查询,而不是业务去数据源查询。

(2)Write-Through:穿透写模式。由业务先调用写操作,然后由 Cache 负责写缓存和 SoR。

(3)Write-Behind:回写模式。发生写操作时业务只更新缓存并立即返回,然后由缓存异步写 SoR,这样可以利用合并写/批量写提高性能。

5.淘汰策略

在空间有限、低频访问或无主动更新通知的情况下,需要对缓存数据进行淘汰。常用的淘汰策略有以下几种:

(1)基于时间。

  • TTL(Time To Live)存活时间。

从缓存数据创建开始到指定的过期时间段,不管有没有被访问缓存都会过期。如 Redis 的 EXPIRE。

  • TTI(Time To Idle)空闲时间。

缓存在指定的时间没有被访问将会被回收。

  • LRU(Least Recently Used)最久未使用。

LRU 基于访问时间,淘汰最长时间未被使用的数据。基于时间局部性原理,即如果数据最近被使用,那么它在未来也极有可能被使用。反之,如果数据很久未使用,那么未来被使用的概率较低。

缺点是可能会由于一次冷数据的批量查询而误淘汰大量热点数据。

LRU 缓存一般采用哈希表(hash map)和双向链表(doubly linked list)来实现。

访问数据时,直接从哈希表通过 key 在 O(1) 时间内获取到所需数据。当缓存命中时,将数据移动到链表头部;有新数据时,插入到链表的头部;当缓存满时将链表尾部的数据丢弃。

在这里插入图片描述

(2)基于次数。

LFU(Least Frequently Used):最少使用。统计每个对象的使用次数,当需要淘汰时,选择被使用次数最少的淘汰。

基本思想:如果数据过去被访问多次,那么将来被访问的频率也更高。

注意 LFU 和 LRU 的区别,LRU 的淘汰规则是基于访问时间,而 LFU 是基于访问次数。

LFU 相对于 LRU,在应对周期性或者偶发性的冷数据批量查询,适应性较好,不会淘汰大量热点数据,导致缓存命中率下降。但LFU需要记录数据的历史访问记录,一旦数据访问模式改变,LFU需要更长时间来适用新的访问模式,即LFU存在历史数据影响将来数据的"缓存污染"问题。

LFU 缓存同样可以采用哈希表(hash map)和双向链表(doubly linked list)来实现。

双向链表来维护访问次数和时间先后顺序。当数据被访问时,那么访问次数加1,并将数据沿着链表往前移,直到前一个数据访问次数大于当前数据。

(3)基于顺序。

FIFO(First In First Out):先进选出原则,先进入缓存的数据先被移除。

FIFO 策略通常基于一个队列来实现。当缓存空间不足时,新的数据项被添加到队列的末尾,而最早添加到队列的数据项会被移除。

(4)基于大小。

基于大小的淘汰(Size-based Eviction)基于缓存中存储项目的大小来选择要移除的项目。在这种策略中,通常会选择以下几种方式来确定要移除的项目:

  • 移除占用空间最大的项目:当缓存空间不足时,选择占用空间最大的项目进行淘汰。这样可以最大程度地释放缓存空间,以容纳更多的新数据。
  • 移除占用空间最小的项目:当缓存空间不足时,选择占用空间最小的项目进行淘汰。这样可以尽量减少淘汰的影响,同时保留更多的缓存数据。
  • 按照空间大小的优先级进行淘汰:将缓存中的项目按照大小进行排序,然后依次选择要移除的项目,直到腾出足够的空间。这样可以在保证缓存空间有效利用的同时,尽量减少淘汰带来的影响。

Size-based Eviction 策略通常适用于对缓存空间有严格限制的场景,可以根据缓存空间的大小和数据的大小来灵活选择要淘汰的项目,以实现最佳的缓存效果。

6.缓存的崩溃与修复

由于在设计不足、请求攻击(并不一定是恶意攻击)等会造成一些缓存问题,下面列出了常见的缓存问题和解决方案。

  • 缓存穿透

大量使用不存在的 Key 进行查询时,缓存没有命中,这些请求都穿透到后端的存储,最终导致后端存储压力过大甚至被压垮。这种情况原因一般是存储中数据不存在,主要有三个解决办法。

(1)设置空置或默认值:如果存储中没有数据,则设置一个空置或者默认值缓存起来,这样下次请求时就不会穿透到后端存储。但这种情况如果遇到恶意攻击,不断的伪造不同的 Key 来查询时并不能很好的应对,这时候需要引入一些安全策略对请求进行过滤。

(2)布隆过滤器:采用布隆过滤器将,将所有可能存在的数据哈希到一个足够大的 Bitmap 中,一个一定不存在的数据会被这个 Bitmap 拦截掉,从而避免了对底层数据库的查询压力。

(3)singleflight:多个并发请求对一个失效的 Key 进行源数据获取时,只让其中一个得到执行,其余阻塞等待到执行的那个请求完成后,将结果传递给阻塞的其他请求达到防止击穿的效果。

  • 缓存雪崩

指大量的缓存在某一段时间内集体失效,导致后端存储负载瞬间升高甚至被压垮。通常是以下原因造成:

(1)缓存失效时间集中在某段时间,对于这种情况可以采取对不同的 Key 使用不同的过期时间,在原来基础失效时间的基础上再加上不同的随机时间;

(2)采用取模机制的某缓存实例宕机,这种情况移除故障实例后会导致大量的缓存不命中。有两种解决方案:
(a)采取主从备份,主节点故障时直接将从实例替换主。
(b)使用一致性哈希替代取模,这样即使有实例崩溃也只是少部分缓存不命中。

  • 缓存热点

虽然缓存系统本身性能很高,但也架不住某些热点数据的高并发访问从而造成缓存服务本身过载。假设一下微博以用户 ID 作为哈希 Key,突然有一天亦菲姐姐宣布婚了,如果她的微博内容按照用户 ID 缓存在某个节点上,当她的万千粉丝查看她的微博时必然会压垮这个缓存节点,因为这个 Key 太热了。这种情况可以通过生成多份缓存到不同节点上,每份缓存的内容一样,减轻单个节点的访问压力。

7.缓存最佳实践

  • 动静分离

对于一个缓存对象,可能分为很多种属性,这些属性中有的是静态的,有的是动态的。在缓存的时候最好采用动静分离的方式。以免因经常变动的数据发生更新而要把经常不变的数据也更新至缓存,成本很高。

  • 慎用大对象

如果缓存对象过大,每次读写开销非常大并且可能会卡住其他请求,特别是在redis这种单线程的架构中。典型的情况是将一堆列表挂在某个 value 的字段上或者存储一个没有边界的列表,这种情况下需要重新设计数据结构或者分割 value 再由客户端聚合。

  • 过期设置

尽量设置过期时间减少脏数据和存储占用,但要注意过期时间不能集中在某个时间段。

  • 超时设置

缓存作为加速数据访问的手段,通常需要设置超时时间而且超时时间不能过长(如100ms左右),否则会导致整个请求超时连回源访问的机会都没有。

  • 缓存隔离

首先,不同的业务使用不同的 Key,防止出现冲突或者互相覆盖。其次,核心和非核心业务进行通过不同的缓存实例进行物理上的隔离。

  • 失败降级

使用缓存需要有一定的降级预案,缓存通常不是关键逻辑,特别是对于核心服务,如果缓存部分失效或者失败,应该继续回源处理,不应该直接中断返回。

  • 容量控制

使用缓存要进行容量控制,特别是本地缓存,缓存数量太多内存紧张时会频繁的swap存储空间或GC操作,从而降低响应速度。

  • 业务导向

以业务为导向,不要为了缓存而缓存。对性能要求不高或请求量不大,分布式缓存甚至数据库都足以应对时,就不需要增加本地缓存,否则可能因为引入数据节点复制和幂等处理逻辑反而得不偿失。

  • 监控告警

对大对象、慢查询、内存占用等进行监控,做到缓存可观测,用得放心。


参考文献

Cache replacement policies - wikipedia

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

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

相关文章

Nuxt3: useFetch使用过程常见一种报错

一、问题描述 先看一段代码&#xff1a; <script setup> const fetchData async () > {const { data, error } await useFetch(https://api.publicapis.org/entries);const { data: data2, error: error2 } await useFetch(https://api.publicapis.org/entries);…

展会邀约 | 加速科技将携重磅产品亮相SEMICON China 2024

SEMICON China 2024将于3月20日-3月22日在上海新国际博览中心隆重举行。展会期间&#xff0c;加速科技将携重磅产品高性能数模混合信号测试机ST2500EX、LCD Driver测试机Flex10K-L、高密度数模混合信号测试系统ST2500E、高性能数模混合信号测试系统ST2500A亮相此次行业盛会&…

[Java、Android面试]_02_HashMap的原理

本人今年参加了很多面试&#xff0c;也有幸拿到了一些大厂的offer&#xff0c;整理了众多面试资料&#xff0c;后续还会分享众多面试资料&#xff0c;感兴趣的朋友可收藏关注。由于时间有限&#xff0c;只能每天整理一点&#xff0c;分享一点儿&#xff01; 现分享如下&#xf…

vue3/vue2若依框架对比,点击新增编辑跳转到新页面(新增编辑共用代码)

vue2若依框架&#xff1a; router里面定义好&#xff0c;编辑里面添加一个id {path: /filmManagement,component: Layout,hidden: true,redirect: noredirect,children: [{path: editFilmDetail,component: () > import(/views/filmManagement/editFilmDetail),name: editFi…

【Mac】鼠标控制\移动\调整窗口大小BBT|边缘触发调整音量\切换桌面

一直在 win 习惯了通过鼠标的侧键来控制窗口的位置、大小&#xff0c;现在找到心的解决方案了&#xff0c;通过 BBT 设置侧键按下\抬起几颗。 以下解决方案的截图&#xff0c;其中还包括了其他操作优化方案&#xff1b; 滚轮配合 cmd 键调节页面大小&#xff1b;配合 option 键…

探索C++中的动态数组:实现自己的Vector容器

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…

算法思想总结:双指针算法

一、移动零 . - 力扣&#xff08;LeetCode&#xff09; 移动零 该题重要信息&#xff1a;1、保持非0元素的相对位置。2、原地对数组进行操作 思路&#xff1a;双指针算法 class Solution { public:void moveZeroes(vector<int>& nums){int nnums.size();for(int cur…

Elasticsearch:在本地使用 Gemma LLM 对私人数据进行问答

在本笔记本中&#xff0c;我们的目标是利用 Google 的 Gemma 模型开发 RAG 系统。 我们将使用 Elastic 的 ELSER 模型生成向量并将其存储在 Elasticsearch 中。 此外&#xff0c;我们将探索语义检索技术&#xff0c;并将最热门的搜索结果作为 Gemma 模型的上下文窗口呈现。 此外…

人工智能迷惑行为大赏!

目录 人工智能迷惑行为大赏 一&#xff1a;人工智能的“幽默”瞬间 1. 图像识别出现AI的极限 2. 小批量梯度下降优化器 3. 智能聊天机器人的冰雹问题 4. 大语言模型-3经典语录 二&#xff1a;技术原理探究 1. 深度学习 2. 机器学习 3. 自然语言处理 4. 计算机视觉 三…

java八股文 笔记(持续更新中~)

1 Redis 2Mysql 3JVM 4java基础底层 5 spring 6 微服务 7.......(持续更新) One:Redis篇 1.穿透 2&#xff1a;击穿 3&#xff1a;雪崩 3 33 4:双写一致 5.持久化 2 JVM: 2&#xff1a; 3&#xff1a; 4&#xff1a; 5&#xff1a; 6&#xff1a; 7&#xff…

学生时期学习资源同步-1 第一学期结业考试题1

原创作者&#xff1a;田超凡&#xff08;程序员田宝宝&#xff09; 版权所有&#xff0c;引用请注明原作者&#xff0c;严禁复制转载

深度学习--离线数据增强

最近做项目遇见数据集背景非常单一&#xff0c;为了增加模型的返回能里&#xff0c;只能自己做一些数据增强来增加背景的多样性。代码如下&#xff1a; import numpy as np import cv2def create_mask(box, height, width):"""创建一个全零的掩码图像&#xff…

Prompt进阶2:LangGPT(构建高性能Prompt策略和技巧)--最佳实践指南

Prompt进阶2:LangGPT(构建高性能Prompt策略和技巧)–最佳实践指南 0.前言 左图右图 prompt 基本是一样的&#xff0c;差别只在提示工程这个词是否用中英文表达。我们看到&#xff0c;一词之差&#xff0c;回答质量天壤之别。为了获得理想的模型结果&#xff0c;我们需要调整设…

uniapp开发DAPP钱包应用(二) Vue + Java

上一节我们讲了如何通过vue uniapp还有web3以及需要准备的相关组件&#xff0c;来搭建了DAPP开发的环境。 这一节&#xff0c;我们来说说如何用代码来实现DAPP相关接口。 1. ethers实现类 导入组件 import { ethers , providers , utils } from "ethers"; impor…

跟着GPT学设计模式之桥接模式

说明 桥接模式&#xff0c;也叫作桥梁模式&#xff0c;英文是 Bridge Design Pattern。在 GoF 的《设计模式》一书中&#xff0c;桥接模式是这么定义的&#xff1a;“Decouple an abstraction from its implementation so that the two can vary independently。”翻译成中文就…

我真是服了!你们刚开始学习的时候也是造火箭吗?能不能有一个简单的纯纯纯html模板给我学学,真的看不懂好嘛!

做一个个人博客第一步该怎么做&#xff1f; 好多零基础的同学们不知道怎么迈出第一步。 那么&#xff0c;就找一个现成的模板学一学呗&#xff0c;毕竟我们是高贵的Ctrl c v 工程师。 但是这样也有个问题&#xff0c;那就是&#xff0c;那些模板都&#xff0c;太&#xff01;…

弧形导轨的设计要求

制造业设备种类越来越多&#xff0c;非标自动化设备渐渐成了主力市场&#xff0c;其中弧形导轨线体作为非标自动化运输中的基石&#xff0c;承担了运输&#xff0c;定位&#xff0c;特殊工位组装&#xff0c;其设计要求也非常严格。 1、精度要求&#xff1a;弧形导轨需要具备高…

大数据 - HBase《一》- Hbase基本概念

目录 1.1. Hbase简介 1.2 Hbase,Hive, Mysql对比 1.3 Hbase数据模型 &#x1f959;region(区域) &#x1f959;rowkey(行键) &#x1f959;列族&#xff08;column family) &#x1f959;列&#xff08;column Qualifier) &#x1f959;版本&#xff08;version)-默认按…

如何仅用3行代码,搞定业务敏感数据加解密?

01 引子&#xff1a;一个数据安全的故事 一个风和日丽的早上&#xff0c;某家快递物流公司内。 &#xfeff;张老板看着电脑屏幕&#xff0c;眉头紧锁。电脑屏幕上赫然写着&#xff0c;“疑似45亿条个人信息泄露&#xff0c;电商物流行业数据安全警铃再响”。据传&#xff0c;…

代码学习记录18

随想录日记part18 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.03.13 主要内容&#xff1a;今天的主要内容是二叉树的第七部分&#xff0c;主要涉及二叉搜索树的最近公共祖先 &#xff1b;二叉搜索树的最近公共祖先&#xff1b;删除二叉搜索树中的节点 。 23…