熟悉 CDN 行业主流技术的朋友应该都比较清楚,虽然 Nginx 近几年发展的如日中天,但是基本上没有直接使用它自带的 proxy_cache 模块来做缓存的,原因有很多,例如下面几个:
不支持多盘
不支持裸设备
大文件不会切片
大文件的 Range 请求表现不尽如人意
Nginx 自身不支持合并回源
在现在主流的 CDN 技术栈里面, Nginx 起到的多是一个粘合剂的作用,例如调度器、负载均衡器、业务逻辑(防盗链等),需要与 Squid、ATS 等主流 Cache Server 配合使用,
Nginx-1.9.8 中新增加的一个模块ngx_http_slice_module解决了一部分问题。
首先,我们看看几个不同版本的 Nginx 的 proxy_cache 对 Range 的处理情况。
Nginx-0.8.15在 Nginx-0.8.15 中,使用如下配置文件做测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; proxy_cache_path /tmp/nginx/cache levels=1:2 keys_zone=cache:100m; server { listen 8087; server_name localhost; location / { proxy_cache cache; proxy_cache_valid 200 206 1h; # proxy_set_header Range $http_range; proxy_pass http://127.0.0.1:8080; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
重点说明以下两种情况:
第一次 Range 请求(没有本地缓存),Nginx 会去后端将整个文件拉取下来(后端响应码是200)后,并且返回给客户端的是整个文件,响应状态码是200,而非206. 后续的 Range 请求则都用缓存下来的本地文件提供服务,且响应状态码都是对应的206了。
如果在上面的配置文件中,加上 proxy_set_header Range $http_range;再进行测试(测试前先清空 Nginx 本地缓存)。则第一次 Range 请求(没有本地缓存),Nginx 会去后端用 Range 请求文件,而不会把整个文件拉下来,响应给客户端的也是206.但问题在于,由于没有把 Range 请求加入到 cache key 中,会导致后续所有的请求,不管 Range 如何,只要 url 不变,都会直接用cache 的内容来返回给客户端,这肯定是不符合要求的。
Nginx-1.9.7在 Nginx-1.9.7 中,同样进行上面两种情况的测试,第二种情况的结果其实是没多少意义,而且肯定也和 Nginx-0.8.15 一样,所以这里只关注第一种测试情况。
第一次 Range 请求(没有本地缓存),Nginx 会去后端将整个文件拉取下来(后端响应码是200),但返回给客户端的是正确的 Range 响应,即206.后续的 Range 请求,则都用缓存下来的本地文件提供服务,且都是正常的206响应。
可见,与之前的版本相比,还是有改进的,但并没有解决最实质的问题。
我们可以看看 Nginx 官方对于 Cache 在 Range 请求时行为的说明:
How Does NGINX Handle Byte Range Requests?
If the file is up-to-date in the cache, then NGINX honors a byte range request and serves only the specified bytes of the item to the client. If the file is not cached, or if it’s stale, NGINX downloads the entire file from the origin server. If the request is for a single byte range, NGINX sends that range to the client as soon as it is encountered in the download stream. If the request specifies multiple byte ranges within the same file, NGINX delivers the entire file to the client when the download completes.
Once the download completes, NGINX moves the entire resource into the cache so that all future byte-range requests, whether for a single range or multiple ranges, are satisfied immediately from the cache.
Nginx-1.9.8我们继续看看Nginx-1.9.8, 当然,在编译时要加上参数--with-http_slice_module,并作类似下面的配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
http { include mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; proxy_cache_path /tmp/nginx/cache levels=1:2 keys_zone=cache:100m; server { listen 8087; server_name localhost; location / { slice 1m; proxy_cache cache; proxy_cache_key $uri$is_args$args$slice_range; proxy_set_header Range $slice_range; proxy_cache_valid 200 206 1h; #proxy_set_header Range $http_range; proxy_pass http://127.0.0.1:8080; } error_page 500 502 503 504 /50x.html; location = /50x.html { root html; } } }
不测不知道,一侧吓一跳,这俨然是一个杀手级的特性。
首先,如果不带 Range 请求,后端大文件在本地 cache 时,会按照配置的 slice 大小进行切片存储。