缓存与限流
2026/3/20大约 9 分钟
缓存与限流
浏览器缓存
expires 指令
server {
listen 80;
server_name example.com;
root /var/www/html;
# 设置过期时间
location ~* \.(jpg|jpeg|png|gif|ico)$ {
expires 30d; # 30 天
}
location ~* \.(css|js)$ {
expires 7d; # 7 天
}
location ~* \.(html|htm)$ {
expires -1; # 不缓存
# 或
expires epoch; # 立即过期
}
# 使用 max
location /static/ {
expires max; # 最大缓存时间
}
# 使用具体时间
location /news/ {
expires 24h; # 24 小时
}
# 使用 modified 参数
location /docs/ {
expires modified +24h; # 基于文件修改时间 + 24小时
}
}
Cache-Control 详解
server {
# HTML - 不缓存
location ~* \.html$ {
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
# 静态资源 - 长期缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# API 响应 - 不缓存
location /api/ {
add_header Cache-Control "no-store";
}
# 私有内容
location /user/ {
add_header Cache-Control "private, max-age=3600";
}
}
Cache-Control 指令说明:
| 指令 | 说明 |
|---|---|
public | 可被任何缓存存储(CDN、浏览器) |
private | 只能被浏览器缓存,不能被 CDN 缓存 |
no-cache | 每次使用前必须向服务器验证 |
no-store | 完全不缓存 |
max-age=秒 | 缓存有效期(秒) |
s-maxage=秒 | CDN 缓存有效期(覆盖 max-age) |
immutable | 资源永不变化,无需验证 |
must-revalidate | 过期后必须验证 |
stale-while-revalidate=秒 | 过期后可在后台更新时使用旧缓存 |
ETag 和 Last-Modified
http {
# 启用 ETag(默认开启)
etag on;
# 启用 Last-Modified(默认开启)
if_modified_since exact;
# exact: 精确匹配
# before: 早于等于匹配
server {
location /static/ {
# 同时使用 ETag 和 Last-Modified
etag on;
add_header Last-Modified $date_gmt;
}
# 关闭 ETag(某些场景下)
location /dynamic/ {
etag off;
}
}
}
缓存策略设计
server {
listen 80;
server_name example.com;
root /var/www/html;
# HTML 文件 - 不缓存(确保获取最新版本)
location ~* \.html?$ {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
# 带 hash 的静态资源 - 长期缓存
# 例如:main.a1b2c3d4.js
location ~* \.[a-f0-9]{8,}\.(js|css)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
access_log off;
}
# 普通静态资源 - 中等缓存
location ~* \.(js|css)$ {
expires 7d;
add_header Cache-Control "public, max-age=604800";
}
# 图片 - 长期缓存
location ~* \.(jpg|jpeg|png|gif|ico|webp|svg)$ {
expires 30d;
add_header Cache-Control "public, max-age=2592000";
access_log off;
}
# 字体 - 长期缓存
location ~* \.(woff|woff2|ttf|eot|otf)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000";
add_header Access-Control-Allow-Origin *;
access_log off;
}
# API 响应 - 短期或不缓存
location /api/ {
add_header Cache-Control "no-store";
}
}
代理缓存(proxy_cache)
proxy_cache_path 配置
http {
# 定义缓存路径和参数
proxy_cache_path /var/cache/nginx/proxy_cache
levels=1:2 # 目录层级(1位:2位)
keys_zone=my_cache:10m # 共享内存区名称:大小(存储 key 和元数据)
max_size=10g # 缓存最大磁盘空间
inactive=60m # 缓存多久未访问后删除
use_temp_path=off # 直接写入缓存目录,不使用临时目录
loader_threshold=300 # 缓存加载器每次迭代时间(毫秒)
loader_files=200 # 缓存加载器每次迭代最大文件数
purger=on # 启用缓存清理进程(商业版)
;
# 多级缓存
proxy_cache_path /var/cache/nginx/cold
levels=1:2
keys_zone=cold_cache:10m
max_size=100g
inactive=7d;
proxy_cache_path /var/cache/nginx/hot
levels=1:2
keys_zone=hot_cache:50m
max_size=1g
inactive=1h;
}
缓存配置详解
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend;
# 使用哪个缓存区
proxy_cache my_cache;
# 缓存 key(决定缓存如何区分不同请求)
proxy_cache_key "$scheme$request_method$host$request_uri";
# 缓存有效期(根据状态码设置)
proxy_cache_valid 200 302 10m; # 200、302 缓存 10 分钟
proxy_cache_valid 301 1h; # 301 缓存 1 小时
proxy_cache_valid 404 1m; # 404 缓存 1 分钟
proxy_cache_valid any 5m; # 其他状态码缓存 5 分钟
# 最少使用次数才缓存
proxy_cache_min_uses 3;
# 缓存请求方法(默认只缓存 GET 和 HEAD)
proxy_cache_methods GET HEAD POST;
# 添加缓存状态头
add_header X-Cache-Status $upstream_cache_status;
}
}
$upstream_cache_status 值说明:
| 值 | 说明 |
|---|---|
MISS | 缓存未命中,请求转发给后端 |
HIT | 缓存命中,直接返回缓存内容 |
EXPIRED | 缓存已过期,正在更新 |
STALE | 使用过期缓存(后端不可用时) |
UPDATING | 缓存正在更新(返回旧缓存) |
REVALIDATED | 缓存验证通过 |
BYPASS | 跳过缓存 |
缓存绕过与条件
location / {
proxy_pass http://backend;
proxy_cache my_cache;
# 跳过缓存的条件(任一为真则跳过)
proxy_cache_bypass $cookie_nocache $arg_nocache;
proxy_cache_bypass $http_pragma $http_authorization;
# 不存储到缓存的条件
proxy_no_cache $cookie_nocache $arg_nocache;
# 根据请求头判断
proxy_no_cache $http_cache_control;
}
# 使用 map 进行复杂的缓存控制
http {
map $request_uri $skip_cache {
default 0;
~*/admin 1;
~*/api/user 1;
~*\.php$ 1;
}
map $request_method $purge_method {
PURGE 1;
default 0;
}
server {
location / {
proxy_pass http://backend;
proxy_cache my_cache;
proxy_cache_bypass $skip_cache;
proxy_no_cache $skip_cache;
}
}
}
缓存锁与过期策略
location / {
proxy_pass http://backend;
proxy_cache my_cache;
# 缓存锁(防止缓存击穿)
# 同一资源并发请求时,只有一个请求去后端,其他等待
proxy_cache_lock on;
proxy_cache_lock_timeout 5s; # 等待超时
proxy_cache_lock_age 5s; # 锁定时间
# 使用过期缓存的条件
# 当后端错误或超时时,使用过期缓存
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
# 后台更新缓存
proxy_cache_background_update on;
# 重新验证过期缓存
proxy_cache_revalidate on;
}
缓存清理
# 方法1:使用 proxy_cache_purge(需要第三方模块或商业版)
http {
map $request_method $purge_method {
PURGE 1;
default 0;
}
server {
location / {
proxy_pass http://backend;
proxy_cache my_cache;
# 缓存清理
proxy_cache_purge $purge_method;
}
}
}
# 清理命令:curl -X PURGE http://example.com/path/to/resource
# 方法2:手动删除缓存文件
# 计算缓存 key 的 MD5,找到对应文件删除
缓存清理脚本:
#!/bin/bash
# 清理指定 URL 的缓存
CACHE_DIR="/var/cache/nginx/proxy_cache"
URL="$1"
# 计算缓存 key 的 MD5
# 默认 key 格式:$scheme$request_method$host$request_uri
KEY="http GET example.com${URL}"
MD5=$(echo -n "$KEY" | md5sum | awk '{print $1}')
# 缓存文件路径
LEVEL1="${MD5: -1}"
LEVEL2="${MD5: -3:2}"
CACHE_FILE="${CACHE_DIR}/${LEVEL1}/${LEVEL2}/${MD5}"
if [ -f "$CACHE_FILE" ]; then
rm -f "$CACHE_FILE"
echo "Cache purged: $CACHE_FILE"
else
echo "Cache not found"
fi
FastCGI 缓存
http {
# 定义 FastCGI 缓存
fastcgi_cache_path /var/cache/nginx/fastcgi_cache
levels=1:2
keys_zone=php_cache:10m
max_size=1g
inactive=1h;
server {
listen 80;
server_name example.com;
root /var/www/html;
# 缓存控制变量
set $skip_cache 0;
# 不缓存 POST 请求
if ($request_method = POST) {
set $skip_cache 1;
}
# 不缓存带查询参数的请求
if ($query_string != "") {
set $skip_cache 1;
}
# 不缓存登录用户
if ($http_cookie ~* "wordpress_logged_in") {
set $skip_cache 1;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# FastCGI 缓存配置
fastcgi_cache php_cache;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
fastcgi_cache_valid 200 10m;
fastcgi_cache_valid 404 1m;
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
fastcgi_cache_lock on;
fastcgi_cache_use_stale error timeout updating;
fastcgi_cache_background_update on;
add_header X-FastCGI-Cache $upstream_cache_status;
}
}
}
限流机制
请求速率限制(limit_req)
http {
# 定义限流区域
# $binary_remote_addr: 客户端 IP(二进制格式,更省内存)
# zone=名称:大小
# rate=速率(r/s 或 r/m)
limit_req_zone $binary_remote_addr zone=req_limit:10m rate=10r/s;
# 基于 URI 限流
limit_req_zone $binary_remote_addr$request_uri zone=uri_limit:10m rate=5r/s;
# 基于 server 限流(全局)
limit_req_zone $server_name zone=server_limit:10m rate=1000r/s;
server {
listen 80;
location / {
# 使用限流区域
# burst: 突发请求数(令牌桶大小)
# nodelay: 不延迟突发请求(直接处理)
limit_req zone=req_limit burst=20 nodelay;
# 或延迟处理突发请求
# limit_req zone=req_limit burst=20;
# 返回状态码(默认 503)
limit_req_status 429;
proxy_pass http://backend;
}
# API 更严格的限流
location /api/ {
limit_req zone=req_limit burst=10 nodelay;
limit_req_status 429;
proxy_pass http://api_backend;
}
}
}
burst 和 nodelay 说明:
| 配置 | 行为 |
|---|---|
| 无 burst | 超过速率的请求直接拒绝 |
| burst=N | 超过速率的请求进入队列等待(最多 N 个) |
| burst=N nodelay | 突发请求立即处理,但消耗令牌 |
多级限流
http {
# 全局限流
limit_req_zone $binary_remote_addr zone=global:10m rate=100r/s;
# API 限流
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
# 登录限流(更严格)
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
server {
# 全局限流
limit_req zone=global burst=50 nodelay;
location /api/ {
# 叠加 API 限流
limit_req zone=api burst=20 nodelay;
proxy_pass http://api_backend;
}
location /login {
# 叠加登录限流
limit_req zone=login burst=5;
limit_req_status 429;
proxy_pass http://api_backend;
}
}
}
并发连接限制(limit_conn)
http {
# 基于 IP 的连接数限制
limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
# 基于 server 的总连接数限制
limit_conn_zone $server_name zone=server_conn:10m;
server {
# 每个 IP 最多 10 个并发连接
limit_conn conn_limit 10;
# 服务器总连接数限制
limit_conn server_conn 1000;
# 连接限制状态码
limit_conn_status 503;
# 日志级别
limit_conn_log_level warn;
location /download/ {
# 下载目录更严格的限制
limit_conn conn_limit 2;
alias /data/downloads/;
}
}
}
带宽限制(limit_rate)
server {
listen 80;
location / {
root /var/www/html;
}
location /download/ {
alias /data/downloads/;
# 限制下载速度
limit_rate 500k; # 限制为 500KB/s
# 或在一定大小后限速
limit_rate_after 10m; # 前 10MB 不限速
limit_rate 1m; # 之后限速 1MB/s
}
# 根据变量动态限速
location /video/ {
alias /data/videos/;
# 使用 map 根据用户类型限速
set $limit_rate 100k; # 默认 100KB/s
if ($cookie_vip = "true") {
set $limit_rate 0; # VIP 不限速
}
limit_rate $limit_rate;
}
}
# 使用 map 实现更复杂的限速
http {
map $cookie_user_type $download_rate {
default 100k;
"free" 100k;
"basic" 500k;
"premium" 2m;
"vip" 0; # 不限速
}
server {
location /files/ {
limit_rate $download_rate;
alias /data/files/;
}
}
}
实战配置
API 接口限流
http {
# 不同接口的限流策略
limit_req_zone $binary_remote_addr zone=api_common:10m rate=30r/s;
limit_req_zone $binary_remote_addr zone=api_search:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=api_write:10m rate=5r/s;
# 根据 API key 限流
map $http_x_api_key $api_key {
default $binary_remote_addr;
~.+ $http_x_api_key;
}
limit_req_zone $api_key zone=api_key_limit:10m rate=100r/s;
server {
listen 80;
server_name api.example.com;
# 默认 API 限流
location /api/ {
limit_req zone=api_common burst=50 nodelay;
limit_req zone=api_key_limit burst=200 nodelay;
limit_req_status 429;
proxy_pass http://api_backend;
}
# 搜索接口(更严格)
location /api/search {
limit_req zone=api_search burst=20 nodelay;
limit_req_status 429;
proxy_pass http://api_backend;
}
# 写接口(最严格)
location ~ ^/api/(create|update|delete) {
limit_req zone=api_write burst=10;
limit_req_status 429;
proxy_pass http://api_backend;
}
# 超限返回 JSON
error_page 429 = @rate_limit_exceeded;
location @rate_limit_exceeded {
default_type application/json;
return 429 '{"error": "Too Many Requests", "retry_after": 60}';
}
}
}
登录接口防暴力破解
http {
# 登录限流:每秒 1 个请求,允许 5 个突发
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
# 登录失败限流(基于 IP + 用户名)
map $request_body $login_user {
~*"username":"(?<user>[^"]+)" $user;
default "";
}
limit_req_zone $binary_remote_addr$login_user zone=login_user:10m rate=1r/m;
server {
location /api/login {
limit_req zone=login burst=5;
limit_req zone=login_user burst=3;
limit_req_status 429;
# 添加响应头
add_header X-RateLimit-Limit 5;
add_header X-RateLimit-Remaining $limit_req_status;
proxy_pass http://auth_backend;
}
# 注册接口
location /api/register {
limit_req zone=login burst=3;
limit_req_status 429;
proxy_pass http://auth_backend;
}
}
}
下载限速配置
http {
# 连接数限制
limit_conn_zone $binary_remote_addr zone=download_conn:10m;
# 根据用户等级设置速度
map $cookie_user_level $download_speed {
default 200k;
"1" 500k;
"2" 1m;
"3" 0; # 不限速
}
server {
location /download/ {
alias /data/downloads/;
# 每 IP 最多 2 个下载连接
limit_conn download_conn 2;
# 前 5MB 不限速,之后限速
limit_rate_after 5m;
limit_rate $download_speed;
# 禁止多线程下载
if ($http_range) {
return 416;
}
}
}
}
CDN 缓存配置
http {
proxy_cache_path /var/cache/nginx/cdn
levels=1:2
keys_zone=cdn_cache:100m
max_size=50g
inactive=7d
use_temp_path=off;
server {
listen 80;
server_name cdn.example.com;
location / {
proxy_pass http://origin_server;
# CDN 缓存
proxy_cache cdn_cache;
proxy_cache_key "$host$request_uri";
proxy_cache_valid 200 7d;
proxy_cache_valid 404 1m;
# 忽略源站缓存控制
proxy_ignore_headers Cache-Control Expires Set-Cookie;
# 使用过期缓存
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_background_update on;
proxy_cache_lock on;
# 添加缓存头
add_header X-Cache-Status $upstream_cache_status;
add_header X-Cache-Key $host$request_uri;
# 设置缓存控制
add_header Cache-Control "public, max-age=86400";
}
# 静态资源更长缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
proxy_pass http://origin_server;
proxy_cache cdn_cache;
proxy_cache_valid 200 30d;
add_header X-Cache-Status $upstream_cache_status;
add_header Cache-Control "public, max-age=2592000, immutable";
}
}
}
总结
本章介绍了 Nginx 缓存与限流机制:
- 浏览器缓存:expires、Cache-Control、ETag/Last-Modified
- 代理缓存:proxy_cache 配置、缓存 key、缓存绕过、缓存清理
- FastCGI 缓存:PHP 应用缓存配置
- 请求限流:limit_req、burst、nodelay、多级限流
- 连接限流:limit_conn 并发控制
- 带宽限制:limit_rate 下载限速
- 实战应用:API 限流、防暴力破解、下载限速、CDN 配置