大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
缓存与性能
这一课你将能做到
- 解释 upstream
keepalive为何要搭配proxy_http_version 1.1与清空Connection,并对照prod.conf.example - 设计一处 只读 GET 的
proxy_cache示例,并列出至少三个 禁止缓存 的 API 路径类型 - 为 Next 的
/_next/static/与 HTML 文档写出不同的expires/Cache-Control策略
为什么要学
反代正确只是「能用」;生产还要「扛得住」。最便宜的两刀是:复用到上游的连接(keepalive) 与 对可缓存响应做边缘缓存。
CodeRoadMap 已在 upstream 打开 keepalive;proxy_cache 则是可选加速,且极易误伤登录态与写操作。
业务层还有 Redis 缓存(redis-essentials)——Nginx 缓存是更外层。本课把分层与「什么绝对不能缓存」讲清。
先建立图像
浏览器缓存 (expires / Cache-Control)
│
▼
Nginx proxy_cache(可选,按 URI/状态码)
│
▼
upstream keepalive 池 ──► Spring / Next
│
▼
应用内缓存(Redis 等)──► MySQL
人话:越靠外的缓存越要「傻而安全」——只缓存公开、幂等、可过期的;越靠内越懂业务失效。
| 手段 | 作用 | 本站现状 |
|---|---|---|
| upstream keepalive | 少握手 | prod 已开 |
| expires / Cache-Control | 控浏览器 | 按资源类型配 |
| proxy_cache | 缓存上游响应 | 可选实验 |
| Redis | 业务热点 | 应用层开关 |
跟着做
步骤 1:读懂已有 keepalive
upstream coderoadmap_backend {
server 127.0.0.1:8080;
keepalive 64;
}
location /api/ {
proxy_pass http://coderoadmap_backend/;
proxy_http_version 1.1;
# 清空 Connection,才能复用上游 keep-alive
proxy_set_header Connection "";
# … 其他 proxy_set_header …
}
注意:这与 WebSocket location 里 Connection "upgrade" 不同。页面反代若要同时支持 HMR,需按 location 分开策略,或接受「开发走直连、生产关 HMR」。
步骤 2:规划 proxy_cache(实验向)
# 放在 http 块
proxy_cache_path /var/cache/nginx/api
levels=1:2
keys_zone=api_cache:10m
max_size=100m
inactive=10m;
# 仅示意:公开列表可短缓存
location = /api/tracks {
proxy_cache api_cache;
proxy_cache_valid 200 60s;
proxy_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache-Status $upstream_cache_status always;
proxy_pass http://coderoadmap_backend/tracks;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
}
用 curl -I 连续两次,观察 X-Cache-Status:MISS → HIT(若配置生效)。
步骤 3:列出禁止缓存清单
在笔记写死:
/api/auth/*— 登录态、令牌- 一切非 GET(POST/PUT/DELETE)
- 带用户私有数据的响应(进度、个人信息)
- 管理端写操作与 CSRF 相关接口
对应配置思维:proxy_cache off; 或根本不把这些 location 放进 cache 区。
步骤 4:Next 静态长缓存
location /_next/static/ {
proxy_pass http://coderoadmap_platform;
expires 365d;
add_header Cache-Control "public, immutable";
}
构建产物带 content hash,长缓存安全。HTML 文档保持短 TTL 或 no-cache,避免壳不更新。
步骤 5:通用 I/O 小开关
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on; # 细节见静态课
与容器 CPU limit 一起看:压缩与 TLS 都吃 CPU,需给 Nginx worker 留余量。
步骤 6:分层对照 Redis
打开脑内对照:
- Nginx:按 URL 的傻缓存,失效靠 TTL
- Redis:按业务 key,可主动删除(Cache-Aside)
公开 tracks 列表两者都能做;不要双层无限叠长 TTL 还不失效,否则发版内容陈旧。
实践建议:若已在 Spring/Redis 做列表缓存,Nginx proxy_cache 可先不开;等公开只读流量成为明确瓶颈,再对单一 GET location 试点 30~60 秒 TTL,并用 X-Cache-Status 证明收益后,才考虑扩大范围。
讲清楚要点
- 因为每次新建 TCP/HTTP 有成本,所以 upstream keepalive 能降低高 QPS 握手——需 HTTP/1.1 与正确 Connection。
- 因为缓存错数据比慢更可怕,所以 proxy_cache 只给公开幂等 GET——写路径与鉴权路径关闭。
- 因为浏览器也有缓存,所以 expires 与 proxy_cache 是不同层——改配置要想「谁在缓存」。
- 因为 hash 静态可变,所以
/_next/static/可 immutable 长缓存——HTML 不能同等对待。 - 因为 Redis 更懂业务失效,所以边缘缓存 TTL 宜短或可关——出问题先关 Nginx 缓存保正确。
- 因为
X-Cache-Status可观测,所以实验时务必打头——否则不知道有没有打中。 - 因为官方 proxy_cache 指令多,所以先小范围 location 试点再扩大——忌全局一刀切。
你可能会懵的地方
-
keepalive 与 worker_connections 什么关系?
worker 要能扛「客户端连接 + 上游连接」。keepalive 占上游侧槽位;调大前看系统与worker_connections。 -
缓存了带 Set-Cookie 的响应怎么办?
危险。确保不缓存个性化响应;必要时用proxy_no_cache/proxy_cache_bypass按 cookie 判断。 -
为什么生产示例没开 proxy_cache?
正确性优先;列表可走 Redis。Nginx 缓存是可选优化,不是本站默认必需。 -
HIT 了但内容旧?
缩短proxy_cache_valid,或发版时清理 cache 目录,或改 cache key 版本前缀。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
| 缓存 POST / 登录响应 | 串会话、泄密 | 仅公开 GET;auth 关闭 |
| WebSocket location 清空 Connection | 升级失败 | upgrade 与 keepalive 分 location |
| HTML 也 365d immutable | 用户卡旧壳 | 文档短缓存 |
| 无限加长 TTL 不观测 | 陈旧难查 | 加 X-Cache-Status,TTL 保守 |
| 以为 gzip 能替代缓存 | 每次仍打上游 | 压缩 ≠ 缓存 |
自测三问
- keepalive 生效通常需要哪两个搭配条件?
- 举三个不应被 Nginx proxy_cache 的路径或条件。
/_next/static/与 HTML 的缓存策略为何不同?
小结一句话
keepalive 生产已开;proxy_cache 只给公开只读 GET;静态长缓存、HTML 短缓存,并与 Redis 分层。