熟悉 CDN 行業主流技術的朋友應該都比較清楚,雖然 Nginx 近幾年發展的如日中天,但是基本上沒有直接使用它自帶的 proxy_cache 模塊來做緩存的,原因有很多,例如下麵幾個: 不支持多盤 不支持裸設備 大文件不會切片 大文件的 Range 請求表現不盡如人意 Nginx 自身不支持合併回 ...
熟悉 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 中,使用如下配置文件做測試:
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,並作類似下麵的配置:
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 大小進行切片存儲。
其次,如果帶 Range 請求,則 Nginx 會用合適的 Range 大小(以 slice 為邊界)去後端請求,這個大小跟客戶端請求的 Range 可能不一樣,並將以 slice 為大小的切片存儲到本地,並以正確的206響應客戶端。
註意上面所說的,Nginx 到後端的 Range 並不一定等於客戶端請求的 Range,因為無論你請求的Range 如何,Nginx 到後端總是以 slice 大小為邊界,將客戶端請求分割成若幹個子請求到後端,假設配置的 slice 大小為1M,即1024位元組,那麼如果客戶端請求 Range 為0-1023範圍以內任何數字,均會落到第一個切片上,如果請求的 Range 橫跨了幾個 slice 大小,則nginx會向後端發起多個子請求,將這幾個 slice 緩存下來。而對客戶端,均以客戶端請求的 Range 為準。如果一個請求中,有一部分文件之前沒有緩存下來,則 Nginx 只會去向後端請求缺失的那些切片。
由於這個模塊是建立在子請求的基礎上,會有這麼一個潛在問題:當文件很大或者 slice 很小的時候,會按照 slice 大小分成很多個子請求,而這些個子請求並不會馬上釋放自己的資源,可能會導致文件描述符耗盡等情況。
小結
總結一下,需要註意的點:
-
該模塊用在 proxy_cache 大文件的場景,將大文件切片緩存
-
編譯時對 configure 加上 --with-http_slice_module 參數
-
$slice_range 一定要加到 proxy_cache_key 中,並使用 proxy_set_header 將其作為 Range 頭傳遞給後端
-
要根據文件大小合理設置 slice 大小
具體特性的說明,可以參考 Roman Arutyunyan 提出這個 patch 時的郵件來往:
https://forum.nginx.org/read.php?29,261929,261929#msg-261929
順帶提一下,Roman Arutyunyan 也是個大牛,做流媒體領域的同學們肯定很多都聽說過:nginx-rtmp 模塊的作者。
參考資料
-
http://www.tianwaihome.com/2015/03/nginx-proxy-cache.html
- Nginx 官方的 Cache 指南
https://www.nginx.com/blog/nginx-caching-guide/
- Nginx各版本changelog
- Nginx proxy 模塊 wiki
http://nginx.org/en/docs/http/ngx_http_proxy_module.html
- http_slice_module 的歷次提交記錄
http://hg.nginx.org/nginx/rev/29f35e60840b
http://hg.nginx.org/nginx/rev/bc9ea464e354
http://hg.nginx.org/nginx/rev/4f0f4f02c98f
- http_slice_module 提交前的郵件來往
https://forum.nginx.org/read.php?29,261929
- Nginx 之前版本關於 Range cache 的郵件來往
https://forum.nginx.org/read.php?2,8958,8958
- 切片模塊的 wiki
http://nginx.org/en/docs/http/ngx_http_slice_module.html
本文來源於:http://www.pureage.info/2015/12/10/nginx-slice-module.html