Nginx Proxy_cache 实践 (with Docker, Ngx-cache-purge, Etag)

最近在用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 the inactive parameter to the proxy_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
  }
}

相关参考

都在文中啦

你可能感兴趣的:(Nginx Proxy_cache 实践 (with Docker, Ngx-cache-purge, Etag))