灵魂拷问
  • 缓存是什么?(没听说过你可以走了)
  • 哪些场景需要用到缓存呢?
  • 缓存可以分类吗?
  • 缓存的实现方式有哪些?

缓存

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

缓存在计算机世界里是一个常见并且不可忽视的一个重要因素,它几乎遍布于你所知的各个领域。例如cpu的一级缓存,二级缓存;浏览器的缓存等。我们在使用缓存的时候要清除的认识到缓存的数据是有有效期的,也就是说可能随时会消失。有的同学会说了,类似redis这些组件都提供了数据持久化的功能,这样数据就不会消失了。至于这个问题其实我想说两点:

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

缓存最常见的存储介质是内存,但这并不意味只有内存可以存储缓存数据,这也是初学者经常会犯的错误。缓存的作用是提供高速的读写功能,所以如果你的设备足够快,理论上都可以作为缓存使用,比如现在的SSD,在一些性能不太严格和敏感的场景下就可以作为存储缓存数据的介质,至于计算机的各种硬件之间的速度差距可以参考之前的文章:

高并发下为什么更喜欢进程内缓存

缓存应用场景

从理论上来说,任何需要提高访问速度的环节都可以加入缓存

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

数据很少变动

这类数据最适合缓存的应用场景,因为它基本不涉及到负责的缓存更新操作,所以只要将其加载到缓存中即可。最具有代表性的像网站用到的js,css等这些静态资源,用户登录之后生成的session信息等。

说到数据很少变动,不得不提CDN这种服务了,很多大型网站都会利用CDN来加速一些不变资源的访问速度,比如一些图片,视频等。由于用户访问这些资源的本源需要跨越多个主干网,在速度上较慢,而CDN恰恰弥补了这个缺陷,所以这里也可以把CDN看成是一种缓存的服务。

热点数据

这种数据是我们平时开发中要加缓存的主要原因数据,最有可能导致系统瘫痪的也是这种数据。它最大的特点是发生时间不确定,流量峰值不确定。大家可能还依稀记得微博因为两个明星出轨而挂掉的事件,虽然微博的系统架构后来经过改造可以同时抗住N个明星出轨,但是在不确定这个因素上依然无能为力。

热点数据的缓存并不容易设计,因为它带有单点属性,什么意思呢?假设我们的缓存服务器有100个节点,这个时候发生了某个热点新闻,而这个热点新闻的缓存在0号节点,大量的请求会被路由到0号节点,很有可能会导致0号节点垮掉,如果0号节点垮掉,基于故障转移策略,流量瞬间会转移到另外一个节点,然后这个节点会垮掉,以此类推.....缓存虽然提高了系统的整体吞吐量,但是在应对有针对性的流量高峰的时候需要单独针对。这其实也是分布式系统要解决的问题,既然一个节点扛不住流量高峰,系统可以设计多个节点一起来抗,至于以上的热点数据场景,最简单粗暴的方式就是缓存副本,一份缓存数据会存在多份副本,类似于MySQL的读写分离方案,多份副本同时提供读取操作。除此之外,这种场景下我推荐使用进程内缓存代替分布式缓存,因为进程内缓存在访问速度上要比需要跨越网络的分布式缓存要快很多,具体可以查看之前的推文:

高并发下为什么更喜欢进程内缓存

耗时操作

某些计算代价或者获取代价很大的数据在特定的条件下也适合进行缓存。为什么要加特定条件呢?如果系统对这些数据的一致性有着严格的要求,而且会频繁的变动,虽然获取数据代价比较大,但是你也要充分考虑缓存带来的副作用。像我们最常用的报表服务,一般生成报表都比较耗时,如果报表的数据是相对稳定的,那我们就可以考虑用缓存来提高系统的性能。

缓存的淘汰

存储缓存的设备限制了缓存是有大小限制的,如果以16G内存来存储缓存,那缓存的上限理论上就是16G(但是实际上要小的多),而且缓存带有时效性,所以当要缓存的数据大于介质容量的时候就需要一种淘汰数据的策略来保证新数据能正常被缓存。

最好的淘汰策略就是把系统不用的数据淘汰出去,但是什么数据是无用数据,这是策略的难点所在,基于用户行为的不确定性,这种数据所以很难用程序去预测。鉴于系统的常规理论,现在主流的有以下几种淘汰策略:

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

缓存实现方式

系统中实现缓存的方式大体上可以分为两种:

进程内缓存

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

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

分布式高并发下Actor模型如此优秀🤠

进程外缓存

顾名思义,进程外缓存的意思是缓存数据和应用程序是隔离的,位于不同的进程内。当然这里又可以把进程外缓存划分为单机版和分布式版本,单机版本这里就不多说了,会存在单机故障问题。

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

写在最后

在面对缓存带给系统的优势之后,也要注意到缓存也会有一些不足。

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

虽然缓存带来了不少问题,但是相比较缓存带给系统性能的提升是毋庸置疑的。我们在设计一个高并发系统的时候,缓存已经成为了一种必备设计,在正确设计了缓存各种策略之后,才能最大发挥缓存的优势

更多精彩文章

image


菜菜
76 声望13 粉丝