最近在用Nuxt.js做一個SSR項目, 第一次做當(dāng)然是踩坑不少, 其中一個坑就是Nuxt文檔所提到的: 將nginx與生成的頁面和緩存代理一起使用, 文檔就一頁, 但實際可不只這么簡單.
至于為什么要選用Nuxt做項目, 而不是傳統(tǒng)的字符串模板引擎或者是前端渲染呢? 可以看我另一篇文章: 使用SSR優(yōu)化Vue的首屏加載速度與SEO (In writing :D)
下面就將闡述整個項目使用到的知識點
Nginx
nginx一般用于在項目中反向代理, 實現(xiàn)負載/靜態(tài)資源服務(wù)器等功能, 可以說項目必備了.
在這里, 我們將使用nginx反向代理node服務(wù)(nuxt的ssr服務(wù)), 并實現(xiàn)代理緩存(proxy_cache).
為什么要緩存呢? 實在是SSR效率太低, 訪問一個稍微復(fù)雜一點的頁面需要300ms的時間才能渲染并返回完畢, 有點隱隱擔(dān)憂.
系統(tǒng)優(yōu)化的最終方案是不調(diào)用系統(tǒng), 緩存就能達到這個目的.
Nginx in docker
如果你受夠了在機器上敲上百行命令行去初始化你的項目環(huán)境(安裝依賴等), 那么docker的好就不言而喻了.
docker鏡像中包含了整個項目甚至是操作系統(tǒng), 所以通常一個鏡像比單個軟件大很多, 為了不必要的磁盤占用, alpine就應(yīng)運而生了.
Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.
基于alpine構(gòu)建的nginx鏡像僅僅只有9M, 而基于debian構(gòu)建的卻有55M,你可以到這個頁面去獲取鏡像并查看詳情: nginx Tags - Docker Hub.
當(dāng)然alpine也不是沒有缺點, 由于精簡到極致, 所以很多命令都不能使用, 這可能會造成不太容易去編寫命令(如Dockerfile).
Proxy-cache-purge
在nginx中使用proxy-cache指令就能開啟代理緩存, 但如果想刷新緩存怎么辦? 在官方文檔中提到了這個, 但是NGINX Plus才支持....
NGINX Plus supports selective purging of cached files. This is useful if a file has been updated on the origin server but is still valid in the NGINX Plus cache (the
Cache-Control:max-age
is still valid and the timeout set by theinactive
parameter to theproxy_cache_path
directive has not expired). With the cache?purge feature of NGINX Plus, this file can easily be deleted. For more details, see Purging Content from the Cache.
僅僅需要這么幾行nginx配置
proxy_cache_path /tmp/cache keys_zone=mycache:10m levels=1:2 inactive=60s;
map $request_method $purge_method {
PURGE 1;
default 0;
}
server {
listen 80;
server_name www.example.com;
location / {
proxy_pass http://localhost:8002;
proxy_cache mycache;
proxy_cache_purge $purge_method;
}
}
好像挺簡單, 但我們平民只有另辟蹊徑了.
百度/谷歌"proxy_cache_purge", 再在 https://hub.docker.com 搜一下"nginx cache pruge", 果然有友軍做了這個: procraft/nginx-purge.
不過他不是基于alpine做的, 那么現(xiàn)在就只有自己動手了, 其實有了參考就都好辦.
- nginx-purge-docker Dockerfile: https://github.com/procraft/nginx-purge-docker/blob/master/Dockerfile
- 官方alpine Dockerfile: https://github.com/nginxinc/docker-nginx/blob/master/stable/alpine/Dockerfile
更多的說明就不寫了.
如果你想繼續(xù)研究這份Dockerfile是怎么寫的, 可以去我的github倉庫查看 nginx-docker.
如果你想直接用這個鏡像, 可以去我的dockerhub查看 bysir/nginx.
安裝好了這個擴展Module之后就可以編寫配置文件了.
由于我們并不是使用的Nginx Plus, 所以上面的配置是不生效的(這個坑我踩過).
那么該怎么寫配置呢?
在我們安裝的Module nginx-modules/ngx_cache_purge 倉庫中有說明.
一般來說這樣一個配置就夠用了.
http {
proxy_cache_path /tmp/cache keys_zone=tmpcache:10m;
server {
location / {
proxy_pass http://127.0.0.1:8000;
proxy_cache tmpcache;
proxy_cache_key $uri$is_args$args;
proxy_cache_purge PURGE from 127.0.0.1;
}
}
}
如果不想限制訪問ip, 則直接這樣寫也可以: proxy_cache_purge PURGE;
如果想清除緩存, 僅僅需要這樣
# curl -X PURGE "http://yourdomain.com/*"
*
代表清除所有緩存, 詳情查閱剛剛所提文檔中的partial-keys.
自定義 proxy_cache_key
有時候$uri$is_args$args
這樣的key不能滿足需求, 比如后端對于PC和Mobile有兩套界面, 是通過UA判斷的, 這時候如果在Nginx緩存就會出現(xiàn)問題.
這時候就需要自定義緩存key.
可以這樣
location / {
set $ua "pc"
proxy_pass http://127.0.0.1:8000;
proxy_cache tmpcache;
proxy_cache_key $uri$is_args$args$ua;
proxy_cache_purge PURGE from 127.0.0.1;
}
With Nuxt.js
進入實戰(zhàn), 現(xiàn)在將編寫配置文件代理Nuxt項目.
可以參考Nuxt所寫的文檔 nginx-proxy, 建議查看英文文檔, 中文翻譯有點不準(zhǔn)確.
在各種試錯下, 終于寫出一個可以正常使用的的配置, 如下
ps: 我會盡量為每一段代碼寫上注釋, 但nginx的各個指令真的難理解, 腦子不夠用, 等以后有緣了再逐個弄清吧.
proxy_cache_path /data/nginx/cache levels=1:2 keys_zone=cache:10m max_size=10g;
server {
listen 80;
location / {
add_header X-Cache-Status $upstream_cache_status; # 添加上緩存狀態(tài), 有Hit和Miss等.
proxy_ignore_headers Cache-Control; # (nuxt推薦寫法), 我猜是忽略瀏覽器的強制刷新發(fā)送的緩存控制頭, 讓強制刷新也不會擊穿緩存.
proxy_cache_valid 500 1m; # 如果服務(wù)端返回500則只緩存1分鐘
proxy_cache_valid any 30m; # 對于其他相應(yīng), 緩存30分鐘, 這些數(shù)值根據(jù)你的業(yè)務(wù)調(diào)整
proxy_http_version 1.1;
proxy_cache cache; # keys_zone
proxy_cache_bypass $arg_nocache; # 使用?nocache=1這樣的請求參數(shù)跳過使用緩存
proxy_cache_key $host$uri$is_args$args; # 緩存key, 如果是當(dāng)前nginx在服務(wù)多個域名, 則需要添加上$host, 否則可以不用$host.
proxy_cache_purge PURGE; # usage: curl -X PURGE "http://youdomain.com/*"
proxy_redirect off;
proxy_cache_background_update off; # 緩存預(yù)熱, 根據(jù)你的需求而定.
proxy_cache_lock on; # 如果當(dāng)個請求在請求同一個key并且沒有緩存, 就會將后續(xù)的請求block, 防止緩存擊穿.
proxy_cache_revalidate on; # 是否讓緩存重新生效. 如果開啟 當(dāng)緩存過期, 但是Etag相等的情況下, 會將這個緩存重新生效.
}
}
對于上面的配置項, 都可以去nginx-caching-guide和ngx-http_proxy_module查閱.
Etag
Etag是用來做緩存驗證的, 瀏覽器在請求資源的時候如果帶上了Etag, 那么服務(wù)端端就可以根據(jù)Etag來判斷是否需要返回新的內(nèi)容給瀏覽器, 如果Etag相等, 那么服務(wù)端就會返回304告知瀏覽器當(dāng)前的資源是最新的 可以拿來使用, 而不是返回整個資源給瀏覽器, 大大節(jié)約了流量傳輸?shù)臅r間.
我當(dāng)然是期望nginx的proxy_cache是支持Etag的, 但是當(dāng)我配置好以后, 卻發(fā)現(xiàn)一直是200.
百度無果(習(xí)以為常), google上找到了一篇文章: nginx-proxy-cache-and-etag, 但也沒有很好得解決辦法.
最后只得讓服務(wù)端(也就是Nuxt)去計算Etag, 經(jīng)過試驗Nginx也能緩存命中, 就這樣吧.
如果你想做試驗, 可以在nuxt.config.js里配置關(guān)閉Etag, 這在nuxt中是默認開啟的.
module.exports = {
render: {
etag: false
}
}
相關(guān)參考
都在文中啦