You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

120 lines
13 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 04 | 缓存设计:做好缓存设计的关键是什么?
你好,我是尉刚强,今天我们来聊聊基于性能的缓存设计。
缓存就是一个临时储存数据的地方。当用户查询数据时,首先会在缓存中查找,如果找到了就直接使用;如果找不到,就再到数据的原始位置去寻找。所以,缓存本质上是一种用空间换时间的技术,通过数据在空间上的重复,来提升数据的访问速度。
不过,随着分布式和云计算技术的发展,数据存储技术也发生了翻天覆地的变化,而且不同存储技术在价格和性能上都存在很大的差异,所以在针对性能进行软件设计时,如果我们没有做好多层级的缓存设计,不仅可能浪费钱,而且获取的性能收益可能也不够理想。
所以在今天的课程上,我会结合互联网应用与服务场景,给你讲解如何做好缓存设计,并会剖析典型的缓存使用案例,帮助你建立起缓存技术原理的系统认识,以此指导你在产品的软件设计中,可以正确使用缓存来提升系统的性能。
## 缓存设计的通关之路
那么首先,我想从两个问题开始,来带你了解下缓存设计要在什么时候做,以及通过不同数据类型的特性对比分析,来跟你一起探讨如何才能做好缓存设计。
好,第一个问题:**在互联网应用服务中,使用缓存技术的目的就只是为了提升访问速度吗?**
其实我认为,并不是所有的缓存都只是为了提升速度,因为**在分布式系统中,缓存机制实际上是系统级性能设计的一个重要权衡手段。**比如当某个数据库的负载比较高,接近系统瓶颈时,我们就可以使用缓存技术,把负荷分担到其他数据库中,那么这里使用缓存的目的,主要就是负载均衡,而不是提升访问速度。
第二个问题:**一个大型系统中的数据种类会非常多,那么需要为每种数据都设计缓存机制吗?**
其实完全没有必要。在实际的业务场景下,系统包含的业务数据太多了,你不可能针对每种数据都设计和实现缓存机制,因为一方面是投入的软件成本太高,另一方面也很可能无法带来比较高的性能收益。所以,在进行缓存设计之前,**你首先需要识别出哪些数据访问对性能的影响比较大。**
那么我们该怎么去识别哪些数据是需要缓存机制呢?在[第1讲](https://time.geekbang.org/column/article/374786)中,我已经给你介绍了性能建模设计方法,也就是通过分析和评估手段,来识别出哪个数据访问操作对性能的影响比较关键,然后再进行缓存设计。
不过接下来,在识别出需要使用缓存机制的数据之后,你可能会发现,这些数据种类之间的特性差异非常大,如果使用同一种缓存设计的话,其实是很难发挥出软件性能的最佳状态的。
所以在这里,我给你总结了三种需要缓存机制的数据种类,分别是不变性数据、弱一致性数据、强一致性数据。了解这三种缓存数据种类的差异,以及对应的设计缓存机制的方法,你就掌握了缓存设计的精髓。
好,下面我们就来具体了解下吧。
### 不变性数据
首先是不变性数据,它代表数据永远不发生变化,或者是在较长一个时间段内不会发生变化,因此我们也可以认为这部分数据是不变的。
这类数据就是可以**优先考虑使用缓存技术**的一种数据类型在实际的业务场景中也非常多。比如Web服务中的静态网页、静态资源或者数据库表中列数据与key的映射关系、业务的启动配置等等这些都可以认为是不变性数据。
而且,**不变性数据也意味着实现分布式一致性会非常容易**我们可以为这些数据选择任意的数据存储方式也可以选择任意的存储节点位置。因此我们实现缓存机制的方式就可以很灵活也会比较简单比如说在Java语言中你可以直接使用内存Caffeine或者内置的结构体来作为缓存都可以。
另外这里你要注意,当你针对不变性数据进行缓存设计时,其中的缓存失效机制可以采用永久不失效,或者基于时间的失效方式。而在采用基于时间的失效方式的时候,你还需要根据具体的业务需求,在缓存容量和访问速度之间做好设计实现上的权衡。
### 弱一致性数据
第二种是弱一致性数据,它代表数据会经常发生变化,但是业务对数据的一致性要求不高,也就是说,不同用户在同一时间点上看到不完全一致的数据,都是可以接受的。
由于这类数据**对一致性的要求比较低**,所以在设计缓存机制时,你只需要实现最终一致性就可以了。这类数据在实际业务中也比较多,比如业务的历史分析数据、一些搜索查找返回数据等,即使最近的一些数据没有记录进去,关系也不大。
另外快速识别这类数据还有一个方法那就是使用数据库Replica复制节点中读取的数据大部分都是这种类型的数据很多数据库Replica节点的数据因为数据同步时延是不满足强一致性要求的
针对弱一致性的数据我们通常使用的缓存失效机制是基于时间的失效方式同时因为弱一致性的特性你可以比较灵活地选择数据存储技术比如内存Cache或者是分布式数据库Cache。你甚至可以基于负载均衡的调度来设计多层级缓存机制。
### 强一致性数据
第三种缓存数据类型是强一致性数据,它是指代码数据会经常发生变化,而且业务对数据库的一致性要求非常高,也就是说当数据发生变更后,其他用户在系统中的任何地方,都应该看到的是更新后的数据。
那么,针对这种类型的数据,我一般是**不推荐你去使用缓存机制**,因为这类数据在使用缓存时会比较复杂,而且很容易会引入新的问题。比如说,用户可以直接提交和修改的各种数据内容,如果没有同步修改缓存中的数据,就会引发数据不一致性的问题,导致比较严重的业务故障。
不过在一些特殊的业务场景中,比如,在针对个别的数据访问频率非常高的情况下,我们还是需要通过设计缓存机制,来进一步提升性能。因此针对这类强一致性数据,在设计缓存机制时,你需要特别注意两点:
1. 这种数据的缓存一定要**采用修改同步的实现方式**。也就是说,所有的数据修改都必须确保可以同步修改缓存与数据库中的数据。
2. 准确识别特定业务流程中,可以**使用缓存获取数据的时间**有多长。因为有些缓存数据比如一次REST请求中多个流程都需要使用的数据只可以在单次业务流程中使用不能跨业务流程使用。
好了,以上就是三种典型的数据种类的缓存设计思路了。这里你需要注意的是,使用缓存一定是以性能优化为目的,因此,你还需要使用**评估模型**来分析缓存是否达到了性能优化的目标。
那么具体是什么评估模型呢?我们来看一下这个性能评估模型的公式:**AMAT = Thit + MR \* MP**。其中:
* AMATAverage Memory Access Time代表的是平均内存访问时间
* Thit是指命中缓存之后的数据访问时间
* MR是指访问缓存的失效率
* MP是指缓存失效后系统访问缓存的时间与访问原始数据请求的时间之和。
另外这里你可能会注意到AMAT与原始数据访问之间的差值代表的就是使用缓存所带来的访问速度的提升。而在一些缓存使用不当的场景下增加的缓存机制很可能会造成数据访问速度下降的情况。所以接下来我就通过真实的缓存设计案例来带你理解如何正确地使用缓存以此帮助你更好地提升系统性能。
## 缓存设计的典型使用场景
好,在开始介绍之前呢,我还想给你说明一下,在真实的业务中,缓存设计的场景其实有很多,这里我的目的主要是让你明确缓存设计的方法。因此,我会从两个比较典型的案例场景入手,来带你理解缓存的使用。
### 如何做好静态页面的缓存设计?
在Web应用服务中一个重要的应用场景就是静态页面的缓存使用。这里的静态页面是指一个网站内所有用户看到的都是一样的页面除非重新部署否则一般不发生变更比如大部分公司官网的首页封面等。
通常静态页面的访问并发量是比较大的,如果你不使用缓存技术,**不仅会造成用户响应时延比较长,而且会对后端服务造成很大的负载压力**。
那么针对静态页面,我们在使用缓存技术时,可以通过将静态缓存放到距离用户近的地方,来减少页面数据在网络上的传输时延。现在,我们来看一个针对静态页面使用缓存设计的示意图:
![](https://static001.geekbang.org/resource/image/3c/c5/3c1903a94f842387a5d160a62a9fe0c5.jpg)
如图上所示针对静态网页首先你就可以在软件后端服务的实例中使用缓存技术从而避免每次都要重新生成页面信息。然后由于静态网页属于不变性数据所以你可以使用内存或文件级缓存。另外针对访问量非常大的静态页面为了进一步减少对后端服务的压力你还可以将静态页面放在网关处然后利用OpenResty等第三方框架增加缓存机制来保存静态页面。
除此之外,在网页中很多的静态页面或静态资源文件,还需要使用浏览器的缓存,来进一步提升性能。
**注意**,这里我并不是建议你针对所有的静态页面,你都需要设计三层的缓存机制,而是你要知道,在软件设计阶段,一般就需要考虑如何做静态页面的缓存设计了。
### 后端服务如何设计数据库的多级缓存机制?
还有一个典型的缓存场景是针对数据库的缓存。现在的数据库通常都是分布式存储的,而且规模都比较大,在针对大规模数据进行查询与分析计算时,都需要花费一定的时间周期。
因此,我们可以先识别出这些计算结果中可以使用缓存机制的数据,然后就可以使用缓存来提升访问速度了。下面是一张针对数据库缓存机制的原理图:
![](https://static001.geekbang.org/resource/image/0e/ff/0ea7c9ca30aef5d38fe63c75151e37ff.jpg)
从图上我们可以看到内存级Cache、分布式Cache都可以作为数据计算分析结果的缓存。而且不同级的缓存访问速度是不一样的内存级的Cache访问速度可以到微秒级别甚至更好分布式Cache访问速度通常可以小于毫秒级别而针对原生数据库的查询与分析通常是大于毫秒级别的。
因此,在具体设计缓存机制的时候,你就需要依据前面我介绍的缓存使用原理,识别出数据类型,然后选择并设计缓存实现机制。
另外,在使用缓存机制实现访问速度优化的过程中,我们的**主要关注点是不同层级缓存所带来的访问速度提升**而在这里不同层级缓存也是可以在一个数据库中的。比如在我参与设计的一个性能优化项目中其Cache策略就是使用MongoDB中的另外一个Collection集合来作为缓存查询分析以此优化性能。
所以,你在做缓存设计时,关注点应该放到不同的数据种类,以及不同层级缓存的性能评估模型上,而不是只关注数据库。只有这样,你才能设计出更好、更优的性能缓存方案。
## 小结
今天,我重点介绍了缓存技术的使用原理和典型应用场景,当你在进行软件业务系统性能设计时,可以结合今天学习的内容,识别出系统中各种可缓存的数据类型,然后有针对性地设计缓存方案,并且还可以根据评估模型,来进行前期的性能验证分析。
另外,在具体的缓存技术实现中,比如缓存替换算法、缓存失效策略等,通常这部分能力是内置于缓存库的配置选项当中的,或者选用第三方库即可,需要自定义设计算法的实现场景很少。所以这里你需要重点做的,就是选择合适的配置和策略即可。
## 思考题
对于一致性要求比较高的数据信息在微服务多实例架构中我们是否可以选择使用内存Cache呢
欢迎在留言区分享你的答案和思考。如果觉得有收获,也欢迎你把今天的内容分享给更多的朋友。