56星座屋
当前位置: 首页 星座百科

缓存的分类及工作原理(缓存的设计与使用)

时间:2023-05-24 作者: 小编 阅读量: 2 栏目名: 星座百科

相反的对数据要求苛刻,变化频率高、访问频率低的数据不能缓存。这是标准的designpattern,包括Facebook的论文《ScalingMemcacheatFacebook》也使用了这个策略。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。WriteThroughWriteThrough套路和ReadThrough相仿,不过是在更新数据时发生。

欢迎关注文章系列 ,关注我
《提升能力,涨薪可待》
《面试知识,工作可待》
《实战演练,拒绝996》
如果此文对你有帮助、喜欢的话,那就点个赞呗,点个关注呗!

一、引言

本文谈及的是后台业务服务缓存问题,在构建和优化业务服务时,

第一想到的应该是优化数据库,比如数据库模型设计、SQL结构化查询语句优化,慢查询往往是系统性能杀手;

第二便是使用缓存。在应用中使用缓存的技术手段并不新鲜,计算机组成原理中我们已经学习过,早期的计算机CPU通过fsb(中央处理器数据总线)直接与主存(内存)连接的方式导致CPU吞吐率下降,内存成为性能瓶颈,同时又由于内存访问热点数据的集中性,诞生了位于CPU和内存之间的高速缓存。

这类比于高并发的场景,数据库读库性能成为瓶颈,需要在DB和高并发的调用之间加上缓存。缓存所带来的性能提升效果更直接、高效。但是相比其他优化手段,缓存的使用并不是零成本的,任何系统使用缓存,都会遇到两大问题:

  1. 数据不一致问题
  2. 系统复杂度增加

深入业务,才能有好的设计。在设计缓存的时候,要根据我们的业务特点,明白什么是不容易变的,什么是相对容易变,什么是真正触达用户的,哪些数据是关键。这样才能发现真正应该cache的点。

二、缓存设计与使用

衡量缓存设计好坏的衡量指标是缓存命中率,缓存的命中率=缓存命中次数/请求次数,命中率越高,缓存的使用率越高。

缓存的适用场景总的来说是混存访问高频读低频写的数据。相反的对数据要求苛刻,变化频率高、访问频率低的数据不能缓存。设计和使用缓存首先要了解服务、DB、Cache的性能情况,一般情况下,DB的响应是10ms级(优化充足的情况下,SQL平均耗时1ms。这是因为命中了索引,并且命中了MySQL缓冲池(内存中)。

如果命中索引但不命中缓冲池,且查询数据量不大,磁盘并发量不高,则大约耗时10ms(磁盘寻道)。如果这些都不满足,耗时就大了),分布式缓存在ms级,进程内缓存在ns级。图1

图1 缓存响应时间

本地缓存可用hashmap(注意并发)、guava-cache(推荐)等实现。对于一致性要求不高且缓存命中率较高的数据服务,本地缓存可以减少服务端调用次数;集中缓存又称分布式缓存,实现由redis(推荐)、memcache等。

2.1 分布式缓存的设计与使用

缓存的设计模式一般分为四种Cache aside, Read through, Write through, Write behind caching。

2.1.1 Cache Aside Pattern

Cache aside pattern是最常用的方式,其具体逻辑如下:

失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。

命中:应用程序从cache中取数据,取到后返回。

更新:先把数据存到数据库中,成功后,再让缓存失效。


这是标准的design pattern,包括Facebook的论文《Scaling Memcache at Facebook》也使用了这个策略。为什么不是写完数据库后更新缓存?你可以看一下Quora上的这个问答《Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?》,主要是怕两个并发的写操作导致脏数据。

那么,是不是Cache Aside这个就不会有并发问题了?不是的,比如,一个是读操作,但是没有命中缓存,然后就到数据库中取数据,此时来了一个写操作,写完数据库后,让缓存失效,然后,之前的那个读操作再把老的数据放进去,这时会造成脏数据。

但,这个case理论上会出现,不过,实际上出现的概率可能非常低,因为这个条件需要发生在读缓存时缓存失效,而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多,而且还要锁表,而读操作必需在写操作前进入数据库操作,而又要晚于写操作更新缓存,所有的这些条件都具备的概率基本并不大。

所以,这也就是Quora上的那个答案里说的,要么通过2PC或是Paxos协议保证一致性,要么就是拼命的降低并发时脏数据的概率,而Facebook使用了这个降低概率的玩法,因为2PC太慢,而Paxos太复杂。当然,最好还是为缓存设置上过期时间。

2.1.2Read/Write Through Pattern

我们可以看到,在上面的Cache Aside套路中,我们的应用代码需要维护两个数据存储,一个是缓存(Cache),一个是数据库(Repository)。所以,应用程序比较啰嗦。而Read/Write Through套路是把更新数据库(Repository)的操作由缓存自己代理了,所以,对于应用层来说,就简单很多了。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。

Read Through

Read Through 套路就是在查询操作中更新缓存,也就是说,当缓存失效的时候(过期或LRU换出),Cache Aside是由调用方负责把数据加载入缓存,而Read Through则用缓存服务自己来加载,从而对应用方是透明的。

Write Through

Write Through 套路和Read Through相仿,不过是在更新数据时发生。当有数据更新的时候,如果没有命中缓存,直接更新数据库,然后返回。如果命中了缓存,则更新缓存,然后再由Cache自己更新数据库(这是一个同步操作)

下图自来Wikipedia的Cache词条。其中的Memory你可以理解为就是我们例子里的数据库。


2.1.3Write Behind Caching Pattern

Write Behind 又叫 Write Back。一些了解Linux操作系统内核的同学对write back应该非常熟悉,这不就是Linux文件系统的Page Cache的算法吗?

是的,你看基础这玩意全都是相通的。在更新数据的时候,只更新缓存,不更新数据库,而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比(因为直接操作内存嘛 ),因为异步,write back还可以合并对同一个数据的多次操作,所以性能的提高是相当可观的。

但是,其带来的问题是,数据不是强一致性的,而且可能会丢失(我们知道Unix/Linux非正常关机会导致数据丢失,就是因为这个事)。在软件设计上,我们基本上不可能做出一个没有缺陷的设计,就像算法设计中的时间换空间,空间换时间一个道理,有时候,强一致性和高性能,高可用和高性性是有冲突的。软件设计从来都是取舍Trade-Off。

另外,Write Back实现逻辑比较复杂,因为他需要track有哪数据是被更新了的,需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候,才会被真正持久起来,比如,内存不够了,或是进程退出了等情况,这又叫lazy write。

在wikipedia上有一张write back的流程图,基本逻辑如下:


2.2 使用本地缓存

本地缓存是应用程序在同一进程内的缓存,通常是ConcurrentHashMap。优点是耗时低得忽略不计,缺点是占用本地内存、多机会冗余、数据不同步。当集群里每个App Server都有个缓存,会有很多数据是重复的,而且某台机器更新了一条数据,别的机器不知道,给应用带来了额外同步的工作。

同步问题可以用消息队列来解决,一台有更新,广播消息给其他机器,准实时同步。

同步问题也可以用Redis 发布订阅模式来通知其它机器进行同步

同步问题也可以用etcd的事件机制来解决多机同步问题

缓存除了加快访问以外,也能提高负载能力。因为数据库连接是有限的资源——不支持NIO(none block IO),每个连接同时只能服务一个线程,有多少连接就有多少并发。如果有慢查询占住了连接,系统性能会急剧下降!缓存就能减轻对数据库连接的依赖。

本地缓存和分布式缓存各有千秋,一般建议用一致性更高的分布式缓存,当性能需要极端调优时,使用本地缓存,或者整体缓存数据量不是很大,具有高频访问几乎不修改的特点的,建议本地缓存,典型的如业务的标签功能。

三、缓存淘汰策略

在只需要缓存一部分热点数据的场景下,比如直播平台的热门房间,需要设计缓存淘汰策略。

3.1 FIFO(First In, First Out)

即先进先出,淘汰最早进来的混存数据,使用一个标准的队列即可

3.2 LRU(Least RecentlyUsed)

即最近最少使用,淘汰最近不使用的缓存数据。即按照最后被使用时间淘汰

和FIFO不同的是,需要对链表做基本模型,读写的时间复杂度是O(1),写入新数据进入头部,链表满了数据从尾部淘汰;

最近时间被访问的数据移动到头部,实现算法有很多,如hashmap 双向链表等等;

问题在于若是偶发性某些key被最近频繁访问,而非常态,则数据受到污染。

2.LFU(Least Frequently used)

即最近使用次数最少的数据被淘汰,注意和LRU的区别在于LRU的淘汰规则是基于访问时间。LFU按照使用次数淘汰数据

LFU中的每个数据块都有一个引用计数,数据块按照引用计数排序,若是恰好具有相同引用计数的数据块则按照时间排序;

因为新加入的数据访问次数为1,所以插入到队列尾部;

队列中的数据被新访问后,引用计数增加,队列重新排序;

当需要淘汰数据时,将已经排序的列表最后的数据块删除;

有很明显问题是若短时间内被频繁访问多次,比如访问异常或者循环没有控制住,而后很长时间未使用,则此数据会因为频率高而被错误的保留下来,没有被淘汰。尤其对于新来的数据,由于其起始的次数是1,所以即便被正常使用也会因为比不过老的数据而被淘汰。所以维基百科说纯粹的LFU算法不经常单独使用而是组合在其他策略中使用。

四、缓存引入的问题

缓存系统一定程度上极大提升系统并发能力,但同样也增加额外技术考虑因素,比如缓存雪崩、缓存穿透、缓存更新与数据一致性

4.1、缓存穿透

缓存穿透是指查询的key不存在,从而缓存查询不到而查询了数据库。若是这样的key恰好并发请求很大,那么就会对数据库造成不必要的压力。比如黑客用一堆不存在的key访问数据,大量请求发送到数据库,数据库压力过大而宕机,怎么解决呢?

1、把所有存在的key都存到另外一个存储的Set集合里,查询时可以先查询key是否存在;

2、将这些key对应的值设置为null 丢到缓存里面去。后面再出现查询这个key 的请求的时候,直接返回null,再根据业务需求设置过期时间。

3、BloomFilter 类似于一个hbase set 用来判断某个元素(key)是否存在于某个集合中。这种方式在大数据场景应用比较多,比如 Hbase 中使用它去判断数据是否在磁盘上。这种方案可以加在第1/2种方案中,在缓存之前加一层 BloomFilter ,在查询的时候先去 BloomFilter 去查询 key 是否存在,如果不存在就直接返回,存在再走查缓存 -> 查 DB。

4.2、缓存击穿

在高并发的系统中,大量的请求同时查询一个 key 时,这个key刚好失效了,就会导致大量的请求都打到数据库上面去。这种现象我们称为缓存击穿。造成缓存击穿的原因是多个线程同时去查询数据库,那么我们可以在第一个查询数据的请求上使用一个 互斥锁。其他的线程进入等待状态,等第一个线程查询到了数据,然后做缓存。后面的线程进来发现已经有缓存了,就直接走缓存。

4.3、缓存雪崩

定义:缓存雪崩是指缓存系统失效,导致大量请求同时进行数据回源,导致数据源压力骤增而崩溃。两种情况会导致此问题:1、多个缓存数据同时失效;2、缓存系统崩溃,缓存同时失效

针对多个缓存数据同时失效的问题,可以将缓存时间离散化,根据缓存数据访问规律和缓存数据不一致的敏感性要求来选择缓存时间。

如果是第二个问题,缓存系统整体故障,则整个缓存系统不可用,大量回源请求,且由于缓存系统故障无法回写缓存,导致无法快速恢复。

这是缓存系统的引入,在解决高性能、高并发的同时,引入新的故障点。

考虑此问题,应从事前、事故中、事后不同阶段考虑:

事前:增加缓存系统高可用方案设计,避免出现系统性故障

事故中:增加多级缓存,在单一缓存故障时,仍有其他缓存系统可用,如之前项目中使用的三级缓存方案:内存级缓存->Memcached->Redis这样的方案;启用熔断限流机制,只允许可承受流量,避免全部流量压垮系统

事后:缓存数据持久化,在故障后快速恢复缓存系统

五、总结

使用缓存会增加系统复杂性,应根据实际业务考虑是否需要使用以及使用分布式缓存还是本地缓存,设置合理的缓存策略可以提高系统的性能,同时要合理的规避可能由于引入缓存而增加的风险。

文章链接:https://www.jianshu.com/p/4f0caa00b2cc

如果此文对你有帮助、喜欢的话,那就点个赞呗,点个关注呗!

    推荐阅读
  • 幼师面试考什么(你都了解多少)

    以下内容希望对你有帮助!幼师面试考什么幼师面试考试内容为抽题目写教案20分钟、5分钟结构化面试、10分钟才艺展示、5分钟答辩。部分地区可能会有区别,具体考试内容请以当地面试公告为准。看图讲故事,给5-6岁幼儿讲述故事,要求语言生动,用词得当。讲故事后,提问幼儿,针对图中小动物的关系提问。

  • 灭害灵喷了植物怎么办(用了灭害灵喷了植物怎么办)

    下面内容希望能帮助到你,我们来一起看看吧!灭害灵喷了植物怎么办灭害灵是高效低毒的一种杀虫剂,里面含的主要成分是三氯氰戊菊酯,喷洒在植物上会和水结合,阻碍植株吸收水分,容易导致叶子发黄枯萎,甚至会死亡。喷洒后要尽快用清水冲洗,冲洗掉药液,并让药液排出去。若是想要驱虫,一定要先稀释后再喷洒,控制好浓度,避免产生药害。

  • 常用解表方剂(王绵之讲方剂辛温解表方)

    一般地恶寒与发热方应是相应的,但发热以后恶寒不是特别明显。恶寒发热,肌表无汗,头痛项强,肢体羧楚疼痛,口苦而渴。头痛项强,鼻塞流涕,身体疼痛,发热恶寒或恶风,无汗,舌苔薄白,脉浮。恶寒发热,无汗,喘咳,痰多而稀,或痰饮咳喘,不得平卧,或身体疼重,四肢、头面浮肿,舌苔白滑,脉浮者。

  • 到底买奔驰c级好还是宝马3系好(新款宝马3系对比奔驰C级)

    今天带来的两位选手,是主打运动中型轿车里面的佼佼者,可以说是老对手了,相近的车身尺寸,相近的动力数据,相近的底盘布局,这一切都被各自品牌的无数拥趸所崇尚着,即使这样也得有个高低之分,今天我们带来了刚刚推出新款的宝马3系,看看他与奔驰C相比有何变化,谁更值得选择。

  • 电动车刹车锈死了怎么办(怎么保养电动车)

    跟着小编一起来看一看吧!电动车刹车锈死了怎么办换个刹车。车辆的保养要注意,尤其是前刹为鼓式的电动车行驶较长的里程或者停放一段时间后,务必要到修理店检查一下。下雨天,车子尽量推进车棚,如无车棚,也要想办法给车子轮毂遮盖一下。驾驶习惯要注意,雨天行车务必要慢,车子轮毂不要浸到水里,遇到水坑尽量绕行。

  • 六年级上册语文人教版近反义词(小学语文六年级上册近义词)

    六年级上册语文人教版近反义词?接下来我们就一起去研究一下吧!六年级上册语文人教版近反义词小学语文六年级上册近义词反义词大全,暑假预习必备!

  • 菠萝蜜嫁接要几天才能熟(菠萝蜜嫁接育苗技术)

    如在冬春气温较低时播种,可先盖一层薄膜保温,再盖上遮阳网。同时要防止苗床积水,以免造成种子腐烂。嫁接苗栽种完毕及时淋一次透水,然后搭上支架盖上薄膜,四周用土压紧,再盖上30%透光度遮阳网。若遇阴雨天,可除去遮阳网。在气温较高,超过36度,要定时揭开薄膜两端换气降温。若侵犯著作权,请主动联系本平台并提供相关书面证据,本平台将更正来源及作者或依据著作权人意见在24小时内删除该文章,并不承担其他任何责任。

  • 三鲜素饺子教学教程(三鲜素饺子的做法)

    三鲜素饺子教学教程和面。取盆,将面粉加入,一边加入淡盐水,一边用筷子搅拌,直至面粉呈棉絮状后,用手揉搓,使面团变得光滑;盖上保鲜膜,醒30分钟左右。将水烧开,下入饺子煮开,加小半碗凉水,再次烧开,反复2-3次,待饺子漂到水面上时,饺子就熟透了。

  • 螃蟹不放水做法(螃蟹不放水怎么做)

    螃蟹不放水做法首先将准备好的梭子蟹八只放在盆中备用,因为梭子蟹的腿部内侧有很多泥土,所以我们需要好好的清洗下表面的泥腥味。准备一盆清水,放入半杯白酒,然后在倒入梭子蟹八只。这样在蒸的时候防止它挣扎的过程中,将腿部蹬掉。用绳子捆住梭子蟹还可以使成品更美观一些。蒸锅中加入一瓶啤酒,然后在上面放上一层篦子。将处理好的螃蟹放在上面,大火蒸12分钟。12分钟之后,这时候的啤酒已经干锅了。

  • 炼好的猪油怎么保存不坏(猪油不放冰箱要怎么保存)

    夏天由于气温高,炼出的猪油很难凝固,这样的油影响存放时间。可以在将油炼好后,端离火源,视油温降至80度左右时,加入白糖搅匀即可。猪油去渣凉透后,在上面放上一勺豆油。只要不热得猪油都化了,就没事,不用放冰箱里也可。猪油存放时间不宜过长,特别在温度高的夏天极易与空气接触而发生氧化,致使酸败变质。猪油不宜用于凉拌和炸食。