使用 HTTP 缓存机制提升系统性能
摘要
HTTP缓存机制定义在HTTP协议标准中,被现代浏览器广泛支持,同时也是一个用于提升基于Web的系统性能的广泛使用的工具。本文讨论如何使用HTTP缓存机制提升基于Web的系统,以及如何避免误用。同时也讨论了几种常见的应用场景。
一般性的缓存机制
抛开具体的技术实现,一般性来说在服务器-客户端模式下,缓存机制通常都是这么工作的,如图所示:
当客户端需要获取某个资源的时候,首先查看客户端本地缓存中是否存在此资源。如果缓存中不存在这个资源,则必须要从服务器端把资源拉到本地。如果存在,则还需要判断缓存的资源是否过期。如果没过期,则从缓存中直接获取资源,不需要再从服务器端获取。如果过期了,则必须从服务器端获取最新资源。还有一种很可能出现的情况是客户端不知道服务器端的资源更新了没有,即不知道缓存中的资源是否过期。这种情况下,客户端还是要向服务器端发请求的,不过在请求中附加了一些信息。这些信息用于服务器判断客户端缓存中的资源是否过期,例如最后修改日期。服务器端如果发现缓存的资源并未过期,那么只需要返回一个信号,告诉客户端从缓存获取即可,并不需要返回资源的内容。
HTTP协议定义的缓存机制
当Web浏览器向服务器请求资源时(如下图所示),服务器可以在响应中包含缓存相关的HTTP头(HTTP Header)。这些HTTP Header会告诉浏览器是否以及如何缓存资源。
关于缓存的HTTP Headers
下面列出的是关于缓存的HTTP Headers,它们有些是出现在响应(Response)中,有些在请求(Request)中。
| HTTP Header | 出现在 |
|----------------------|-----------------|
| Cache-Control | 响应 |
| Expires | 响应 |
| Last-Modified | 响应 |
| If-Modified-Since | 请求 |
| ETag | 响应 |
| If-None-Match | 请求 |
下面会一一介绍这些Header的用法,有一些是要配套使用的。
Cache-Control
在Chrome调试器中,可以在Response中找到Cache-Control,例如下图中的服务响应就包含一个Cache-Control头,其值为"public, max-age=30"
Cache-Control的值一般为 [public | private | no-cache, no-store ], [max-age=n] ([A | B] 表示可以从A和B中取一个),"max-age=n"表示n秒后资源失效。
public是指资源应该被缓存,并且中间经过的代理服务器(假如有的话)也应该缓存这个资源。隐含的意思是,其他用户可能也能分享这个资源的缓存。
private是指资源应该被缓存,但是只能被客户端的浏览器缓存。
no-cache, no-store是指示资源不应该被缓存。
max-age是指缓存多长时间,单位是秒。
下面举几个常用的例子:
-
Cache-Control : public, max-age=60
告诉浏览器,当前资源应该被缓存,同时也告诉代理服务器(假如有的话)也可以缓存这个资源。缓存应该在60秒后过期。 -
Cache-Control : private, max-age=60
告诉浏览器,当前资源应该被缓存,同时也告诉代理服务器(假如有的话)不要缓存这个资源。缓存应该在60秒后过期。 -
Cache-Control : no-cache, no-store
告诉浏览器以及代理服务器,不要缓存这个资源。
Expires
是相对于max-age的另一种指示过期时间的方式。max-age表示多少时间后过期,而Expires表示在某个日期时间点后过期。换种通俗的说法,max-age相当于说“保质期六个月”,而Expires是说“在此日期之前”饮用。
注意:如果max-age和Expires同时存在,应该以max-age为准。但是通常可以把两个都设置成一个时间点。例如下面的JavaScript代码所示:
// Set the max age to 1 year.
res.setHeader('Cache-Control', 'public, max-age=31536000');
// Set expire date to 1 year later.
var currentDate = new Date((new Date()).getTime() + 3600 * 1000 * 24 * 365);
res.setHeader('Expires', currentDate.toUTCString());
限制:max-age和Expires设置的缓存过期时间最多为一年(365天),如果多于这个值则浏览器有可能会忽略。
Last-Modified 和 If-Modified-Since
Last-Modified出现在响应中,告诉浏览器当前资源的最后修改时间,例如:
Last-Modified: Mon, 03 Jan 2011 17:45:57 GMT
而If-Modified-Since出现在下次请求中,询问服务器当前缓存的资源是否已经过期,例如:
If-Modified-Since: Mon, 03 Jan 2011 17:45:57 GMT
如果资源没有过期,则服务器应该返回304,不需要返回资源的内容。如果已过期,则服务器应该返回资源的内容到浏览器,并且返回200 HTTP状态码。
Last-Modified和If-Modified-Since配套使用,可以在保证不会误用缓存里的过期资源的前提下,减少服务器向浏览器发送的数据量,以及由于服务器在缓存未过期的情况下只需要返回304 HTTP状态码,而不需要返回整个资源的内容,也给了服务器优化的机会。
ETag 和 If-None-Match
Last-Modified 和 If-Modified-Since是用日期时间判断一个缓存的资源是否有效。ETag和If-None-Match则是用内容摘要作为判定的依据。内容摘要是指为一个资源的内容产生一串比较短的数字,当内容变化时,产生的数字串也会改变。内容摘要的算法有很多种,较常见的是SHA-1哈希算法、CRC32等。
ETag包含在服务器的响应中,为内容摘要。在下一次请求中,If-None-Match的值为上次的ETag的值。服务器根据If-None-Match的值(即内容摘要)判断缓存的资源是否有效。
304状态码
304状态码("Not Modified")表示资源(相对于缓存过的)没有被修改过。
检查请求与响应以及缓存工作情况
这里仅以Google Chrome为例,介绍如何检查请求与响应,以及检查浏览器缓存是否在发挥作用。其他的浏览器平台也是类似的。
打开Google Chrome,按F12,出现调试界面,切换到Network页。
你会看到很多请求,包括URL、方法、状态等等,注意到上图有一栏显示的是"(from cache)",已经用红框标出。这表示这些资源都是从缓存里获取的,没有经过服务器。清除Cache之后,看到的会是浏览器从服务器端获取资源,如下图所示:
点击其中的一项,显示出请求和响应的细节信息。
跟HTTP缓存相关的Header已经被红框标出,这里真正发挥作用Cache-Control和Expires,这两个让浏览器根本不用发请求。浏览器只要发请求就会产生一定的延时,因为即使服务器返回304这样简单的数据,在底层也还需要TCP/IP层的握手等各种操作,而且HTTP协议也会有额外开销。
应用场景
下面罗列了几种场景,并讨论如何设定缓存策略。
静态资源
对于那些不经常改变的静态资源,比如CSS、图片、动画等,应尽可能地利用缓存。因为这些资源通常很大而且几乎每个页面可能都会用到,缓存会大大提高系统效率。对于这些资源,响应中应该包含如下内容:
Cache-Control:public; max-age=31536000
Expires: Mon, 25 Jun 2013 21:31:12 GMT
max-age=31536000意味着31536000秒(也就是一年)后缓存失效。这里尤为注意不能设置成多于一年,因为RFC上限制了最大只能是一年,超过一年的情况不同的浏览器处理策略不同,有些直接就忽略了Cache-Control。
动态资源
对于动态内容,需要依据内容的实际情况,定义合适的max-age。例如对于SNS网络中的时间线通常可以设置成几秒。
私有内容
对于需要登录才能访问到资源,Cache-Control应该设置成private以禁止代理服务器缓存这些资源,否则会威胁信息安全。
禁用缓存
某些情况下需要禁止使用缓存,则应该把Cache-Control设置成"no-cache, no-store",如下所示。
Cache-Control:no-cache, no-store