CDN 简介(二)

这个系列的文章主要参考《CDN 技术详解》,作为大陆第一本介绍 CDN 的,写的确实比较烂,根本就谈不上『详解』,但是『介绍』的性质已经达到了。因此,我会结合这本书以及自己的经验感受,皮毛的总结下 CDN 技术,因为很多具体的原体我也不是很清楚。

第 3 章 内容缓存工作原理及实现
互联网最终的就是 cache,CDN 也不列外。在 CDN 出现之前,有如下的一些技术来解决部分的问题:
1. scale up/scale out
这个当然目前还在使用,前者是提高服务器配置,后者就是直接加机器。不过这些不能很好的解决问题,尤其对于距离较远的用户。

2. mirroring
这个目前还是有不少网站在用的,就是做一个跟源站一模一样的 mirror,然后可以通过 IP 或者 DNS 解析来定向用户的访问,不过大部分时候是用户自己选择,你得把用户都当作傻逼,因此他们选错了非但不能起到加速的效果,反而由于跨了运营商而拖了后腿。其次就是这个由于是这个资源的 mirror,需要大量的资源的堆积。

3. cache
这个已经有 CDN 的雏形在里面了,减少重复的访问,节约带宽。

下面这些问题是一个网站发展过程中都会遇到的,CDN 能够很大程度上解决这些问题。
1. 无法及时满足用户的并发需求
2. 在没有 mirror 之前,远距离访问很痛苦
3. 有了 mirror,不能及时同步
4. 一个 mirror 失效,不能及时调度
5. 部署了多套 mirror 之后,成本会显著增加
6. 中心机器的 IP 暴露,安全性问题

cache 一般都是作为 proxy 使用的,说到 proxy 也就有正向、反向和透明三种模式。
正向在公司内部使用较多,主要是控制灵活,用户需要将 GW 设置为这个 cache 的 IP。这样可以节约带宽,上网行为审计,同时也可以加速网页访问。
反向一般部署在网站前面,对于用户来说,是透明的。这样第一比较安全,第二也能起到一定的 LB 作用,这时候就可以通过 GSLB 来对全网的 cache 设备做 LB。
透明跟正向类似,但是用户不需要设置 GW 为这个代理的 IP。当用户请求一个 url 时,其请求被透明,然后他检查是 hit 了还是 miss 了,如果 miss 则帮助用户取回数据然后返回给用户。

cache 最起码有下面几个好处:
1. 减少相应延时
2. 较少带宽
3. 减轻服务器压力

对于 CDN 来说,cache 主要还是指 web cache,更具体点就是 HTTP 的 cache。DB 也有,但是比较少。
HTTP 目前主要分为 HTTP 1.0 以及 1.1 版本,目前主流是 1.1 版本。HTTP 是一个无状态的协议,也就是说同一个客户端的本次请求跟上次的请求没有任何的关系,TCP 在每一次的 HTTP 请求和响应完成之后都会关闭,如果需要请求其他的资源,则需要新建一个 TCP 链接,所以 HTTP 1.0 是一个非持久性的连接。
上面这个是有明显问题的,当请求多了之后,TCP 的连接会造成巨大的开销,因此 HTTP 1.1 针对在 1.0 中 TCP 不能复用的情况,采用了更有效的持久性链接的方式。持久性的具体还分为 pipelining 以及 no pipelining 两种方式,前者可以连续发送多个请求,而这后则必须要在前一个请求的相应收到后才能发现该请求,服务端和客户端默认都使用 pipelining 的方式。如果不希望使用持久性,可以在服务端或者客户端将 response 或者 request header 的 connection 设置成 close,默认是 Keep-Alive。
具体的 request 以及 response 里面的信息不在这类介绍了。既然 HTTP 是无状态的,那么如果遇到登录等需要输入密码的情况,就需要通过一种机制来让用户不要再次输入密码,这个可以通过 cookie 和 session 实现。
用户输入 URL 发送请求时,会先看本地是否有该网站的 Cookie 文件,如果有则将 Cookie 文件中的信息(一般是加密处理过的)包含在请求头中一起发给服务器,如果没有,则没有任何动作。由于 Cookie 有一定的安全风险,比如 Cookie 欺骗,这个不是很好的方案。
除了使用 Cookie 来保持 HTTP 状态外,还可以使用 Session 技术,这个直接把用户的信息存在服务端,比直接存在本地相对安全些。在服务端生成 Session ID 之后,会包含在响应消息中传给浏览器,下次用户再次访问时,请求中会包含此 Session ID,服务端收到了到 DB 或者文件中做匹配,这样就可以获取用户之前的访问状态了。服务器将 Session ID 传回给浏览器可以通过 Cookie 的方式,也就是通过 Set-Cookie 扩展头将 Session ID 传递到浏览器,然后浏览器每次请求都会在 cookie 带上该 ID,由于 Cookie 可能回本本地禁用,可以通过 URL 重写的方式实现,也就是把 Session ID 绑定到 URL 后面。当然 Session ID 依然存在会话劫持的可能。由于 Cookie 是存在本地的,这个只会消耗本地的资源,由于 Session ID 存储于服务端,这个会对服务器造成比较大的压力。二者需权衡使用。

对于 HTTP 的缓存,一般有如下的方式来控制缓存的是与否。
1. HTML META 标签
这个很少使用,缓存一般不解析 HTML 内容。

2. 使用 Expires 头信息控制
在 response header 有一个叫 Expires 的属性,他会告诉缓存在多长时间内是新鲜的,过了这个时间,如果客户端请求,缓存就会向源站请求。这个比较常见,大部分是设置成一个绝对的时间值,比如内容最后修改时间加上一个特定的时间所得到的时间。这个对于缓存静态文件比较好,注意要使用 GMT 时间,还要保持源跟缓存之间的时间同步。

3. 验证
这个是 HTTP 1.1 提出的,以检验缓存是否可用。

4. Cache-Control 头信息
主要包括 max-age, x-maxage, public, no-cache 等,这个需要单独的章节来归纳。

5. Pragma HTTP 头信息
这个主要在 HTTP 1.0 中比较常见,HTTP 1.1 主要还是使用上面的 Cache-Control 方式。

对于 web cache 的衡量指标,主要包括并发量、吞吐率、命中率以及响应时间等。而最后一项相应时间由多个部分组成,最关键的有 DNS 解析时间,一般在 0.18-0.3s;建立连接的时间,也就是 TCP 三次握手的时间一般在 0.15-0.3s;重定向时间,一般小于 0.1s;收到第一个包的时间,一般在 0.2-0.4s 之间;图片下载时间,通常采用 150KB 的图片下载所使用的时间来衡量 CDN 元素级加速性能,一般 1-2s;页面总下载时间,超过 10s 用户就不耐心了。

存储是 web cache 对缓存内容持久化的载体,需要关注容量、成本、服务性能等指标。目前比较常用的有共享存储,这个性能,稳定性都比较好,但是成本比较高;分布式的容错比较好,比较适合流媒体使用,但是有一定的技术要求;DAS 比较适合存储 10KB 左右大小的文件,通过大容量 RAID 硬盘就可以实现。

HTTP 1.1 把这种验证以后再决定是否返回消息内容的方式叫『有条件』的请求返回方式,方式如下:
1. 源站向 cache 返回消息时,会附带一个验证信息,cache 在缓存内容时会保存这个验证信息
2. 当有用户请求时,cache 如果发现缓存内容过期,就使用验证信息生成一个『有条件』的请求来向源站请求验证
3. 源站收到请求之后,将请求中包含的验证信息跟本地的进行比对,如果相等,则返回一个诸如 304 状态码且消息主体为空的响应消息,表示缓存可以继续使用,如果不相等,则会返回新的内容

上面的这个在 HTTP 头中表示为 If-Match,也可以用 If-None-Match 表示。对于验证信息,主要包含 Last-Modified 和 Entity Tag 两种,分别对应 Last-Modified 和 ETag 两个头信息。一般 Last-Modified 的值可以被包含在请求头 If-Modified-Since 中,但是其在时间精度上有一定的缺陷,而 ETag 能解决此问题,他由源站指定计算方法,请求消息在要求源站验证时,可以将 ETag 包含在请求头 If-Match 中。

另外,为了让缓存效率更高,开发者可以从下面几方面入手:
1. 保持 URL 稳定
2. 使用公共库存放每页都引用的元素
3. 对于不经常改变的元素,可以将 Cache-Control: max-age 设置的比较长
4. 少用 cookie,因为比较难以缓存
5. 尽量避免使用 POST,GET 相比更同样缓存
 

  • Mazhechao

    看王工的文章都不用买书了

  • http://log4d.com/ alswl

    突然想起来我的 vps 有几个子域名下面是静态的,一直忘了加 Expires,赶紧加起来。