关于缓存的理解

news2024/11/14 17:29:44

关于缓存的理解

为系统引入缓存的理由

通常情况,在我们面临系统的基础设施,例如数据库无法处理量级的请求时候,总是会下意识的使用缓存,这次我们以设计的角度思考,在为你的系统引入缓存之前,它是否真的需要缓存呢?

在软件开发中,引入缓存的负面作用要明显大于硬件的缓存。

主要有这样几个原因:

从开发角度来说,引入缓存会提高系统的复杂度,因为你要考虑缓存的失效、更新、一致性等问题(硬件缓存也有这些问题,只是不需要由你来考虑,主流的 ISA 也都没有提供任何直接操作缓存的指令);

从运维角度来说,缓存会掩盖掉一些缺陷,让问题在更久的时间以后,出现在距离发生现场更远的位置上;

从安全角度来说,缓存可能泄漏某些保密数据,这也是容易受到攻击的薄弱点。那么,冒着前面提到的这种种风险,你还是想要给系统引入缓存,是为了什么呢?其实无外乎有两种理由。

第一种,为了缓解 CPU 压力而做缓存。

比如说,把方法运行结果存储起来、把原本要实时计算的内容提前算好、把一些公用的数据进行复用,等等,这些引入缓存的做法,都可以节省 CPU 算力,顺带提升响应性能。

第二种,为了缓解 I/O 压力而做缓存。

比如说,通过引入缓存,把原本对网络、磁盘等较慢介质的读写访问,变为对内存等较快介质的访问;把原本对单点部件(如数据库)的读写访问,变为对可扩缩部件(如缓存中间件)的访问,等等,也顺带提升了响应性能。这里请你注意,缓存虽然是典型的以空间换时间来提升性能的手段,但它的出发点是缓解 CPU 和 I/O 资源在峰值流量下的压力,“顺带”而非“专门”地提升响应性能。

所以我的言外之意就是,如果你可以通过增强 CPU、I/O 本身的性能(比如扩展服务器的数量)来满足需要的话,那升级硬件往往是更好的解决方案。即使需要你掏腰包多花一点儿钱,那通常也比引入缓存带来的风险更低。

缓存属性

其实,不少软件系统最初的缓存功能,都是以 HashMap 或者 ConcurrentHashMap 为起点开始的演进的。当我们在开发中发现,系统中某些资源的构建成本比较高,而这些资源又有被重复使用的可能性,那很自然就会产生“循环再利用”的想法,把它们放到 Map 容器中,下次需要时取出重用,避免重新构建。这种原始朴素的复用就是最基本的缓存了。

不过,一旦我们专门把“缓存”看作是一项技术基础设施,一旦它有了通用、高效、可统计、可管理等方面的需求,那么我们需要考虑的因素就会变得复杂起来了。通常我们在设计或者选择缓存时,至少需要考虑以下四个维度的属性:

  • 吞吐量:缓存的吞吐量使用 OPS 值(每秒操作数,Operations per Second,ops/s)来衡量,它反映了对缓存进行并发读、写操作的效率,即缓存本身的工作效率高低。
  • 命中率:缓存的命中率即成功从缓存中返回结果次数与总请求次数的比值,它反映了引入缓存的价值高低,命中率越低,引入缓存的收益越小,价值越低。
  • 扩展功能:缓存除了基本读写功能外,还提供了一些额外的管理功能,比如最大容量、失效时间、失效事件、命中率统计,等等。
  • 分布式支持:缓存可以分为“进程内缓存”和“分布式缓存”两大类,前者只为节点本身提供服务,无网络访问操作,速度快但缓存的数据不能在各个服务节点中共享。后者则相反。

在我们就先来探讨下前三个属性(下一篇我们会重点讨论分布式缓存)。

吞吐量

缓存的吞吐量只在并发场景中才有统计的意义,因为不考虑并发的话,即使是最原始的、以 HashMap 实现的缓存,访问效率也已经是常量时间复杂度,即 O(1)。

但 HashMap 并不是线程安全的容器,并发场景我们可以改用 ConcurrentHashMap 来实现,这相当于给 Map 的访问分段加锁(从 JDK 8 起已取消分段加锁,改为 CAS+Synchronized 锁单个元素)。

而无论采用怎样的实现方法,线程安全措施都会带来一定的吞吐量损失。

设计缓存的思路是**“尽可能避免数据竞争”是最关键的。因为无论我们如何实现同步,都不会比直接不需要同步更快。**

缓存中最主要的数据竞争来源于读取数据的同时,也会伴随着对数据状态的写入操作,而写入数据的同时,也会伴随着数据状态的读取操作。

针对前面所讲的伴随读写操作而来的状态维护,我们可以选择两种处理思路。

一种是以 Guava Cache 为代表的同步处理机制。即在访问数据时一并完成缓存淘汰、统计、失效等状态变更操作,通过分段加锁等优化手段来尽量减少数据竞争。(关于Guava的操作我之前有博客写过)

另一种是以 Caffeine 为代表的异步日志提交机制。这种机制参考了经典的数据库设计理论,它把对数据的读、写过程看作是日志(即对数据的操作指令)的提交过程。

命中率与淘汰策略

受硬件约束,缓存肯定不可能无限大,这是一种以空间换时间的策略,需要在消耗空间与节约时间之间取得平衡,这就要求缓存必须能够自动、或者由人工淘汰掉缓存中的低价值数据。有以下几种思路。

**第一种:FIFO(First In First Out)**即优先淘汰最早进入被缓存的数据。

FIFO 的实现十分简单,但一般来说,它并不是优秀的淘汰策略,因为越是频繁被用到的数据,往往越会早早地被存入缓存之中。所以如果采用这种淘汰策略,很可能会大幅降低缓存的命中率。

**第二种:LRU(Least Recent Used)**即优先淘汰最久未被使用访问过的数据。LRU 通常会采用 HashMap 加 LinkedList 的双重结构(如 LinkedHashMap)来实现。也就是,它以 HashMap 来提供访问接口,保证常量时间复杂度的读取性能;以 LinkedList 的链表元素顺序来表示数据的时间顺序,在每次缓存命中时,把返回对象调整到 LinkedList 开头,每次缓存淘汰时从链表末端开始清理数据。

**第三种:LFU(Least Frequently Used)**即优先淘汰最不经常使用的数据。LFU 会给每个数据添加一个访问计数器,每访问一次就加 1,当需要淘汰数据的时候,就清理计数器数值最小的那批数据。LFU 可以解决前面 LRU 中,热点数据间隔一段时间不访问就被淘汰的问题,但同时它又引入了两个新的问题。

第一个问题是需要对每个缓存的数据专门去维护一个计数器,每次访问都要更新,在前面讲“吞吐量”的时候,我也解释了这样做会带来高昂的维护开销;

第二个问题是不便于处理随时间变化的热度变化,比如某个曾经频繁访问的数据现在不需要了,它也很难自动被清理出缓存。

针对以上两个问题,学术界提供了 TinyLFU 和 W-TinyLFU 算法,都带来了优化效果。有兴趣可以下去了解一下。

扩展功能

一般来说,一套标准的 Map 接口就可以满足缓存访问的基本需要,不过在“访问”之外,专业的缓存往往还会提供很多额外的功能。

加载器

许多缓存都有“CacheLoader”之类的设计,加载器可以让缓存从只能被动存储外部放入的数据,变为能够主动通过加载器去加载指定 Key 值的数据,加载器也是实现自动刷新功能的基础前提。Guava就用到了这个。

淘汰策略

有的缓存淘汰策略是固定的,也有一些缓存可以支持用户根据自己的需要,来选择不同的淘汰策略。

失效策略

失效策略就是要求缓存的数据在一定时间后自动失效(移除出缓存)或者自动刷新(使用加载器重新加载)。

事件通知

缓存可能会提供一些事件监听器,让你在数据状态变动(如失效、刷新、移除)时进行一些额外操作。有的缓存还提供了对缓存数据本身的监视能力(Watch 功能)。

并发级别

对于通过分段加锁来实现的缓存(以 Guava Cache 为代表),往往会提供并发级别的设置。这里你可以简单地理解为,缓存内部是使用多个 Map 来分段存储数据的,并发级别就用于计算出使用 Map 的数量。如果这个参数设置过大,会引入更多的 Map,你需要额外维护这些 Map 而导致更大的时间和空间上的开销;而如果设置过小,又会导致在访问时产生线程阻塞,因为多个线程更新同一个 ConcurrentMap 的同一个值时会产生锁竞争。

容量控制

缓存通常都支持指定初始容量和最大容量。设定初始容量的目的是减少扩容频率,这与 Map 接口本身的初始容量含义是一致的;而最大容量类似于控制 Java 堆的 -Xmx 参数,当缓存接近最大容量时,会自动清理掉低价值的数据。

引用方式

Java 语言支持将数据设置为软引用或者弱引用,而提供引用方式的设置,就是为了将缓存与 Java 虚拟机的垃圾收集机制联系起来。

统计信息

缓存框架会提供诸如缓存命中率、平均加载时间、自动回收计数等统计信息。

持久化

也就是支持将缓存的内容存储到数据库或者磁盘中。进程内缓存提供持久化功能的作用不是太大,但分布式缓存大多都会考虑提供持久化功能。

小结

2023-3-5

表格里的四类就基本囊括了目前主流的进程内缓存方案。希望通过这节课的学习,你能够掌握服务端缓存的原理,能够独立分析各种缓存框架所提供的功能属性,明白它们有什么影响,有什么收益和代价。

简单说一下使用缓存弊端,也欢迎大家互动补充。

1、空间换时间,无限制的缓存策略可能导致内存溢出
2、缓存的预加载、过期失效、击穿、穿透、雪崩等问题
3、既然是缓存,必然是有时效性的问题,就会出现不一致的问题

4、增加系统复杂度,耦合代码,出问题难以排查。

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

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

相关文章

Windows下nvm的安装配置及使用

目录 一:nvm简介 二:nvm下载及安装 三:nvm配置镜像 四:nvm的基本使用 五:nvm的一些常用命令 一:nvm简介 nvm 全名叫做 nodejs version manage,是一个非常棒的nodejs的版本管理工具&#x…

Mp4屏录文件无法播放的修复方法

屏录文件算是比较特殊的一类文件,原因是其采集范围仅限于桌面,和我们现实的摄像机采集相比,桌面类的更单一,所以能实现较小的长度存放较多的帧。下面我来看一个屏录文件损坏后的修复案例,同时讲下CHS零壹视频修复程序Q…

GraphCut、最大流最小割定理

G(V,E);V为点集,E为边集; 节点集V中的节点分为: (1)终端节点。不包含图像像素,用S和T表示。S为源点,T为汇点。图像分割中通常用S表示前景目标&a…

sql开窗函数

用的Oracle数据库进行测试一、数据准备DROP TABLE T_TEST; CREATE TABLE T_TEST (id NUMBER(10) VISIBLE NOT NULL ,姓名 VARCHAR2(50 BYTE) VISIBLE ,性别 VARCHAR2(50 BYTE) VISIBLE ,班级 VARCHAR2(50 BYTE) VISIBLE ,成绩 NUMBER(5,2) VISIBLE );INSERT INTO T_TEST VALUE…

【蓝桥杯专题】 递归 递推 (C++ | 洛谷 | acwing)

文章目录【蓝桥杯专题】 递归 &递推 (C | 洛谷 | acwing)复习P5534 【XR-3】等差数列P4994 终于结束的起点P1028 [NOIP2001 普及组] 数的计算波动数列[递归]母牛的故事蓝桥杯:耐摔指数菜狗现在才开始备战蓝桥杯QAQ 【蓝桥杯专题】 递归 &…

8 神经网络及Python实现

1 人工神经网络的历史 1.1 生物模型 1943年,心理学家W.S.McCulloch和数理逻辑学家W.Pitts基于神经元的生理特征,建立了单个神经元的数学模型(MP模型)。 1.2 数学模型 ykφ(∑i1mωkixibk)φ(WkTXb)y_{k}\varphi\left(\sum_{i1…

基于龙芯+国产FPGA 的VPX以太网交换板设计(三)

调试与测试是本系统设计实现的重要环节。单板调试主要包括各单元电路和接口 的调试,主要通过查看信号波形和运行软件对每个功能进行测试。本章将设计一系列 的调试和测试方案来验证电路设计的正确性。 6.1 电路板静态检查 经过原理图设计、印制板设计、制造、印制板…

HTML5智慧渔业WebGL可视化云平台

中国作为全球第一大水产养殖大国,未来中国水产养殖的出路在哪里?智慧渔业到底能起到多大的作用?在未来它能为我国水产养殖做出什么深刻的变化吗?今天给大家分享一个基于 数维图 的 Sovit3D可视化编辑器 构建的水产养殖3D可视化场景案例——智慧渔业可视化管理系统…

Hadoop框架:MapReduce基本原理和入门案例

Hadoop MapReduce是一种用于处理大数据的编程模型。它将数据集切分成多个小任务,每一个小任务都可以通过独立的计算来完成,最终的结果可以通过合并或更新数据来进行聚合。Hadoop MapReduce极大地简化了处理大数据的过程,因为它可以同时进行多…

锁屏面试题百日百刷-Hive篇(十一)

锁屏面试题百日百刷,每个工作日坚持更新面试题。锁屏面试题app、小程序现已上线,官网地址:https://www.demosoftware.cn。已收录了每日更新的面试题的所有内容,还包含特色的解锁屏幕复习面试题、每日编程题目邮件推送等功能。让你…

大坝安全监测和水雨情测报系统-智慧水利

政策背景2021年3月23日《国务院办公厅关于切实加强水库除险加固和运行管护工作的通知》(国办发〔2021〕8号)和2021年9月22日国务院常务会议均明确要求,加快推进水库除险加固,加强雨水情和安全监测预警设施建设,健全常态…

23种Java设计模式

目录 🧡 Java 设计模式 六大原则 创建型模式 工厂模式 (Factory Pattern) 抽象工厂模式 (Abstract Factory Pattern) 单例模式 (Singleton Pattern) 建造者模式 (BuilderPattern) 原型模式 (Prototype Pattern) 结构型模式 适配器模式 (Adapter Pattern) …

vmware 虚拟机创建 LVM

LVM 原理 LVM (Logical volume Manager): 虚拟设备驱动,是在内核中块设备和物理设备之间添加的一个新的抽象层次, LVM 可以弹性的调整 文件系统的容量 LVM的实现原理:LVM 将几个实体的 partitions/disk 通过软件组合成一块独立的大磁盘VG,之…

中职网络空间安全B-windows渗透

Windows渗透 目录 Windows渗透 要点 cev2017-7269 ms14-064 pr.exe 提权 3389.bat 打开连接 破解hash 总体是众多小点的结合 1.通过本地pc中的渗透平台kali对服务器场景进行服务及版本扫描渗透测试,并将该操作显示结果中445端口对应的服务版本信息字符串作为fla…

Django实践-03模型-02基于admin管理表

文章目录Django实践-03模型利用Django后台管理模型1. 将admin应用所需的表迁移到数据库中。2. 创建访问admin应用的超级用户账号,3. 运行项目4.注册模型类5.对模型进行CRUD操作。6.实现学科页和老师页效果1. 修改polls/views.py文件。2.修改templates/polls/subject…

THUPC-2023 游记

清华校赛,战火重燃 原文链接 宣传图 上周四同学在洛谷无意间看到了宣传图,当时很有感触。不知觉间,又是一年春,又是一场触动心弦的 THUPC 了。 周五的团建过于有趣,致使我完全将 THUPC 抛之脑后了。 周日上午被省选…

原型链(回顾)

概念prototype__proto__原型链查找机制万物皆对象判断私有/共有属性方法Object.prototype.prototype nullObject.create(proto, [propertiesObject])给类的原型上扩展属性方法的4种方法Fn.prototype.xxx xxxObject.prototype.xxx xxxf1.proto.xxx xxx原型重定向 概念 原型…

虚拟相机 Cinemachine Virtual Camera

一.简介 本质上,虚拟相机应该是相机行为的配置文件,虚拟相机之间的切换实际上就是在进行相机行为之间的切换; 虚拟相机并不会创建任何摄像机,他只会创建虚拟节点,实际上操作的是Cinemachine Brain 虚拟相机属性设置完毕后,应尽量避免在游戏中对齐进行修改, 如有需要可以多创建…

RocketMQ-03

1. 高级功能 1.1 消息存储 分布式队列因为有高可靠性的要求,所以数据要进行持久化存储。 消息生成者发送消息MQ收到消息,将消息进行持久化,在存储中新增一条记录返回ACK给生产者MQ push 消息给对应的消费者,然后等待消费者返回A…

Ubuntu 搭建文件服务器(Nginx)

1,下载Nginx 2,安装Nginx 3,Nginx指令及脚本使用 4,配置Nginx 1,下载Nginx ①去官网下载对应的Nginx版本 nginx: download ②直接在ubuntu使用指令下载 wget http://nginx.org/download/nginx-1.23.3.tar.gz 2…