一、前言:什么是"本地缓存查询"?
在微服务架构中,我们经常遇到这样的场景:
- 用户频繁查询商品详情(
GET /api/product/123) - 客户端轮询配置接口(
GET /api/config/app) - 第三方调用只读数据接口(如城市列表、字典项)
这些请求具有高读低写、结果稳定、重复率高的特点。
❌ 传统做法:每次请求都穿透到后端 → 数据库压力大、响应慢
✅ Nginx 本地缓存查询 :将后端返回的 JSON 响应缓存在 Nginx 本地磁盘,后续相同查询直接返回缓存!
本文将教你如何用 Nginx 的 proxy_cache 模块,实现高效的本地缓存查询能力。
二、核心机制:proxy_cache 如何工作?
💡 关键优势:
- 零代码改造:后端无需任何改动
- 毫秒级响应:缓存命中时 RT < 1ms
- 自动过期:按配置时间自动清理
- 磁盘持久化:Nginx 重启后缓存仍有效(除非超时)
三、基础配置:5 步开启本地缓存查询
步骤 1:定义缓存区域(http 块)
Lua
http {
# 定义用于查询缓存的区域
proxy_cache_path /var/cache/nginx/query
levels=1:2 # 两级目录防 inode 耗尽
keys_zone=query_cache:100m # 共享内存区(100MB ≈ 80万 key)
inactive=10m # 10分钟未访问则删除
max_size=2g # 最大缓存 2GB
use_temp_path=off; # 关键!避免临时文件拷贝性能损耗
# 创建目录并授权
# mkdir -p /var/cache/nginx/query && chown nginx:nginx /var/cache/nginx/query
}
步骤 2:在查询接口启用缓存
Lua
server {
listen 80;
server_name api.example.com;
# 商品查询接口(只读、高频)
location /api/product/ {
# 启用缓存
proxy_cache query_cache;
# 自定义缓存 key(确保唯一性)
proxy_cache_key "$host|$request_uri";
# 仅缓存 200 成功响应,有效期 5 分钟
proxy_cache_valid 200 5m;
# 忽略后端 Cache-Control 头(由 Nginx 统一控制)
proxy_ignore_headers Cache-Control Expires;
# 透传真实 IP
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://product_service;
}
# 字典查询接口(几乎不变)
location /api/dict/ {
proxy_cache query_cache;
proxy_cache_valid 200 1h; # 缓存 1 小时
proxy_pass http://dict_service;
}
}
步骤 3:定义上游服务
Lua
upstream product_service {
server 10.0.0.10:8080;
}
upstream dict_service {
server 10.0.0.20:8080;
}
四、高级技巧:精准控制缓存查询行为
4.1 添加缓存状态头(用于调试)
Lua
# 在 location 中添加
add_header X-Cache-Status $upstream_cache_status always;
# 返回值说明:
# HIT → 命中缓存
# MISS → 未命中(已回源)
# BYPASS → 跳过缓存(如 POST 请求)
4.2 强制跳过缓存(调试用)
Lua
# 请求带 ?debug=1 时不走缓存
if ($arg_debug = "1") {
set $nocache "1";
}
proxy_cache_bypass $nocache;
proxy_no_cache $nocache;
4.3 防止缓存穿透(恶意查询)
Lua
# 对不存在的 ID 返回 404 并缓存 1 分钟
proxy_cache_valid 404 1m;
# 或限制 ID 格式(正则匹配)
location ~ ^/api/product/(\d+)$ {
proxy_cache query_cache;
proxy_cache_valid 200 5m;
proxy_pass http://product_service;
}
五、生产级最佳实践
5.1 缓存托底:后端挂了也能查
Lua
location /api/campaign/ {
proxy_cache query_cache;
proxy_cache_valid 200 30s;
# 关键!后端错误时使用过期缓存
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
proxy_pass http://campaign_service;
}
✅ 效果 :即使后端宕机,用户仍能查看最近一次活动数据。
5.2 性能优化建议
| 项目 | 推荐值 | 说明 |
|---|---|---|
| 存储介质 | SSD 磁盘 | 避免 HDD I/O 瓶颈 |
| inactive | 5~30 分钟 | 根据数据更新频率调整 |
| max_size | ≤ 磁盘 80% | 防止占满磁盘导致服务异常 |
| keys_zone | ≥ 50MB | 高 QPS 场景需更大内存 |
5.3 清理缓存(手动刷新)
bash
# 示例:清除商品 123 的缓存
KEY="api.example.com|/api/product/123"
HASH=$(echo -n "$KEY" | openssl md5 | cut -d' ' -f2)
LEVEL1=${HASH: -1}
LEVEL2=${HASH: -3:2}
rm -f /var/cache/nginx/query/$LEVEL1/$LEVEL2/$HASH
# 无需 reload,下次请求自动重建
六、验证缓存是否生效
6.1 通过响应头判断
bash
curl -I "http://api.example.com/api/product/123"
首次请求:
HTTP/1.1 200 OK
X-Cache-Status: MISS
...
后续请求:
HTTP/1.1 200 OK
X-Cache-Status: HIT
...
6.2 查看缓存文件
bash
# 查看缓存目录
ls /var/cache/nginx/query/a/bc/
# 输出:abcdef1234567890... (MD5 哈希值)
七、常见问题排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 始终 MISS | 后端返回 Cache-Control: no-cache |
添加 proxy_ignore_headers Cache-Control; |
| 缓存未过期 | inactive 时间未到 |
检查 proxy_cache_valid 和 inactive 区别 |
| 磁盘空间不足 | 未设置 max_size |
设置合理的 max_size 并监控磁盘 |
| 中文乱码 | 后端未指定 UTF-8 | 确保后端返回 Content-Type: application/json; charset=utf-8 |
八、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!