Nginx Proxy_cache 实践 (with Docke
最近在用Nuxt.js做一个SSR项目, 第一次做当然是踩坑不少, 其中一个坑就是Nuxt文档所提到的: 将nginx与生成的页面和缓存代理一起使用, 文档就一页, 但实际可不只这么简单.
至于为什么要选用Nuxt做项目, 而不是传统的字符串模板引擎或者是前端渲染呢? 可以看我另一篇文章: 使用SSR优化Vue的首屏加载速度与SEO (In writing :D)
下面就将阐述整个项目使用到的知识点
Nginx
nginx一般用于在项目中反向代理, 实现负载/静态资源服务器等功能, 可以说项目必备了.
在这里, 我们将使用nginx反向代理node服务(nuxt的ssr服务), 并实现代理缓存(proxy_cache).
为什么要缓存呢? 实在是SSR效率太低, 访问一个稍微复杂一点的页面需要300ms的时间才能渲染并返回完毕, 有点隐隐担忧.
系统优化的最终方案是不调用系统, 缓存就能达到这个目的.
Nginx in docker
如果你受够了在机器上敲上百行命令行去初始化你的项目环境(安装依赖等), 那么docker的好就不言而喻了.
docker镜像中包含了整个项目甚至是操作系统, 所以通常一个镜像比单个软件大很多, 为了不必要的磁盘占用, alpine就应运而生了.
Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.
基于alpine构建的nginx镜像仅仅只有9M, 而基于debian构建的却有55M,你可以到这个页面去获取镜像并查看详情: nginx Tags - Docker Hub.
当然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做的, 那么现在就只有自己动手了, 其实有了参考就都好办.
- 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
更多的说明就不写了.
如果你想继续研究这份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缓存就会出现问题.
这时候就需要自定义缓存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
进入实战, 现在将编写配置文件代理Nuxt项目.
可以参考Nuxt所写的文档 nginx-proxy, 建议查看英文文档, 中文翻译有点不准确.
在各种试错下, 终于写出一个可以正常使用的的配置, 如下
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; # 添加上缓存状态, 有Hit和Miss等.
proxy_ignore_headers Cache-Control; # (nuxt推荐写法), 我猜是忽略浏览器的强制刷新发送的缓存控制头, 让强制刷新也不会击穿缓存.
proxy_cache_valid 500 1m; # 如果服务端返回500则只缓存1分钟
proxy_cache_valid any 30m; # 对于其他相应, 缓存30分钟, 这些数值根据你的业务调整
proxy_http_version 1.1;
proxy_cache cache; # keys_zone
proxy_cache_bypass $arg_nocache; # 使用?nocache=1这样的请求参数跳过使用缓存
proxy_cache_key $host$uri$is_args$args; # 缓存key, 如果是当前nginx在服务多个域名, 则需要添加上$host, 否则可以不用$host.
proxy_cache_purge PURGE; # usage: curl -X PURGE "http://youdomain.com/*"
proxy_redirect off;
proxy_cache_background_update off; # 缓存预热, 根据你的需求而定.
proxy_cache_lock on; # 如果当个请求在请求同一个key并且没有缓存, 就会将后续的请求block, 防止缓存击穿.
proxy_cache_revalidate on; # 是否让缓存重新生效. 如果开启 当缓存过期, 但是Etag相等的情况下, 会将这个缓存重新生效.
}
}
对于上面的配置项, 都可以去nginx-caching-guide和ngx-http_proxy_module查阅.
Etag
Etag是用来做缓存验证的, 浏览器在请求资源的时候如果带上了Etag, 那么服务端端就可以根据Etag来判断是否需要返回新的内容给浏览器, 如果Etag相等, 那么服务端就会返回304告知浏览器当前的资源是最新的 可以拿来使用, 而不是返回整个资源给浏览器, 大大节约了流量传输的时间.
我当然是期望nginx的proxy_cache是支持Etag的, 但是当我配置好以后, 却发现一直是200.
百度无果(习以为常), google上找到了一篇文章: nginx-proxy-cache-and-etag, 但也没有很好得解决办法.
最后只得让服务端(也就是Nuxt)去计算Etag, 经过试验Nginx也能缓存命中, 就这样吧.
如果你想做试验, 可以在nuxt.config.js里配置关闭Etag, 这在nuxt中是默认开启的.
module.exports = {
render: {
etag: false
}
}
相关参考
都在文中啦