缓存的使用

文章目录

  • 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.mfbz.cn/a/453964.html

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

相关文章

React 的入门介绍

React 是什么 React是一个用于构建用户界面的JavaScript库。它由Facebook开发,并于2013年首次发布。React将用户界面拆分为小的可重用组件,每个组件都有自己的状态,并根据状态的变化来更新界面。 React使用了虚拟DOM(Virtual DO…

人工智能迷惑行为大赏——需求与科技的较量

目录 前言 一、 机器行为学 二、人工智能迷惑行为的现象 三、产生迷惑行为的技术原因 四、社会影响分析 五、解决措施 总结 前言 随着ChatGPT热度的攀升,越来越多的公司也相继推出了自己的AI大模型,如文心一言、通义千问等。各大应用也开始内置…

Python 数据持久层ORM框架 SQLAlchemy模块

文章目录 ORM 框架SQLAlchemy 简介SQLAlchemy 作用SQLAlchemy 原理SQLAlchemy 使用流程数据库驱动配置关系型数据库配置NoSQL数据库配置 创建引擎(Engine)定义模型类(ORM)创建会话(Session)创建数据库表其他关键字参数,这些参数将传递给底层的 DDL 创建函数。 删除数…

cmake的一个简单通用模板

文件的tree结构如下所示 在build目录下执行cmake: cmake ../ //生成makefile make //编译 若想清理cmake产生的文件,直接删掉build rm -r build 顶层 CMakeLists.txt cmake_minimum_required(VERSION 3.5) // 限制版本号,因…

nginx实时流量拷贝ngx_http_mirror_module

参考: Module ngx_http_mirror_module Nginx流量拷贝ngx_http_mirror_module模块使用方法详解 ngx_http_mirror_module用于实时流量拷贝 请求一个接口,想实时拷贝这个请求转发到自己的服务上,可以使用ngx_http_mirror_module模块。 官网好像…

【Python】科研代码学习:十一 Optimization (Optimizer, Scheduler)

【Python】科研代码学习:十一 Optimization [Optimizer, Schedule] Optimizer 前置知识优化器在 torch 中的调用优化器在 transformers 中的调用AdamW 优化器 Scheduler 前置知识调度器在 transformers 中的调用 Optimizer 前置知识 什么是 Optimizer 优化器&#…

动态住宅代理IP使用教程全面讲解

在数字化时代,隐私保护和信息安全成为全球网民的共同关切。特别是对于海外用户,由于地理位置和网络监管政策的不同,访问全球信息资源变得更加复杂。使用动态住宅IP搭建代理,作为解决这一问题的有效手段,动态IP代理通过…

漏洞原理 | CORS跨域学习篇

0x01:原理 1、 什么是CORS 全称跨域资源共享,用来绕过SOP(同源策略)来实现跨域访问的一种技术。 CORS漏洞利用CORS技术窃取用户敏感信息 2、 同源策略简介 同源策略是浏览器最核心也是最基本的安全功能,不同源的客户端脚本在没有明确授权…

智慧城市与绿色出行:共同迈向低碳未来

随着城市化进程的加速,交通拥堵、空气污染、能源消耗等问题日益凸显,智慧城市与绿色出行成为了解决这些问题的关键途径。智慧城市利用信息技术手段,实现城市各领域的智能化管理和服务,而绿色出行则强调低碳、环保的出行方式&#…

2.2 评估方法 机器学习

我们若有一个包含m个样例的数据集,若我们既需要训练,也需要测试,我们该如何处理呢?下面是几种方法: 2.2.1 留出法 “留出法”直接将数据集D划分为两个互斥的集合,其中一个作为训练集S,另一个作…

OpenGL-贴纸方案

OpenGL-贴纸方案 普通贴纸(缩放、Z轴旋转、平移) OpenGL环境说明 OpenGL渲染区域使用正交投影换算,正常OpenGL坐标是vertexData,这样的 Matrix.orthoM 进行换算 //顶点坐标(原点为显示区域中心店)private final float[] vertex…

软件应用实例,宠物医院处方笺模板实例,佳易王兽医电子处方开单管理系统软件操作教程

软件应用实例,宠物医院处方笺模板实例,佳易王兽医电子处方开单管理系统软件操作教程 一、前言 以下软件程序操作教程以佳易王兽医电子处方软件V17.0为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 1、兽医宠物店开电子处…

React进阶(Redux,RTK,dispatch,devtools)

1、初识Redux 是React最常用的集中状态管理工具,类似于Vue中的Pinia(Vuex),可以独立于框架运行 作用:通过集中管理的方式管理应用的状态 案例-实现一个计数器 实现步骤: Redux管理数据的流程: state:一个对象&…

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

原创作者:田超凡(程序员田宝宝) 版权所有,引用请注明原作者,严禁复制转载

华为数通方向HCIP-DataCom H12-821题库(多选题:161-180)

第161题 以下关于IPv6优势的描述,正确的是哪些项? A、底层自身携带安全特性 B、加入了对自动配置地址的支持,能够无状态自动配置地址 C、路由表相比IPv4会更大,寻址更加精确 D、头部格式灵活,具有多个扩展头 【参考答案】ABD 【答案解析】 第162题 在OSPF视图下使用Filt…

WPF —— Calendar日历控件详解

1: Calendar的简介 日历控件用于创建可视日历,让用户选择日期并在选择日期时触发事件。 DisplayMode 用来调整日历显示模式,分为Month、Year 和Decade 三种。如下是None 2:Calendar控件常用的属性 SelectionMode 选中日历的类…

nginx学习记录-nginx初步配置

1. 虚拟机安装系统并配置网络 系统网上找个能用的镜像就行,我用的是阿里的镜像,地址是centos安装包下载_开源镜像站-阿里云 (aliyun.com) 以下是我本地的虚拟机配置 配置过程中按照提示操作系统即可。 安装好系统后,配置centos的ip&#x…

Ribbon简单使用

Ribbon是Netflix发布的云中间层服务开源项目,其主要功能是提供客户端实现负载均衡算法。Ribbon客户端组件提供一系列完善的配置项如连接超时,重试等。简单的说,Ribbon是一个客户端负载均衡器,我们可以在配置文件中Load Balancer后…

LiveGBS流媒体服务器中海康摄像头GB28181公网语音对讲、语音喊话的配置

LiveGBS海康摄像头国标语音对讲大华摄像头国标语音对讲GB28181语音对讲需要的设备及服务准备 1、背景2、准备2.1、服务端必备条件(注意)2.2、准备语音对讲设备2.2.1、不支持跨网对讲示例2.2.2、 支持跨网对讲示例 3、开启音频开始对讲4、搭建GB28181视频…

C++提高笔记(三)---STL容器(vector、deque)

1、vector容器 1.1vector基本概念 功能:vector数据结构和数组非常相似,也称为单端数组 vector与普通数组区别:不同之处在于数组是静态空间,而vector可以动态扩展 动态扩展:并不是在原空间之后续接新空间&#xff0…