0

nginx 缓存陷阱

 2 years ago
source link: https://zzyongx.github.io/blogs/nginx_cache_trap.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

nginx 缓存陷阱

接手的一个老系统,nginx做proxy,apache做upstream。某个请求总是返回MySQL连接错误,但是手动连接却没有问题。进一步排查发现nginx配置了cache,也就是某次请求upstream返回了错误,被nginx缓存住了,虽然后来upstream恢复了正常,但cache仍然继续返回错误。为了解决这个问题,我需要搞清两个问题:

  1. 如何控制什么样的响应被缓存
  2. 如何控制缓存时间

1 测试配置文件

相应的配置做了注释,并提示了注意事项。

nginx.conf

# 配置缓存目录 /data/nginx/cache,必须确保目录存在,并且nobody用户可以写入(这里假设以nginx以nobody用户运行)
# sudo -u nobody touch /data/nginx/cache/test 测试目录可用

# keys_zone 配置的是缓存元数据的内存大小,max_size 是缓存所用磁盘大小
# inactive 的含义是2h内没有访问的cache,被自动清除
# use_temp_path=off upstream过来的响应直接写入缓存目录,避免二次拷贝

proxy_cache_path /data/nginx/cache levels=2:2 keys_zone=content_cache:512m max_size=10g inactive=2h use_temp_path=off;

conf.d/test.cache.conf

# cache
server {
  listen       80;

  # 必须为on,否则cache不生效
  proxy_buffering on;

  # 方便查看缓存是否生效,也可以把这个加入access log,方便统计
  add_header X-Cache-Status $upstream_cache_status;
  # cache 使用的 key
  proxy_cache_key $proxy_host$request_uri;

  location /t1 {
    # 返回200的请求被缓存120小时
    proxy_cache_valid 200 120h;
    proxy_cache content_cache;

    rewrite /t1/(.+)$ /$1 break;
    proxy_pass http://127.0.0.1:9090;
  }

  location /t2 {
    # 返回200的请求被缓存,缓存时间由upstream控制
    proxy_cache_valid 200 0h;
    proxy_cache content_cache;

    rewrite /t2/(.+)$ /$1 break;
    proxy_pass http://127.0.0.1:9090;
  }
}

# upstream
server {
  listen 9090;
  default_type "text/plain";

  location /static {
    return 200 "CACHED-STATIC@$request_uri";
  }

  location /max-age {
    add_header Cache-Control "max-age=86400";
    return 200 "CACHED-MAX-AGE@$request_uri";
  }
}

2.1 proxy控制缓存时间

t1 配置了 proxy_cache_valid 200 120h 只要 127.0.0.1/t1/ 返回200 (static或max-age),就会被缓存120h。这就带来一个问题:很多upstream,因为框架的原因,或者开发自身的原因,当(可恢复的)错误发生时,仍然返回了200,导致错误响应被缓存。

2.2 upstream控制缓存时间

t2 配置了 proxy_cache_valid 200 0h 只有访问 127.0.0.1/t2/max-age 才会被缓存,static不会,因为upstream只给max-age配置了缓存。

2.3 两种方式对比

对于静态内容,或者不可恢复的错误(返回非200),我觉得都行,proxy控制可能更简单。对于动态内容,我觉得upstream控制比较好。缓存可以极大提升性能,可是缓存了错误的内容,恢复起来非常麻烦,动态内容upstream控制,可以降低风险。

3 确保缓存正确

因为缓存的存在,保证客户端获取的数据正确(过期的不算错误)尤为重要。为此可以在返回的数据中加入md5(由服务端生成,通过header或body返回),当md5校验失败时,重新获取(通过约定特殊的参数或header,以跳过缓存 proxy_cache_bypass ,同时服务端可以监控这些特殊参数或header发现问题)。

4 proxy_cache_key 的选择

proxy_cache_key 默认使用host + uri,通常这不会有问题,但是碰到upstream根据客户端的能力返回不同的内容的时候,就有问题了。例如:根据客户端是否 Accept webp 返回webp格式的图片,根据客户端的 Accept-Encoding 返回压缩或不压缩的数据。这时虽然uri一样,但是返回给客户端的东西是不同的,此时根据uri缓存就有问题。

4.1 Vary

此时upstream可以使用 Vary ,例如: Vary:Accept nginx 会为每类Accept缓存一份数据。

4.2 自定义 proxy_cache_key

Vary 是个很简洁的方法,但 Accept 的种类可能多达几十种,就会缓存几十份数据,但两份就够了,webp和非webp,这是极大的浪费和性能损失。此时自定义 proxy_cache_key 也许更好,虽然更复杂。

set $cachekey "$proxy_host$request_uri";
if ($http_accept ~* "image/webp") {
  set $cachekey "$proxy_host$request_uri/webp";
}
proxy_cache_key $cachekey;

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK