来,先上文章的目录,让大家可以对 缓存 这块知识先建立一个系统性的认知,然后我会按点逐个击破,读者们也可以按需阅读哈!
1. 什么是缓存(What)
维基百科对缓存的定义是:
In computing, a cache is a hardware or software component that stores data so that future requests for that data can be served faster; the data stored in a cache might be the result of an earlier computation or a copy of data stored elsewhere.
简而言之,缓存就是存储数据副本或计算结果的组件,以便后续可以更快地访问。
在计算中,缓存是一个高速数据存储层,其中存储了数据的子集,且通常是短暂性存储,这样日后再次请求该数据时,直接读缓存会比重新计算结果或读数据存储更快。通过缓存,你可以高效地重用之前检索或计算的数据。
2. 为什么要使用缓存(Why)
从定义上可以看出所谓缓存其实是其他数据的副本,使用缓存是为了更快地检索或计算数据。
1)硬件层面:如CPU中的高速缓存
从上图可以看出CPU(蓝色)和内存(粉红色)之间存在巨大的性能差距。
在CPU访问数据和指令遵循计算机系统的局部性原理:
-
时间局部性:CPU 通常使用的许多数据会被多次使用。
-
空间局部性:CPU 使用的许多数据通常在物理上接近以前使用的数据。
使用高速缓存可以弥补CPU和内存之间的性能差异,减少 CPU 浪费计算时间等待内存数据。
2)软件层面
-
为缓解 CPU 压力而做缓存:比如把方法运行结果存储起来、把原本要实时计算的内容提前算好、把一些公用的数据进行复用,这可以节省 CPU 算力,顺带提升响应性能。
-
为缓解 I/O 压力而做缓存:比如把原本对网络、磁盘等较慢介质的读写访问变为对内存等较快介质的访问,将原本对单点部件(如数据库)的读写访问变为到可扩缩组件(如缓存中间件)的访问,顺带提升响应性能。
3)产品层面
是否可以解决用户的痛点问题决定着用户会不会一开始尝试使用某款产品,是否有极致的用户体验影响用户会不会持续使用某款产品。在业务中使用缓存的目的就是通过扩大系统吞吐量、减少时延和响应时间来优化用户体验,适当的性能优化可以提升体验,增强用户粘性。
在现有的互联网应用中,缓存的使用是一种能够提升服务快速响应的关键技术,也是产品经理无暇顾及的非功能需求,需要在设计技术方案时对业务场景,具有一定的前瞻性评估后,决定在技术架构中是否需要引入缓存解决这种这种非功能需求。
3. 什么时候使用缓存(When)
缓存不是架构设计的必选项,也不是业务开发中的必要功能点,只有在业务出现性能瓶颈,进行优化性能的时候才需要考虑使用缓存来提升系统性能。并非所有的业务场景都适合用缓存,读多写少、不要求一致性、时效要求越低、访问频率越高、对最终一致性和数据丢失有一定程度的容忍的场景才适合使用缓存,缓存并不能解决所有的性能问题,倘若滥用缓存会带来额外的维护成本,使得系统架构更复杂更难以维护。
虽然缓存适用于各种各样的案例,但要充分利用缓存,需要进行一定的规划。所以在决定是否缓存一段数据时,请考虑以下问题:
-
使用缓存值是否安全? 相同的数据在不同的上下文中可能有不同的一致性要求。例如电商系统中,在线结账期间,必须知道商品的确切价格,因此不适合使用缓存,但在其他页面上,价格晚几分钟更新不会给用户带来负面影响。
-
对于该数据而言,缓存是否高效? 某些应用程序会生成不适合缓存的访问模式;例如,扫描频繁变化的大型数据集的键空间。在这种情况下,保持缓存更新可能会抵消缓存带来的所有优势。
-
数据结构是否适合缓存?例如: 以单条数据库记录形式缓存数据通常足以提供显著的性能优势。但有些时候,数据最好以多条记录组合在一起的格式进行缓存。缓存以简单的键值形式存储,因此您可能还需要以多种不同格式缓存数据记录,以便按记录中的不同属性进行访问。
另外把缓存当做存储来使用是一件极其致命的做法,这种错误的认识,将缓存引入系统的那一刻起就意味着已经让系统走上了危险的局面,只有对缓存的使用边界有深刻的理解,才能尽可能减少引入缓存带来的副作用。
4. 谁会使用缓存(Who)
其实缓存的思想随处可见,日常生活中人们都会有意无意地用到。比如你会把常看的书放到书桌上,这样你可以更快地拿到它们,而受限于桌面空间,不常看的书就要放在空间更大的书柜里了,等到要看的时候再从书柜里拿出来放到书桌上。这里的书桌其实就是一种缓存介质了。
对于程序员来说,缓存更是家常便饭了。有哪些程序员会用到缓存技术呢?
1)硬件开发工程师:比如CPU缓存、GPU缓存和数字信号处理器(DSP)缓存等。
2)软件开发工程师:
-
客户端开发:比如第4章节讲到的页面、浏览器、APP缓存技术都和客户端息息相关。
-
后端开发:比如服务端本地缓存、redis和memcached充当数据库缓存、静态页面缓存等。
-
分布式开发:现在分布式大行其道,分布式缓存必然是分布式开发中绕不开的一环,6.10节的分布式缓存也是我们要重点讲解的。
-
操作系统开发:操作系统内核负责管理磁盘缓存,比较典型的就有主存中的页面缓存技术。
5. 哪些地方会使用缓存(Where)
5.1 缓存分类
按照不同的维度,可以对缓存分门别类,如下图所示。
下面我着重按照 缓存所处链路节点的位置 来系统梳理出不同类型的缓存应用。
1. 客户端缓存
HTTP 协议的无状态性决定了它必须依靠客户端缓存来解决网络传输效率上的缺陷。由于每次请求都是独立的,服务端不保存此前请求的状态和资源,所以也不可避免地导致其携带有重复的数据,造成网络性能降低。HTTP 协议对此问题的解决方案便是客户端缓存。
常见的客户端缓存有如下几种:
1) 页面缓存
页面缓存是指将静态页面获取页面中的部分元素缓存到本地,以便下次请求不需要重复资源文件,h5很好的支持的离线缓存的功能,具体实现可通过页面指定manifest文件,当浏览器访问一个带有manifest属性的文件时,会先从应用缓存中获取加载页面的资源文件,并通过检查机制处理缓存更新的问题。
2) APP缓存
APP可以将内容缓存到内存或者本地数据库中,例如在一些开源的图片库中都具备缓存的技术特性,当图片等资源文件从远程服务器获取后会进行缓存,以便下一次不再进行重复请求,并可以减少用户的流量费用。
客户端缓存是前端性能优化的一个重要方向,毕竟客户端是距离“用户”最近的地方,是一个可以充分挖掘优化潜力的地方。
3) 浏览器缓存
浏览器缓存通常会专门开辟内存空间以存储资源副本,当用户后退或者返回上一步操作时可以通过浏览器缓存快速的获取数据,减少页面加载时间和带宽使用。在 HTTP 从 1.0 到 1.1,再到 2.0 版本的每次演进中,逐步形成了现在被称为“状态缓存”、“强制缓存”(许多资料中简称为“强缓存”)和“协商缓存”的 HTTP 缓存机制。在HTTP 1.1中通过引入e-tag标签并结合expire、cache-control两个特性能够很好的支持浏览器缓存。
下面我们用思维导图理解 强制缓存 和 协商缓存,请注意体会梳理思路!
对于Etag的补充说明:
HTTP 服务器可以根据自己的意愿来选择如何生成这个标识,比如 Apache 服务器的 Etag 值默认是对文件的索引节点(INode),大小和最后修改时间进行哈希计算后得到的。
Etag 是 HTTP 中一致性最强的缓存机制,比如,Last-Modified 标注的最后修改只能精确到秒级,如果某些文件在 1 秒钟以内,被修改多次的话,它将不能准确标注文件的修改时间;又或者如果某些文件会被定期生成,可能内容并没有任何变化,但 Last-Modified 却改变了,导致文件无法有效使用缓存,这些情况 Last-Modified 都有可能产生资源一致性问题,只能使用 Etag 解决。
Etag 却又是 HTTP 中性能最差的缓存机制,体现在每次请求时,服务端都必须对资源进行哈希计算,这比起简单获取一下修改时间,开销要大了很多。Etag 和 Last-Modified 是允许一起使用的,服务器会优先验证 Etag,在 Etag 一致的情况下,再去对比 Last-Modified,这是为了防止有一些 HTTP 服务器未将文件修改日期纳入哈希范围内。
扩展知识:内容协商机制
到这里为止,HTTP 的协商缓存机制已经能很好地处理通过 URL 获取单个资源的场景,为什么要强调“单个资源”呢?在 HTTP 协议的设计中,一个 URL 地址是有可能能够提供多份不同版本的资源,比如,一段文字的不同语言版本,一个文件的不同编码格式版本,一份数据的不同压缩方式版本,等等。因此针对请求的缓存机制,也必须能够提供对应的支持。为此,HTTP 协议设计了以 Accept(Accept、Accept-Language、Accept-Charset、Accept-Encoding)开头的一套请求 Header 和对应的以 Content-(Content-Language、Content-Type、Content-Encoding)开头的响应 Header,这些 Headers 被称为 HTTP 的内容协商机制。与之对应的,对于一个 URL 能够获取多个资源的场景中,缓存也同样也需要有明确的标识来获知根据什么内容来对同一个 URL 返回给用户正确的资源。这个就是 Vary Header 的作用,Vary 后面应该跟随一组其他 Header 的名字,比如:
HTTP/1.1 200 OK Vary: Accept, User-Agent
以上响应的含义是应该根据 MIME 类型和浏览器类型来缓存资源,获取资源时也需要根据请求 Header 中对应的字段来筛选出适合的资源版本。
2. 网络缓存
网络缓存位于客户端以及服务端中间,通过代理的方式解决数据请求的响应,降低数据请求的回源率。回源率又分为以下两种:
-
回源流量比:回源流量是代理服务器节点请求源服务器资源时产生流量。回源流量比=回源流量/(回源流量+用户请求访问的流量),比值越低,性能越好。
-
回源请求数比:指代理服务器节点对于没有缓存、缓存过期(可缓存)和不可缓存的请求占全部请求记录的比例。
网络缓存常见的代理形式分为两种:web代理缓存、边缘缓存。
在介绍网络缓存之前我们先了解下前置知识----两种服务器代理方式:正向代理、反向代理。
正向代理其实就是:客户端通过代理服务器与源服务器进行非直接连接。客户端可以感知到代理服务器的存在,对源服务器透明(源服务器感知不到客户端的存在),如下图所示
通信时客户端和代理服务器要设置好代理协议,比如Socks协议或者是HTTP协议(可以设置4.1节浏览器缓存中的HTTP Header)。
那正向代理有什么作用呢?
-
提高访问速度:通常代理服务器都设置一个较大的缓冲区,当有外界的信息通过时,同时也将其保存到缓冲区中,当其他用户再访问相同的信息时, 则直接由缓冲区中取出信息,传给用户,以提高访问速度。
-
控制对内部资源的访问:如某大学FTP(前提是该代理地址在该资源的允许访问范围之内)使用教育网内地址段免费代理服务器,就可以用于对教育网开放的各类FTP下载上传,以及各类资料查询共享等服务。
-
过滤、调整内容:例如限制对特定计算机的访问、压缩请求包、改变请求包的语言格式等。
-
隐藏真实IP:通过代理服务器隐藏自己的IP,但更安全的方法是利用特定的工具创建代理链(如:Tor)。
-
突破网站的区域限制:通过代理服务器访问一些被限制的网站。
反向代理就是:客户端通过代理服务器与源服务器进行非直接连接。客户端只会得知反向代理的IP地址,而不知道在代理服务器后面的服务器集群的存在。如下图所示
反向代理有什么作用呢?
-
对于静态内容及短时间内有大量访问请求的动态内容提供缓存服务。
-
对客户端隐藏服务器(集群)的IP地址。
-
安全:作为应用层防火墙,为服务器提供基于Web的攻击行为(例如DoS/DDoS)的防护,更容易排查恶意软件等
-
为后端服务器(集群)统一提供加密和SSL加速(如SSL终端代理)。
-
负载均衡:若服务器集群中有机器负荷较高,反向代理通过URL重写,把请求转移到低负荷机器获取与所需相同的资源。深入了解负载均衡强烈推荐看这篇干货: