在商业世界中,人们常说“现金为王”。然而,在技术世界里,我们却说“缓存为王”。 从浏览器到应用前端、应用后端、数据库,每一层都可以通过缓存来显著地提高系统的扩展能力,改善系统的响应能力,同时减少系统的负担。
互联网平台上的内容可以分为静态和动态两种。静态内容指那些不经常改变的文本和图像。动态内容是指随着时间的推移,不断变化的内容。本文主要讨论静态内容实现缓存的七种不同方法。
1. 利用 CDN 实现缓存
CDN,即内容分发网络,是通过骨干网络把一组计算机连接起来,存储客户数据或内容的副本。通过在不同的网络,策略性地通过部署边缘服务器和应用大量的技术和算法,把用户的请求指定到最佳响应节点上。这种优化的逻辑可以是基于最少网络跳转数量、最高系统可用性或最少的请求数量。这种优化常常聚焦在减少最终用户、请求者或服务可以感知的响应时间。
正如本例所示,在网站服务器前使用CDN的效果是,CDN负责处理所有的请求,只有当需要查询缓冲内容是否更新时才会访问源服务器。因此,只需要购买少量低配置的服务器和网络带宽,以及少数维护基础设施的人员。无论网站的网页是动态还是静态,都可以考虑加入CDN形成混合缓存。该层缓存可以提供快速交付的好处,通常有非常高的可用性,而且网站服务器处理更少的流量。
2. 利用 HTTP 头来灵活管理缓存
HTTP 头提供了有关代理缓存的有效控制,这些 HTTP 头在 HTML 中看不到,而是由网络服务器或生成页面的代码动态生成。通过服务器配置或代码来控制。一个典型的 HTTP 响应头看起来可能像这样:
HTTP Status Code: HTTP/1.1 200 OK
Date: Thu, 21 Oct 2015 20:03:38 GMT
Server: Apache/2.2.9 (Fedora)
X-Powered-By: PHP/5.2.6
Expires: Mon, 26 Jul 2016 05:00:00 GMT
Last-Modified: Thu, 21 Oct 2015 20:03:38 GMT
Cache-Control: no-cache
Vary: Accept-Encoding, User-Agent
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
与缓存最相关的头是 Expires和Cache-Control 。Expires 实体头字段提供响应有效期信息。如果想要把响应标记为“永不过期”,源服务器就应发送从响应时间算起一年后的日期。在前面例子中,注意到 Expires 头标识日期为 2017 年 5 月 12 日 05:00GMT。如果今天是 2017 年 4 月 12 日,请求的页面将在大约一个月后过期,浏览器应在那个时候从服务器获取数据以刷新内容。
Cache-Control 通用头字段,用于按 RFC2616 第 14 节定义的 HTTP1.1 协议定义指令,沿请求/响应链的所有缓存机制必须遵守这些指令。该头可以发出许多指令,包括 public、private,、no-cache 和 max-age。如果响应同时包含Expires头和 max-age 指令,即使 Expires 限制较多,max-age 指令的优先级同样高过 Expires 头。以下是一些 Cache-Control 指令的定义:
public—响应可以由任何缓存、共享或非共享缓存来处理。
private—响应针对单用户,不能放在由共享缓存。
no-cache—在与源服务器确认之前,不得使用缓存来满足后续的其他请求。
max-age—如果当前数值大于在请求时给定的值(秒),那么响应过时。
设置 HTTP 头有几种方式,包括通过网络服务器和代码。Apache2.2 的配置设置在 httpd.conf 文件。Expires 头要求把 mod_expires 模块添加到 Apache。Expires 模块有三条基本指令。第一条 ExpiresActive 告诉服务器激活该模块。第二条指令 ExpiresByType 设置 Expires 服务特定类型的对象(如图片或文本)。第三个指令 ExpiresDefault 设置如何处理所有未指定类型的对象。参见下面的代码示例:
ExpiresActive On
ExpiresByType image/png "access plus 1 day"
ExpiresByType image/gif "modification plus 5 hours"
ExpiresByType text/html "access plus 1 month 15 days 2 hours"
ExpiresDefault "access plus 1 month"
在 HTTP 设置 Expires、Cache-Control 和其他头的另外一种方法是在代码中实现。PHP 直接利用 header() 命令发送原始的 HTTP 头。在任何输出前必须通过 HTML 标签或从 PHP 代码调用 header() 命令。关于头设置,参见下面的 PHP 示例代码。其他语言也有类似的头设置方法。
<?php
header("Expires: 0");
header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT");
header("cache-control: no-store, no-cache, must-revalidate");
header("Pragma: no-cache");
?>
最后一个主题涉及到调整网络服务器的配置,以优化其性能与可扩展性。keep-alives 或 HTTP 持久连接允许多个 HTTP 请求复用 TCP 连接。在 HTTP / 1.1 中,所有的连接都是持久的,大多数网络服务器默认允许保持连接。根据 Apache 文档记载,使用连接保持可以使 HTML 页面延迟减少了 50%。在 Apache 的 httpd.conf 文件中 keep-alives 的默认设置为打开,但 KeepAliveTimeOut 的默认值只设置为 5 秒。超时设置较长的好处是不必建立、使用和终结 TCP 连接就可以处理更多的 HTTP 请求,超时设置较短的好处是网络服务器的线程不会被捆绑住,可以继续服务其他请求。根据应用或网站的具体情况在两者之间寻找平衡点很重要。
有一个实际的例子,利用 AOL 研发的开源的网页测试工具 webpagetest.org ,对某网站做了一个测试。测试对象是一个运行在 2.2 版的 Apache HTTP 服务器上的简单 MediaWiki。
3. 利用 Ajax 实现缓存
2005 年杰西·詹姆斯·加勒特在他的文章《 Ajax :一种网络应用的新方法》中创造了 Ajax 这个术语。Ajax 是 Asynchronous JavaScript and XML 的缩写。虽然我们经常把它作为一种技术,但更为贴切的描述是一组技巧、语言和在浏览器(或客户端)上使用的方法,有助于为最终用户带来更丰富内容和更实时体验。
因为可以减少数据在网络上不必要的往复传递,从而使用户与浏览器之间的互动更容易,用户交互因此可以更迅速地发生。用户不用等待服务器的响应就可以放大或缩小图片,下拉菜单可以根据以前的输入预先安排好,当用户在搜索栏输入查询关键词时,就可以开始看到那些可能会感兴趣并起到引导作用的潜在搜索词。Ajax 的异步特性还可以帮助我们,在往客户端浏览器加载邮件时,可以根据用户的某些动作来判断是否要继续接收邮件,而不必等用户点击“下一页”按钮。
但是其中的一些动作不利于平台的扩展,以用户在网站上输入某个特定商品的搜索关键词为例。我们可能想用商品目录来填充搜索建议,即那些当用户键入搜索条件时出现的关键词。Ajax 可以通过用户后续的每个按键向服务器发送请求,根据已键入的词而返回搜索结果,并在不需要用户介入刷新浏览器的情况下,把搜索结果填充到下拉菜单。有可能返回的是基于用户不完全键入的字符串而获得的完整搜索结果!许多搜索引擎和电子商务网站都可以找到这种实施的例子。以每个后续按键为基础最终形成搜索服务器需要的查询语句,可能既昂贵也浪费我们的后台系统资源。例如,当用户输入 “Beanie Baby” 时,可能会带来连续 11 次的搜索,其实真正只需要一次。用户的体验可能很奇妙,但如果用户按键速度很快,在打完字前,实际上多达 8-10 次的搜索可能永远没有机会返回结果。
我们的目标是减少在网络上来回传输数据,以减少用户感知的响应时间和降低服务器的负载。因此,响应头中的 Expires 设置的有效期应足够长,这样,浏览器会在本地缓存第一次查询的结果,并在后续请求中反复使用。静态或半静态的对象,如公司商标或者简介图片,其有效期应该设置成几天或更长。某些对象的时间敏感性可能很强,如阅读好友的状态更新。在这些情况下,应该把 Expires 头设置为数秒甚至数分钟,以给用户实时的感觉,同时降低整体的负载。
数据集是静态甚至半动态的情况(例如,有限的或上下文敏感的产品目录)很容易解决。从客户端看,以异步的方式获取这些结果,然后缓存起来供同一客户端以后使用,或者更重要的是确保 CDN、中间缓存或代理存储它们,以利于其他的用户进行类似的搜索。