缓存介绍

news/2024/9/21 12:36:13/文章来源:https://www.cnblogs.com/joey-wang/p/18385678

从业务层面上的堆数据库下性能瓶颈的解决方案:

分库分表、读写分离

程序员修神之路--略懂数据库集群读写分离而已


缓存

缓存 (Cache):本质是数据交换的一段缓冲区,也可以称为一种存储数据的组件,主要用于减小数据交换双方速度不匹配的问题。

缓存在计算机世界里是一个常见并且不可忽视的一个重要因素,几乎遍布于各个领域。例如 CPU 的一级缓存,二级缓存;浏览器的缓存等。使用缓存时要认识到缓存的数据具有有效期,也就是说可能随时会消失。虽然类似 radis 这些组件都提供数据持久化的功能,这样数据就不会消失。但还要考虑两点:

  • 当组件提供持久化功能时,必然会发生磁盘的 IO 操作,而磁盘 IO 的操作必然会大大降低缓存组件的性能,那缓存的价值还有吗?
  • 缓存的数据在时间定义上是一种临时性的数据,如果做了持久化,这种临时性的意义就不存在了,而且还占用了磁盘的存储空间

缓存的常见存储介质为内存,但这并不意味只有内存可以存储缓存数据。缓存的作用是提供高速的读写功能,所以如果设备足够快,理论上都可以作为缓存使用,比如现在的 SSD,在一些性能不太严格和敏感的场景下就可以作为存储缓存数据的介质。

缓存应用场景

理论上,任何需要提高访问速度的环节都可以加入缓存。但系统加入缓存模块会在一定程度上增加系统的复杂度,所以在是否引入缓存的问题上,需要根据业务场景来平衡。一般符合以下几种特征的数据可以考虑引入缓存模块:

  1. 数据很少变动:最适合缓存,因为基本不涉及缓存的更新操作,只需将数据加载到缓存即可。
    • 🌰 网站用到的 js,css 等静态资源,用户登录之后生成的 session 信息等。
    • 🌰 CDN 服务:很多大型网站都会利用CDN来加速一些不变资源的访问速度,比如一些图片,视频等。由于用户访问这些资源的本源需要跨越多个主干网,在速度上较慢,而 CDN 恰恰弥补了这个缺陷,所以这里可把 CDN 看成是一种缓存的服务。
  2. 热点数据:热点数据最大的特点是发生时间不定,流量峰值不定,最有可能导致系统瘫痪,是开发中要加缓存的主要原因。
    • 热点数据的缓存不容易设计,因为带有单点属性。🌰 假设缓存服务器有100个节点,此时发生了某个热点新闻,该新闻的缓存在0号节点,大量的请求会被路由到0号节点,很有可能会导致0号节点垮掉,如果0号节点垮掉,基于故障转移策略,流量瞬间会转移到另外一个节点,然后这个节点会垮掉,以此类推。缓存虽然提高了系统的整体吞吐量,但在应对有针对性的流量高峰时需要单独针对。这也是分布式系统要解决的问题,以上的热点数据场景,最简单粗暴的方法是缓存副本,一份缓存数据存多份副本,类似于MySQL的读写分离方案,多份副本同时提供读取操作。除此之外,这种场景下我推荐使用进程内缓存代替分布式缓存,因为进程内缓存在访问速度上要比需要跨越网络的分布式缓存要快很多。
  3. 耗时操作:若数据的计算代价或者获取代价很大,但不太会频繁变动 or 变动较频繁但系统对数据的一致性要求不高,则也适合进行缓存。

缓存的淘汰

当缓存数据大于缓存介质容量时,需要一种缓存替换算法来淘汰旧数据,保证新数据能正常缓存。

现在主流的有以下几种淘汰策略:

  • LFU (Least Frequently Used):缓存系统会记住每条缓存数据被访问的频率,会优先淘汰最不常用的数据。
  • LRU (Least Recently Used): 缓存系统会记住每条数据最后的访问时间,会优先淘汰长时间未被访问的数据
  • ARC (Adaptive Replacement Cache):这个缓存算法同时跟踪记录LFU和LRU,以及驱逐缓存条目,来获得可用缓存的最佳使用,它被认为是性能最好的缓存算法之一,介于LRU和LFU之间,能够记忆效果和自调,当然开发肯定会比较复杂。
  • FIFO:基于队列的原理的淘汰算法,先进先出。这种算法比较简单,现实中使用比较少是因为这种业务场景比较少。

缓存实现方式

进程内缓存

指缓存和应用程序在同一个进程内,在获取缓存数据时无需跨越网络。所以是访问速度最快的一种方式。

进程内缓存一般用在单机或者小型系统中,但是,在整体架构实现了一致性的前提下,也可用于大型系统,🌰 在多个服务器节点的情况下,假如用户A的信息缓存在0号节点,如果有一种机制能保证用户A的所有请求都只会到达0号节点,这个时候利用进程内缓存就完全没有问题。最典型的像Actor模型应用。

进程外缓存

指缓存数据和应用程序是隔离的,位于不同的进程内。

这里又可把进程外缓存划分为单机版本和分布式版本,单机版本会存在单机故障问题。分布式版本通常被称为分布式缓存,是基于分布式理论的一种架构模式。它突破了单机缓存的容量限制和单机故障问题,虽然在访问速度上比进程内缓存要慢很多,但是相比较磁盘 IO 操作要快的多,所以现在很多大型系统都喜欢用分布式缓存来提高性能。像用的最多的Redis在3.0版本之后就提供了集群方案。

缓存的一些不足:

  • 缓存和数据源的一致性问题
  • 缓存命中问题
  • 缓存的雪崩穿透问题
  • 缓存的并发竞争问题
  • 缓存适合读多写少的系统
  • 引入缓存组件会给系统设计带来一定的复杂度
  • 缓存会加大运维的成功以及排查bug的成本

缓存一致性

数据一致性问题是分布式系统不可避免的一个痛点,虽然可利用分布式事务做到数据一致性,但在实际系统架构设计中,还是推崇尽量避免分布式事务。缓存和数据库数据的一致性,在产生原理上,和分布式数据的一致性类似。凡是处于不同物理位置的两个操作,如果操作的是相同数据,都会遇到一致性问题。

系统最常见的操作流程:

  1. 数据的请求首先查询缓存中是否存在该数据
  2. 若数据命中缓存(在缓存中存在)则直接返回数据;若数据没有命中缓存(缓存中不存在),则去数据库中取数据
  3. 从数据库中取回数据,然后把数据写入缓存

可见对数据库的操作和对缓存的操作是两个不同阶段的操作,在任何一个操作过程中都会发生线程安全问题。🌰 两个线程同时查询缓存时,可能两个线程都没有命中缓存,就会同时查询数据库,然后两个线程同时回写缓存。并发读操作,在多数情况下缓存和数据库数据还能保持一致,若并发写操作,更有可能导致缓存和数据库数据的不一致。🌰 以最常见的用户积分场景为例,每个用户都有自己的积分,发生以下过程:

  1. 线程A根据业务会把用户id为1的积分更新成100,线程B根据业务会把用户id为1的积分更新成200
  2. 在数据库层面,线程A和线程B肯定不存在并发情况,因为数据库用锁来保证了ACID(假如是mysql等关系型数据库),无论数据库中最终的值是100还是200,我们都假设正确。假设更新数据库的顺序是先A后B,则数据库中的值为200
  3. 线程A和线程B在回写缓存时,很可能操作缓存的顺序是先B后A(因为网络调用存在不确定性),这个时候缓存内的值会被更新成100,发生了缓存和数据库不一致的情况

可见,上述出现缓存和数据库数据不一致的根本原因是,多个线程同时操作数据,且对缓存和数据库数据更改的两个操作不是原子的。因此根本的解决发方案是:将两个操作合并成逻辑上的一个原子操作,or 相同数据只允许一个线程操作。

分布式锁

利用分布式锁将对缓存和数据库数据的两个操作封装为逻辑上的一个操作。具体流程为:

  1. 每个想要操作缓存和数据库的线程都必须先申请分布式锁
  2. 如果成功获得锁,则进行数据库和缓存操作,操作完毕释放锁
  3. 如果没有获得锁,根据不同业务可以选择阻塞等待或者轮训,或者直接返回的策略

分布式锁在一定程度上会降低系统的性能,而且分布式锁的设计要考虑到down机和死锁的意外情况。而最常见的分布式锁的实现方式就是利用 redis。

PS:利用分布式锁也是解决分布式事务的一种方案。

单线程

发生缓存和数据库不一致的原因在于多个线程的同时操作,如果相同的数据始终只会有一个线程去操作,也能避免不一致的情况。🌰 nodejs,可以充分利用 nodejs 单线程的优势;Actor模型,Actor模型在对于同样的对象上可以看做是单线程模式。

单线程的模式基本上和分布式锁的方案类似,只不过单线程不需要锁就可以实现操作的顺序化,这也是单线程的优势所在。

删除缓存

相比于分布式锁的方案,实际更常用删除缓存的方式:在可能发生数据不一致的场景下,以数据库为主,操作完数据库后,不更新缓存而是删除缓存。

这在一定意义上相当于只操作数据库,把需要维护的两个数据源变成了一个数据源。

❗️这种方案利用缓存的过期时间来保证数据的最终一致性,因此可在一些能容忍数据暂时不一致的场景下采用此方案。

该方案的缺点是:若相同的数据被频繁更新,则缓存会被频繁删除,当有读请求的时候又会被频繁的从数据库加载,缓存命中率不高。所以这种方案更适用于那种对缓存命中率不敏感的系统。

其他方案

以缓存为主,应用程序只和缓存组件通信,持久化数据库由缓存组件负责。

该方案还需考虑:

  • 数据从缓存持久化到数据采用什么样的解决方案,是同步进行还是异步进行呢?
  • 在新数据请求的时候,如果缓存不存在,要采用什么样的方式来填充数据?
  • 如果缓存模块挂掉了该怎么办?

以缓存为主的方案的优势是数据优先进入IO速度快的设备,对于那些请求量大,但是可以容忍一定数据丢失的应用非常合适,🌰 应用 log 数据的收集系统,这种系统其中一个最大的特点就是可以容忍一定数据的丢失,但是并发的请求数会非常大。所以可利用缓存设备前置的方案来应对这种应用场景。

缓存设计问题

在一个高并发系统中,核心功能的缓存命中率一般要保持在90%以上甚至更高,如果低于这个命中率,整个系统可能就面临着随时被峰值流量击垮的可能。

如果按照传统的缓存和DB的流程,一个请求到来的时候,首先会查询缓存中是否存在,如果缓存中不存在则去查询对应的数据库。假如系统每秒的请求量为10000,而缓存的命中率为60%,则每秒穿透到数据库的请求数为4000,对于关系型数据库mysql来说,每秒4000的请求量对于分了一主三从的Mysql数据库架构来说也已经足够大了,再加上主从的同步延迟等诸多因素,这个时候你的 mysql 已经行走在 down 机边缘了。

缓存的最终目的,是在保证请求低延迟的情况下,尽最大努力提高系统的吞吐量。

缓存系统的设计中可能导致系统崩溃的原因有:缓存穿透、缓存雪崩

缓存穿透

缓存穿透:当一个请求到来的时候,在缓存中没有查找到对应的数据(缓存未命中),业务系统不得不从数据库(这里其实可以笼统的成为后端系统)中加载数据。

发生缓存穿透的原因:

  1. 请求的数据在缓存和数据中都不存在:此时若按照一般的缓存设计,每次请求都会到数据库查询一次,然后返回不存在,这种场景下,缓存系统几乎没有起任何作用。
    • 在正常的业务系统中,发生这种情况的概率比较小,就算偶尔发生,也不会对数据库造成根本上的压力。但若系统中有死循环的查询 or 被黑客攻击,故意伪造大量的请求来读取不存在的数据而造成数据库的down机,🌰 如果系统的用户id是连续递增的int型,黑客很容易伪造用户id来模拟大量的请求。
  2. 请求的数据在缓存中不存在,在数据库中存在:这是正常情况,因为缓存的容量有限,不可能把所有业务数据都放到缓存。优先把访问最频繁的热点数据放入缓存系统,就能利用缓存的优势抗住主要的流量来源,而剩余的非热点数据,就算是有穿透数据库的可能性,也不会对数据库造成致命压力。

发生缓存穿透是不可避免的,我们能做的是尽量避免大量的请求发生穿透。解决缓存的穿透问题本质上是要解决怎么样拦截请求的问题,一般情况下会有以下几种方案:

回写空值

当请求的数据在数据库中不存在的时候,缓存系统可以把对应的key写入一个空值,这样当下次同样的请求就不会直接穿透数据库,而直接返回缓存中的空值了。

该方法要注意几点:

  • 当有大量的空值被写入缓存系统中,同样会占用内存,不过理论上不会太多,完全取决于key的数量。而且根据缓存淘汰策略,可能会淘汰正常的数据缓存项。
  • 空值的过期时间应该短一些,比如正常的数据缓存过期时间可能为2小时,可以考虑空值的过期时间为10分钟,这样做一是为了尽快释放服务器的内存空间,二是如果业务产生相应的真实数据,可以让缓存的空值快速失效,尽快做到缓存和数据库一致。
//获取用户信息
public static UserInfo GetUserInfo(int userId){//从缓存读取用户信息var userInfo = GetUserInfoFromCache(userId);if (userInfo == null){//回写空值到缓存,并设置缓存过期时间为10分钟CacheSystem.Set(userId, null,10);}return userInfo;
}

bloom filter

bloom filter:将所有可能存在的数据通过多种哈希到一个足够大的 bitmap 中,一个一定不存在的数据会被这个 bitmap 拦截掉,从而避免了对底层存储系统的查询压力。

bloom filter 优势:占用内存非常小,判断一个数据不存在100%正确。

缺点:会存在假阳性,不支持删除数据。

缓存雪崩

缓存雪崩:指缓存中数据大批量同时过期,造成查询数据库数据量巨大,引起数据库压力过大导致系统崩溃。

缓存穿透 🆚 缓存雪崩:缓存穿透是指缓存中不存在数据而造成会对数据库造成大量查询,而缓存雪崩是因为缓存中存在数据但同时大量过期。二者本质相同,都是对数据库造成了大量的请求。

多个缓存key同时失效的场景是产生雪崩的主要原因,有以下几种解决方案:

设置不同过期时间

给缓存的每个key设置不同的过期时间是最简单的防止缓存雪崩的手段,整体思路是给每个缓存的key在系统设置的过期时间之上加一个随机值,或者干脆是直接随机一个值,有效的平衡key批量过期时间段,消掉单位之间内过期key数量的峰值。

 public static int SetUserInfo(int userId){//读取用户信息var userInfo = GetUserInfoFromDB(userId);if (userInfo != null){//回写到缓存,并设置缓存过期时间为随机时间var cacheExpire = new Random().Next(1, 100);CacheSystem.Set(userId, userInfo, cacheExpire);return cacheExpire;}return 0;
}

后台单独线程更新

这种场景下,可以把缓存设置为永不过期,缓存的更新不是由业务线程来更新,而是由专门的线程去负责。当缓存的key有更新时候,业务方向mq发送一个消息,更新缓存的线程会监听这个mq来实时响应以便更新缓存中对应的数据。不过这种方式要考虑到缓存淘汰的场景,当一个缓存的key被淘汰之后,其实也可以向mq发送一个消息,以达到更新线程重新回写key的操作。

缓存的可用性和扩展性

和数据库一样,缓存系统的设计同样需要考虑高可用和扩展性。虽然缓存系统本身的性能已经比较高了,但是对于一些特殊的高并发的热点数据,还是会遇到单机的瓶颈。🌰 假如某个明星出轨了,这个信息数据会缓存在某个缓存服务器的节点上,大量的请求会到达这个服务器节点,当到达一定程度的时候同样会发生down机的情况。类似于数据库的主从架构,缓存系统也可以复制多分缓存副本到其他服务器上,这样就可以将应用的请求分散到多个缓存服务器上,缓解由于热点数据出现的单点问题,提高可用性。

和数据库主从一样,缓存的多个副本也面临着数据的一致性问题,同步延迟问题,还有主从服务器相同key的过期时间问题。

至于缓存系统的扩展性同样的道理,也可以利用“分片”的原则,利用一致性哈希算法将不同的请求路由到不同的缓存服务器节点,来达到水平扩展的要求,这一点和应用的水平扩展道理一样。

关于一致性hash

参考链接

程序员修神之路--听说你会缓存?

程序员修神之路--谈了千百遍的缓存数据的一致性问题

程序员修神之路--缓存架构不够好,系统容易瘫痪

程序员修神之路--高并发下为什么更喜欢进程内缓存

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

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

相关文章

开源的工作流系统突出优点总结

随时欢迎大家一起了解开源的工作流系统的突出优势和特点。当前,想要实现高效率的办公,可以一起来了解低代码技术平台、开源的工作流系统的相关特点和功能优势。作为较受职场喜爱的平台产品,低代码技术平台拥有可视化才做界面、灵活、好维护操作等多个优势特点,在推动企业流…

2024年最新版Typora免费使用教程心得

在数字化时代,写作已成为我们日常沟通、知识分享的重要手段。然而,繁琐的排版格式常常让人望而却步。幸运的是,Markdown编辑器以其简洁的语法和高效的排版功能,为我们带来了福音。Typora是一款功能强大的文本编辑器,它采用所见即所得的编辑方式,能够让用户快速地编辑各种…

P10786 [NOI2024] 百万富翁

讲解 P10786 [NOI2024] 百万富翁。先爆搜出 t>=9 的部分分,然后考虑使用动态规划算法进行常数优化跑出答案。思路: 先考虑 Sub1 的部分分,暴力算法:暴力询问所有 \(i<j\) 的数对 \((i,j)\)。 则一个 \(i\) 为最大值当且仅当 \((i,j)\) 的返回值都是 \(i\) 且在 \(i\)…

用我十多年的“奇葩”经验,给在“挂吊瓶”的博客园几点建议

初识博客园 我是08年开始接触开发的,一开始涉及的就是.net和java,记得那会好像是jar6来着,net嘛还是2.0 那时候包括现在,找资料很多时候会找到博客园来 一开始我以为博客园是很多博主成立的一个联盟,就是各自弄一个博客系统,然后公用一个域名 为啥会这么想呢? 因为我看高…

基于深度学习网络的USB摄像头实时视频采集与水果识别matlab仿真

1.算法运行效果图预览 (完整程序运行后无水印)将usb摄像头对准一个播放不同水果图片的显示器,然后进行识别,识别结果如下: 本课题中,使用的USB摄像头为:2.算法运行软件版本 matlab2022a3.部分核心程序 (完整版代码包含详细中文注释和操作步骤视频)程序中包括MATLAB读取摄…

Android 常用的性能分析工具详解:GPU呈现模式

此篇将重点介绍几种常用的Android性能分析工具: 一、Logcat 日志 选取Tag=ActivityManager,可以粗略地知道界面Displaying的时间消耗。当我们打开一个Activity的时候,log会打印一串log如下: I/ActivityManager﹕ Displayed xxx.xxx.xxx/TestActivity: +1s272ms (total +3s…

AI - 一文了解AIOps的含义、特点与功用

AIOps定义 AIOps是智能运维(Artificial Intelligence forITOperations)的英文缩写。 当今,专业厂商根据自身理解和商业目的,分别给出了各具特色的AIOps定义。 主要关键字:IT运维、人工智能 (AI)、机器学习(ML)、自然语言处理(NLP)、大数据、数据分析、运营效率等 以下…

两种解决powerdesigner概念模型转物理模型报字段重复错误的方法

问题 使用 powerdesigner 概念模型转物理模型时会报一个不能重复的错误解决方法 一、取消勾选Unique code取消勾选以后保存,再一次生成物理模型。 二、取消勾选Entity Attribute,不对属性进行检查 如果Unique code取消勾选后依旧不行,可以尝试第二种解决办法。取消勾选以后点…

AT cf17 final J Tree MST

AT cf17 final J Tree MST 考场上想出的黑题,然而写挂了…… 思路 考场推出 boruvka 算法,会的直接跳过就好。 结论:一个点向另外一个点连出的最小边,一定在最小生成树上。 证明:参考 Kruskal 生成树的流程,若当前边(最小边)不在最小生成树上,表明边的两端已经在同一个…

EPIC Institute of Technology Round Summer 2024 (Div. 1 + Div. 2) VP记录

EPIC Institute of Technology Round Summer 2024 (Div. 1 + Div. 2) VP记录 A 一眼 \((n - 1) m + 1\)。 B 最后的数列是固定的,每个数与最后数列的数相减后,对差值求和再加上最大值即可。 C 唐诗 C 题,获得 \(3\) 发罚时。 只有一个数右边的数归零了,它才会归零。 右往左…

入职后,我发现工作内容和自己想象中的不太一致。。

2018年6月,大三暑假进行时,实习第二天上班 昨天王工跟我说最好统一开发工具用eclipse,今早我瞄到其实也有同事用idea。 eclipse还得学习,用idea算了,随便上网找个盗版的就好咯,不纠结这么多。 公司被逮到,也是公司的问题,公司没有禁止使用idea,一定就不是我的问题。一…