Nginx 反向代理 缓存一致性 配置详解
之前把 Nginx 配成反向代理加上 proxy_cache 之后,一开始觉得真香,后端扛不住了就靠缓存顶着,访问速度嗖嗖的。结果有天改了个商品详情页的文案,刷新了八百遍还是旧内容,后来才反应过来是 Nginx 那层缓存在作怪,缓存 TTL 是 300 秒,我他妈等了五分钟它还是纹丝不动,因为那五分钟还没过...
这个问题说大不大,说小不小,主要是 Nginx 默认的缓存策略太"死脑筋"了,你让它缓存,它就真的一股脑缓存到时间到了才去回源,中间你后端数据怎么变它根本不管。所以缓存一致性这个东西,本质上就是你在"性能"和"新鲜度"之间找平衡,没有银弹,只有取舍。
下面是我折腾了大概一个下午摸出来的几套配置,按场景来,你对照着抄就行。
先说最基础的缓存配置长什么样
proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m;
server {
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 302 300;
proxy_cache_valid any 1m;
add_header X-Cache-Status $upstream_cache_status;
}
}
这个 X-Cache-Status 头一定要加,排查问题的时候你就靠它判断到底是 HIT、MISS、EXPIRED 还是 BYPASS,不加的话你根本不知道请求有没有走到缓存层,两眼一抹黑。
改完数据想立刻生效:缓存主动清除
最简单粗暴的办法,proxy_cache_bypass。你在后端改完数据之后,发一个带特定 header 的请求,让 Nginx 跳过缓存直接回源,同时把旧的缓存条目用 proxy_cache_purge 模块清掉。
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 300;
proxy_cache_bypass $http_purge_cache;
proxy_no_cache $http_purge_cache;
}
# 如果你编译了 ngx_cache_purge 模块的话,可以单独加一个清除入口
location ~ /purge(/.*) {
allow 127.0.0.1;
deny all;
proxy_cache_purge my_cache $1;
}
用的时候就是 curl -H "Purge-Cache: 1" http://your-domain/api/products/123,这个请求会绕过缓存回源,并且把缓存写回去的时候直接覆盖旧的。要是装了 purge 模块,curl http://your-domain/purge/api/products/123 就能精准干掉那一条缓存。
不过说实话,ngx_cache_purge 这个东西现在维护得有点半死不活的,你要用的话最好确认一下你的 Nginx 版本和编译选项,别到时候发现这模块压根没编进去,又得重新编译,那感觉就像重新装了个系统。
更精细的做法:基于 etag 和 last-modified 协商
如果你的后端能返回 ETag 或者 Last-Modified,那 Nginx 其实可以跟缓存里的旧版本做协商,不用等 TTL 到期,请求来的时候先问后端"这玩意儿变没变",没变就直接吐缓存,变了就更新。
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 10m;
proxy_cache_use_stale updating error timeout;
proxy_cache_lock on;
proxy_cache_lock_timeout 5s;
proxy_cache_revalidate on;
}
关键就在那个 proxy_cache_revalidate on,开了这个之后 Nginx 在缓存过期但还没删掉的窗口期内,会带上 If-None-Match 和 If-Modified-Since 去问后端,后端要是返回 304 那就不重新拉完整响应体了,省带宽也省后端压力。
另外 proxy_cache_use_stale updating 这个配置也很实用,意思是"一个请求正在回源更新缓存的时候,其他并发请求继续吃旧的缓存,别全给我打穿到后端去"。不然高并发的情况下,缓存刚好到期的那一瞬间几百个请求同时穿透,后端直接起飞,这个坑我踩过一次,写个博客的功夫差点把数据库连崩了。
proxy_cache_lock on 也是配合这个用的,保证同一个 key 在过期后只有一个请求去回源刷新,其他请求等着就行,别搞成羊群效应。
动静分离:API 不缓存,静态资源往死里缓存
最后再说一个我觉得最管用的思路,就是别给所有东西都缓存。你把 /api/ 下面的路径全 proxy_cache off 或者 proxy_cache_bypass 1,静态资源 /static/、图片、js 这些就 proxy_cache_valid 200 7d,爱怎么缓存怎么缓存,反正改版本的时候文件 hash 也变了,缓存 key 不一样自然就是新的。
location /api/ {
proxy_pass http://backend;
proxy_cache off; # 或者只缓存 GET,且时间设短一点比如 30s
}
location /static/ {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_valid 200 7d;
proxy_cache_key "$scheme$request_method$host$request_uri";
}
proxy_cache_key 这个默认是 $scheme$proxy_host$request_uri,如果你的请求带 query string,确保 key 里包含 $args,不然 /api/search?q=1 和 /api/search?q=2 会命中同一条缓存,那就真的是一致性彻底炸了。
最后
缓存一致性这玩意儿,配置其实就那几行,真正头疼的是你的业务能不能容忍"最终一致"。我现在的习惯是:用户自己看自己的数据走实时(proxy_cache_bypass),公开读多的内容走缓存 + 短 TTL + 主动 purge,静态资源走长 TTL + 文件名 hash。没有一劳永逸的配置,只有根据你的更新频率和容忍度慢慢调,调着调着就知道了。
评论
还没有评论。
发表评论
提交后评论将经过自动审核,审核通过后公开展示。