一次 CDN 源站流量暴增的问题分析

上周经历了一次 CDN 源站流量暴增的问题,原因是有 CDN 有一批文件过期,大量回源。

CDN 回源过程:

  1. 第一次回源,如果源站返回 Last-Modified 头,CDN 会记录下来;

  2. CDN 的文件过期后,如果源站之前已经返回了 Last-Modified 头,就以 If-Modified-Since 头(内容是 源站返回的Last-Modified 值) 来访问源站,如果文件未变更,则返回 304;如果之前源站没返回  Last-Modified 头,则会下载整个文件,源站返回 200。

流量暴增的问题就出现在源站不返回 Last-Modified 头上,解决方法是源站返回 Last-Modified 头。

但是需要注意几点,先看看源站前面的 Nginx 配置文件:

location ~ /(?<name>.+?)/.* {
    expires 100d;

    rewrite ^/.+?/(.*)$ /$1 break;

    proxy_cache m_wdjcdn_com;
    proxy_cache_key &quot;$request_uri&quot;;
    proxy_cache_valid 200 30d;

    proxy_set_header Host $name.cdn.xxxx.com;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_pass http://new-source-nodes;
}
  1. 如果源站文件放在本地硬盘(使用 root 指令) 而不是通过 proxy_pass 打到后端,就不会出现这个问题,因为 Nginx 对静态文件完全支持 Last-Modified 和 If-Modified-Since 语义;

  2. Nginx 有一个指令 if_modified_since,默认是 exact,表示 If-Modified-Since 头的值必须和上次获取的 Last-Modified 值一致,否则还会 200;可以把 if_modified_since 改成 before,表示 If-Modified-Since 值可以比 Last-Modified 值大,也就是更新。

Nginx Cache方面的设置

这里 Cache 有两种情况说明,一种是浏览器访问Nginx,浏览器会Cache;一种是Nginx 访问后端,Nginx 自己Cache 。

 

第一种情况

来个例子:

$ curl -I http://su.bdimg.com/static/superplus/css/super_min_2b5190eb.css
HTTP/1.1 200 OK
Server: JSP3/2.0.4
Date: Fri, 31 Oct 2014 07:28:20 GMT
Content-Type: text/css
Content-Length: 25076
Connection: keep-alive
ETag: “1121644245”
Last-Modified: Wed, 24 Sep 2014 15:50:22 GMT
Expires: Mon, 23 Mar 2015 16:29:25 GMT
Age: 3164335
Cache-Control: max-age=15552000
Accept-Ranges: bytes
Vary: Accept-Encoding

先多句嘴说说Date 和 Age 的意思,Date 的意思是服务器发送消息的时间; Age 的意思有点复杂,它的存在暗示你访问的服务器不是源服务器,而是一台缓存服务器,Age 的大小表示这个资源已经”存活了”多长时间,所以这个值不会大于 源服务器设置的最大缓存时间。

这里Expires 表示过期时间,Cache-Control 表示最大的存活时间,在服务器端的Nginx 我们可以用 expires 指令来定义这两项。

比如:
expire -1;
expire 0;
expire 1h;
expire max;
expire off;

这个指令表示在HTTP响应头中是否增加或修改Expires 和 Cache-Control ,仅当响应状态是200, 201, 204, 206, 301, 302, 303, 304, 或者 307的时候有效。

当expire 为负时,会在响应头增加Cache-Control: no-cache;

当为正或者0时,就表示Cache-Control: max-age=指定的时间(秒);

当为max时,会把Expires设置为 “Thu, 31 Dec 2037 23:55:55 GMT”, Cache-Control 设置到 10 年;

当为off时,表示不增加或修改Expires 和 Cache-Control 。

 

另外,浏览器可以通过两种方式来判断缓存文件是否被更新了,一种是 If-Modified-Since ,浏览器通过发送If-Modified-Since 头,带上「上一次请求获得的修改时间,(在Last-Modified头里)」发送到服务器,服务器会判断这个时间之后文件有没有改变,如果没有改变返回状态值304,如果有返回新文件,状态值200 。

还有一种是利用Etag(Entity Tag),它是实体内容的hash字符串(md5或者SHA1的算法计算出来的),这个方法更准确但是更消耗服务器的CPU,浏览器发送If-None-Match 头,里面是Etag值(浏览器之前请求文件时,服务器会返回Etag 头),服务器收到后做比较,如果一样就返回304,不一样也发送最新文件,返回值200。

 

第二种情况

 一般先在http 里面定义一个缓存路径:

[text]
proxy_buffering on;
proxy_cache_path $cache_path/m_wdjcdn_com levels=1:2 keys_zone=m_wdjcdn_com:512m inactive=20d max_size=300G;

[/text]

然后在server 的 location 里来使用缓存,比如:

[text]

proxy_cache m_wdjcdn_com;
proxy_cache_key "$scheme$host$request_uri";
proxy_cache_valid 200 301 302 20m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout invalid_header updating;
proxy_cache_min_uses 1;
proxy_cache_revalidate on;
[/text]

要注意,proxy_cache在proxy_buffering 开启之后才有效,否则不会缓存

proxy_cache_path 的levels 定义目录层次结构;inactive 表示数据没被访问多长时间后从缓存中删除;缓存大小超过max_size之后使用LRU算法删除数据。

proxy_cache 定义使用哪个zone;
proxy_cache_key 定义缓存key;
proxy_cache_valid 不同状态码缓存不同时间;
proxy_cache_use_stale 定义后端服务器在何种情况下Nginx 返回过期的缓存;
proxy_cache_min_uses 请求多少次之后才会被缓存;
proxy_cache_revalidate 表示资源过期后是否使用If-Modified-Since 和If-None-Match来确认资源是否改变。

另外,还有一个指令 proxy_cache_purge,它用于清除缓存,比如下面的配置:

[text]location ~ /purge(/.*) {
proxy_cache_purge m_wdjcdn_com ""$scheme$host$1";
}[/text]

如果访问/purge/nosa.png,就把nosa.png的缓存清除了,这个功能可用于刷新缓存。

 

最后再看下关于 proxy_cache_use_stale 的官方解释:

This directive tells Nginx when to serve a stale item from the proxy cache. The parameters for this directive are similar to proxy_next_upstream with the addition of ‘updating’.

To prevent cache stampedes (when multiple threads stampede in to try to update the cache simultaneously) you can specify the ‘updating‘ parameter. This will cause one thread to update the cache and while the update is in progress all other threads will serve the stale version of what is in the cache.