nginx的proxy cache MISS问题

这两天在部署某应用时,想配置一下nginx的动静态分离,提高一下应用响应时间,在静态资源缓存测试中发现总是MISS,问题在哪里呢?

proxy cache MISS

curl测试发现请求静态资源怎么总是返回都是 MISS 。原来是不小心copy了一段牛逼的配置惹的祸。配置是某云的slb上学习来的,貌似比我以前的配置好,这源代码编译的nginx也是某云写的,这配置肯定有合理性,拿过来学习一下了,哈哈。

配置文件的http段中正好有 proxy_buffering off; 这么一行,引发了cache MISS的问题。

  1. 开始怀疑location配置得不对,但是如果配置得有问题是不可能在static.log中看见日志的,说明location是正确的。
  2. 又怀疑location中的proxy cache配置有问题,这配置在生产系统上跑了很久的,不存在问题。
  3. 那只可能是哪行配置有问题了。一百多行配置,有问题大体应该在http段中,只能硬着头皮上二分查找了,一半一半配置注释掉进行。想想都有点麻烦,但这是能想到的唯一的办法了。体力活也得干。

    首先把机器上的配置放到我的虚机上测试,居然可以跑,状态是HIT,真是神奇了。这坑好深啊。只能上机器去慢慢搞了,那机器需要ssh tunnel上去,网速不是一般的慢,按个键半天不响应一下,实在不想上去折腾。二分搞了几次,时间也过去了个把小时,终于找到了 proxy_buffering ,或许我应该直接看出来,但是面对茫茫多的配置,我迷茫了。。。

    找到后心想怎么就这么背,相同的nginx版本怎么可能这行配置在虚机行,物理机上就不行,这不是坑爹吗! 突然想到测试时只是 nginx -s reload , 重新加载配置文件,http段的配置有不生效的可能,后来 ./nginx -s quit;./nginx 完全停了nginx再启动后,果然全部MISS掉了。唉,以后改配置要小心,防止出现配置不生效的情况,太浪费时间了。谁知道proxy_buffering改了reload会不生效。

解决方法

在location中打开

location ~ (^/).*\.(gif|jpg|jpeg|png|js|css|html|cab|bmp)$
{
    proxy_buffering on;
}

有段官网论坛的mail list:

Re: nginx 1.9.12 proxy_cache always returns MISS Great! =) Make sure proxy_buffering stays on - that will bypass the cache if turned off, and make sure your key space is large because you'll throw 500s for everything if it runs out (I figured it would evict a key if it ran out of space, and what was a wrong assumption)

我又稍微看了一下代码,u->buffering在http, server, location中可以初始化,之前在http中配置了proxy_buffering off; 但是在location中没配置,所以一直是 off ,在location中再设置为 on 即可。

# vim ./src/http/modules/ngx_http_proxy_module.c
    { ngx_string("proxy_buffering"),
      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_proxy_loc_conf_t, upstream.buffering),
      NULL },

...

static ngx_int_t
ngx_http_proxy_handler(ngx_http_request_t *r)
...

    u->buffering = plcf->upstream.buffering;

...
# vim ./src/http/ngx_http_upstream.c
static void
ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)

...

    if (!u->buffering) {

...

配置

http
{
  # 缓存临时目录。后端的响应并不直接返回客户端,而是先写到一个临时文件中,然后被rename一下当做缓存放在 proxy_cache_path。
  proxy_temp_path  /dev/shm/proxy_temp_dir; 

  # 缓存目录,目录里的文件名是 cache_key 的MD5值。
  # levels=1:2 表示采用2级目录结构;
  # keys_zone=cache_one:128m inactive=2h max_size=1g;
  # 缓存区名称为cache_one  # 缓存的内存空间为100M,内存空间可多次使用;
  # 默认过期时间为2小时,超过2小时未被访问文件自动清除;
  # 最大文件存储为1G,超过后清除最少使用的数据。
  proxy_cache_path  /dev/shm/proxy_cache_dir levels=1:2 keys_zone=cache_one:128m inactive=2h max_size=1g;
}

location ~ (^/).*\.(gif|jpg|jpeg|png|js|css|html|cab|bmp)$
{
  # 引用前面定义的缓存区 cache_one。
  proxy_cache  cache_one; 

  # 定义cache_key。
  proxy_cache_key  $host$uri;

  # 为不同的响应状态码设置不同的缓存时间,比如200、302等正常结果可以缓存的时间长点,而404、500等缓存时间设置短一些,这个时间到了文件就会过期,而不论是否刚被访问过。
  proxy_cache_valid  200 304 1h;

  proxy_next_upstream http_502 http_504 error timeout invalid_header;

  # 忽略Cache-Control的请求头控制,依然进行缓存。例如对请求头设置cookie后,默认是不缓存的,需要增加忽略配置。
  # 忽略掉设置cookie的header,静态资源不需要,而且会让session失效。
  proxy_ignore_headers Set-Cookie;

  proxy_hide_header Cookie;

  proxy_http_version 1.1;

  proxy_set_header Connection "";

  # 在response的http header中增加一个header,显示cache的状态是HIT/MISS/STALE,方便curl时查看状态。
  add_header  Nginx-Cache "$upstream_cache_status";

  # 默认是打开的,on意味着从upstream返回的respone会被cache,一直到接收完。long poll的场景下需要关闭proxy_buffering。
  proxy_buffering   on;

  # python -m SimpleHTTPServer; python -m http.server
  proxy_pass http://127.0.0.1:8000;

  # 在响应头里设置Expires:或Cache-Control:max-age,返回给客户端的浏览器缓存失效时间。
  expires 1d;

  access_log "pipe:/usr/local/sbin/cronolog /home/admin/nginx/logs/static_%Y-%m-%d_log" static;
}

gzip配置

proxy cache当然要配合使用压缩功能,对文本格式的内容进行压缩传输,这样可以节省大量带宽、提高响应时间,否则失去cache的意义。

官网gzip模块文档

# 在http段配置
# 开启giz模块,默认是关闭的。
gzip  on;

# 设置http协议版本的最低版本,对1.0版本以上进行压缩。以前发现有些版本的硬负载radware转发请求是http 1.0协议,F5无此问题.
gzip_http_version 1.0;

# gzip压缩比/压缩级别,压缩级别:1-9,级别越高压缩率越大,当然压缩时间也就越长(传输快但比较消耗cpu)。压缩级别为1也是不错的选择,机器的cpu会压力小些。
gzip_comp_level 6;

# 允许压缩的页面最小字节数,默认是全部都压缩,最好不要小于1k,因为小于1k会越压越大。
gzip_min_length 1024;

# enables compression for all proxied requests.
gzip_proxied    any;

# 给http请求增加vary字段,不支持gzip的不进行压缩处理.
gzip_vary       on;

# ie6不压缩. 其实设不设一个样,现在还有用ie6的吗?
gzip_disable    msie6;

# 设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流。96 16k代表以16k为单位的buffer,96个buffer.
gzip_buffers    96 8k;

# 设置压缩文件类型,xml/html/js/css打开压缩.
gzip_types      text/xml text/plain text/css application/javascript application/x-javascript application/rss+xml application/atom+xml application/xml;