类CDN网站一度霸占Alexa域名排行榜前100。过去,一些小网站根本不需要使用CDN,或者根本负担不起它的价格,但近几年这种现象发生了很大的变化。CDN市场上有很多按次付费和非企业的提供商,这使得CDN成为了一项人人都能负担得起的服务。本文介绍如何使用这个简单易用的缓存服务。要使用内容分发网络(CDN),您需要正确理解HTTP响应头:哪些标签与HTTP响应头相关?它们是如何工作的?如何使用它们?我将在本文中回答这些问题。这篇文章的内容并不像教科书那么精确。其实在某些情况下,为了描述的清楚简洁,我会根据自己的理解对一些问题进行简化。本文将通过一些实际例子来介绍缓存理论。在这篇文章的基础上,还会写一些文章来介绍如何使用CDN作为一些指定的CMS或框架的缓存层。为什么要使用CDN?CDN是一个全球分布的网络,可以将网站内容更快地传送到世界各地的特定位置,通常远离实际的内容服务器。例如,您的网站托管在爱尔兰,而您的用户从澳大利亚访问它。这时候,当你的用户访问你的网站时,延迟会很高。将你的(静态)数据放在澳大利亚,用CDN将大大改善用户访问网站的体验。但是,CDN的使用不限于此。其实CDN可以理解为一个普通的缓存,比如代理缓存(edgecache)。即使您不关心用户的具体地理位置,您也应该考虑使用CDN的代理缓存来改善您的用户体验。为什么要使用代理缓存?简而言之,代理缓存缓存您网站的某些页面,并通过缓存非常快速地提供“静态”内容。举个简单的例子,假设您有一个博客,其起始页列出了所有最近的博客。为此,PHP脚本从数据库中获取最新的文章实体,将它们转换为HTML结果页面,并将它们返回给用户。因此,对于一个请求(访问)包含:一次PHP执行+一组数据库查询。对于1000个请求(访问),它包含:1000次PHP执行+1000组数据库查询。每个PHP执行都需要CPU、内存和I/O操作,数据库操作也是如此。请求的需求与访问用户的数量成线性比例。听上去怎么样?与其说不是,是因为这种线性关系是有限制的:磁盘只能提供一定量的I/O,CPU和内存都不是最优的。这样发展到一定程度,就是说当某个资源到了瓶颈的时候,就会出现一个问题:你的网站访问起来会很慢,甚至所有人都访问不了。其实此时其他资源还没有完全填满。诚然这时候你可以扩大你的硬件规模来突破这个瓶颈,但这会让项目变得更复杂,成本也更高。实际上有更简单和更便宜的解决方案。中间加一层代理缓存,会减少对你的资源限制。以前面的例子为例,使用代理缓存只需要第一次请求执行一个PHP脚本,查询数据库,生成一个HTML结果页面。所有后续请求都会从这个缓存中获取内容,读取缓存几乎和直接从内存中读取一样快。也就是说,上面的线性刻度瓶颈问题解决了!100个用户或1000个用户都无所谓,仍然只有1次PHP执行、1次数据库查询和1次结果页面生成。CDN!=CDNCDN也有不同的类型。站长可能很好奇数据是怎么存储的?它存储在哪里?数据在CDN上是如何分布的?它是如何分布的?这篇文章不是写给站长的,而是写给开发者的,所以我只能告诉你有“经典CDN”和“点对点CDN”,后者是现在主流的方式。对于开发者来说,他们更感兴趣的是如何把数据放到CDN中,而不是拿到数据到CDN之后做什么。说起来,有pushCDN和pullCDN之分。顾名思义,“推送CDN”就是你要向CDN提供内容;“pullCDN”是指如何从CDN中获取内容。本文将主要介绍拉取式CDN,因为很多时候拉取式CDN更容易使用,可以毫不费力地集成到现有网站中。拉取式CDN如何工作?让我们举个例子,假设您有一个可访问的网站https://www.foobar.tld。在这种情况下,域名http://www.foobar.tld将放置在拉取CDN服务器上,而不是您的Web服务器上。CDN充当您的Web服务器的代理。还有一个未公开的域名指向实际的网络服务器。假设在这个例子中是direct.foobar.tld,实际的Web服务器称为origin。此CDN将接受所有请求。如果其缓存中有结果,则直接返回给用户,否则,请求将托管在您实际的Web服务器上,然后将返回的结果缓存起来以供以后的请求使用,并将结果返回给用户同时。最简单的拉取CDN操作流程如下:获取一个页面的请求,这个页面:http://www.foobar.tld/some/page使用some/page作为缓存key检查缓存中是否存在,然后直接从缓存中返回结果给用户,如果不在缓存中,则请求http://direct.foobar.tld/some/page,将返回结果以some/page为key写入缓存,并将结果返回给用户上面的静态内容VS动态内容这个过程非常适合完全静态的内容。静态内容是指如果用户访问同一个URL地址,返回的所有数据都是一样的。例如,CSS文件就具有这样的特性。http://www.foobar.tld/public/css/main.css这个文件是一个普通的文件,对于所有访问该网站的用户来说都是一样的,所以特别适合Cache它。与静态文件相反的是动态文件。内容在运行时确定也很常见。比如多语言问题,需要根据浏览器语言返回内容。还有一些与“用户会话”相关的内容。例如,当用户登录时,“登录”按钮应该替换为“注销”按钮。你绝对不希望它被缓存。这些高度活跃的内容(例如每小时或更短时间更新的页面)无法缓存,或者不能在缓存中停留太久。这就是缓存变得有趣的地方,它不难理解和实现。绝大多数拉取式CDN以“每页”缓存的形式处理动态内容。实现此目的的一种简单方法是HTTP响应缓存标头。首先你要知道缓存头有“旧版”和“新版”两种,也就是说一开始并没有设计成现在的版本,还有一个逐渐演变的过程。较新的版本指的是HTTP/1.1,而较旧的版本指的是HTTP/1.0。它的选项之多,让每个人都为之头疼。我认为这是人们不愿意使用缓存头的最重要原因。言归正传,我们只关注ETag和Cache-Control这两个标签。大多数CDN还支持旧版本(Expires、Pragma和Age),但这些仅用于向后兼容。ETag标头我们从最简单的ETag开始:它是文档版本的标识符。通常是内容的MD5值,但也可以包含其他内容,代表文档的版本/日期,如:1.0或2017-02-27。这里要注意一点,一定要用双引号括起来,比如:ETag:"d3b07384d113edec49eaa6238ad5ff00"。二次认证现在让我们考虑ETags的实际应用:二次认证。暂时不考虑之前的proxy+source架构模式,只考虑简单的client-server模式。如下图所示:假设客户端请求http://www.foobar.tld/hello.txt,然后服务端返回如下响应内容:#REQUESTGET/hello.txtHTTP/1.1Host:www.foobar.tld#RESPONSEHTTP/1.1200OKDate:Sun,05Feb201712:34:56UTCServer:ApacheLast-Modified:Sun,05Feb201710:34:56UTCETag:"8a75d48aaf3e72648a4e3747b713d730"Content-Length:8Content-Type:twoTheretextchar/plain设置了UTC响应中有趣的头标识:一个是ETag,内容的MD5值,另一个是Last-Modified,也就是hello.txt文件最后一次被修改的时间。这里双因素认证就派上用场了:当客户端短时间内再次访问上述URL时,客户端浏览器会使用If-*请求头。比如If-None-Match,检查ETag的内容是否发生变化。也就是说,如果ETag发生变化,客户端会收到一个完整的新响应;如果ETag没有改变,客户端会收到一个标识符,表明内容没有改变。GET/hello.txtHTTP/1.1If-None-Match:"8a75d48aaf3e72648a4e3747b713d730"Host:www.foobar.tld如果ETag没有改变,服务器会返回:HTTP/1.1304NotModifiedDate:Sun,05Feb201712:34:57UTCServer:ApacheLast-Modified:Sun,05Feb201710:34:56UTCETag:"8a75d48aaf3e72648a4e3747b713d730"Content-Length:8Content-Type:text/plain;charset=UTF-8如上所示,这次服务器的响应不是200ok,而是304NotModified,这意味着它会跳过数据包主体,让客户端直接去自己的缓存中获取数据。本例中包体内容为body,比较小,效果不明显。但是想象一下,如果是一个很大的内容,或者是一个非常复杂的动态生成的内容,价值会很大。作为开发者,你可能会想:“没那么好用,我还得掌握IF类头标签,比以前还麻烦”。别着急,这只是对共享缓存的介绍,这就是代理缓存的由来。先看原架构:
