概述
1.1 背景介绍
在多年的网站运维经历中,不合理的架构设计导致的性能问题屡见不鲜。例如,一个加载时间动辄数秒的电商网站,用户流失率会居高不下。此时,仅优化代码往往收效甚微,而对架构进行“动静分离”改造,则可能成为解决问题的关键。
所谓动静分离,其核心思想是将网站的静态资源(如CSS、JavaScript、图片、字体文件)与动态内容(由PHP、Python、Java等后端语言生成的页面或API响应)区分开来处理。静态资源直接由高效的Web服务器(如Nginx)或CDN快速响应,动态请求才被转发至后端应用服务器。这种架构带来的益处是多方面的:
- Nginx处理静态文件的性能远超传统应用服务器。
- 后端服务器得以从繁重的静态请求中解脱,负载显著降低。
- 静态资源可以充分利用浏览器本地缓存和CDN的边缘缓存。
- 动态服务可以独立进行横向扩展,架构更灵活。
1.2 技术特点
动静分离架构具备以下几个核心技术特点:
高效的资源分发
Nginx利用sentfile系统调用,能够在内核态直接完成文件数据传输,避免了用户态与内核态之间的数据拷贝开销,处理静态文件时单机QPS可达十万级别。
灵活的路由规则
通过location指令,可以精确匹配不同类型的请求(如使用正则表达式、前缀匹配等),并为其指定不同的处理逻辑。
多级缓存体系
结合浏览器缓存、CDN缓存以及Nginx自身的缓存,配合ETag和Last-Modified等HTTP缓存协商机制,可以构建一个智能、高效的缓存链路。
零停机部署
通过为静态资源添加版本号或哈希值的方式命名,可以实现资源更新时的无缝切换,用户访问不受影响。
1.3 适用场景
动静分离架构尤其适用于以下几类场景:
内容型网站
新闻门户、博客、文档站点等静态内容占比高的网站,优化效果最为显著。
电商平台
海量的商品图片、前端样式和脚本资源,分离后能极大减轻应用服务器的压力。
移动应用后端
可以将App的API接口请求与静态资源(如启动图、热更新包)分流到不同的处理链路。
高并发场景
在秒杀、大促等流量高峰期间,动静分离能有效避免静态资源请求冲垮后端核心服务。
1.4 环境要求
| 组件 |
版本要求 |
说明 |
| 操作系统 |
Rocky Linux 9 / Ubuntu 24.04 LTS |
推荐使用Rocky Linux 9作为生产环境 |
| Nginx |
1.26.x / 1.27.x |
建议使用mainline分支获取最新特性 |
| 后端服务 |
PHP-FPM 8.3 / Tomcat 10 / Gunicorn |
根据实际技术栈选择 |
| 存储 |
NFS / MinIO / 阿里云OSS |
静态资源集中存储方案 |
| 内存 |
最低4GB |
Nginx缓存需要足够内存 |
| 磁盘 |
SSD推荐 |
静态资源I/O密集,SSD效果明显 |
详细步骤
2.1 准备工作
安装Nginx
在Rocky Linux 9环境下安装:
# 添加Nginx官方仓库
cat > /etc/yum.repos.d/nginx.repo << 'EOF'
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
EOF
# 启用mainline分支(可选,获取最新版本)
dnf config-manager --enable nginx-mainline
# 安装Nginx
dnf install nginx -y
# 查看版本
nginx -v
# nginx version: nginx/1.27.3
在Ubuntu 24.04环境下安装:
# 安装依赖
apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring -y
# 导入官方GPG key
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
| tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
# 添加仓库
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/mainline/ubuntu `lsb_release -cs` nginx" \
| tee /etc/apt/sources.list.d/nginx.list
# 设置仓库优先级
echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
| tee /etc/apt/preferences.d/99nginx
# 安装
apt update && apt install nginx -y
创建目录结构
# 静态资源目录
mkdir -p /data/static/{css,js,images,fonts,uploads}
mkdir -p /data/www/html
# 日志目录
mkdir -p /var/log/nginx/{static,dynamic}
# 缓存目录
mkdir -p /var/cache/nginx/{static,proxy}
# 设置权限
chown -R nginx:nginx /data/static /data/www
chown -R nginx:nginx /var/cache/nginx
chmod -R 755 /data/static /data/www
规划URL结构
在配置前,需要清晰规划URL的匹配规则。以下是一个常见方案:
静态资源URL:
- /static/* -> 所有静态资源
- /assets/* -> 前端构建产物
- /uploads/* -> 用户上传文件
- *.css, *.js -> 样式和脚本
- *.jpg, *.png, *.gif, *.webp -> 图片
- *.woff, *.woff2, *.ttf -> 字体
动态请求URL:
- /api/* -> API接口
- /admin/* -> 管理后台
- /*.php -> PHP脚本
- 其他未匹配的请求 -> 转发后端
2.2 核心配置
主配置文件 /etc/nginx/nginx.conf
# Nginx主配置文件
# 针对动静分离场景优化
user nginx;
# worker进程数,设置为CPU核心数
worker_processes auto;
# 绑定CPU亲和性,减少上下文切换
worker_cpu_affinity auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
# 工作进程可打开的最大文件描述符数
worker_rlimit_nofile 65535;
events {
# 单个worker的最大连接数
worker_connections 65535;
# 使用epoll事件模型(Linux)
use epoll;
# 允许一次接受多个连接
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 自定义日志格式,区分静态和动态请求
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
log_format static '$remote_addr [$time_local] "$request" '
'$status $body_bytes_sent '
'rt=$request_time';
access_log /var/log/nginx/access.log main;
# 开启高效文件传输
sendfile on;
# 配合sendfile使用,减少网络包数量
tcp_nopush on;
# 禁用Nagle算法,减少延迟
tcp_nodelay on;
# 连接超时设置
keepalive_timeout 65;
keepalive_requests 1000;
# 客户端请求限制
client_max_body_size 100m;
client_body_buffer_size 128k;
client_header_buffer_size 4k;
large_client_header_buffers 4 32k;
# 隐藏版本号
server_tokens off;
# Gzip压缩(静态资源)
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 5;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript
application/json application/javascript application/xml
application/xml+rss application/x-javascript
image/svg+xml font/woff font/woff2;
# 静态文件缓存
open_file_cache max=10000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# 代理缓存配置
proxy_cache_path /var/cache/nginx/proxy
levels=1:2
keys_zone=proxy_cache:100m
max_size=10g
inactive=7d
use_temp_path=off;
# upstream后端服务器组
upstream backend_servers {
# 使用IP哈希保持会话
# ip_hash;
# 后端服务器列表
server 127.0.0.1:8080 weight=5 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 weight=5 max_fails=3 fail_timeout=30s;
# 备用服务器
server 127.0.0.1:8082 backup;
# 长连接配置
keepalive 32;
keepalive_requests 1000;
keepalive_timeout 60s;
}
# 引入站点配置
include /etc/nginx/conf.d/*.conf;
}
站点配置文件 /etc/nginx/conf.d/example.conf
# 动静分离站点配置
# 站点:example.com
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# 强制HTTPS(生产环境启用)
# return 301 https://$server_name$request_uri;
root /data/www/html;
index index.html index.htm;
# 访问日志
access_log /var/log/nginx/static/access.log static;
error_log /var/log/nginx/static/error.log warn;
# ==================== 静态资源处理 ====================
# 静态资源目录 - 最常用的匹配方式
location /static/ {
alias /data/static/;
# 开启高效传输
sendfile on;
tcp_nopush on;
# 缓存控制:静态资源缓存1年
expires 1y;
add_header Cache-Control "public, immutable";
# 允许跨域访问(字体文件需要)
add_header Access-Control-Allow-Origin *;
# 关闭访问日志减少I/O
access_log off;
# 找不到文件时返回404,不要转发到后端
try_files $uri =404;
}
# 前端构建产物(带hash的文件名)
location /assets/ {
alias /data/www/html/assets/;
# 带hash的文件可以永久缓存
expires max;
add_header Cache-Control "public, immutable";
access_log off;
try_files $uri =404;
}
# 用户上传文件
location /uploads/ {
alias /data/static/uploads/;
# 上传文件缓存时间短一些,方便更新
expires 7d;
add_header Cache-Control "public";
# 禁止执行脚本,安全考虑
location ~ \.(php|jsp|py|pl|sh)$ {
deny all;
}
try_files $uri =404;
}
# 图片文件
location ~* \.(jpg|jpeg|gif|png|ico|webp|bmp|svg)$ {
root /data/static/images;
expires 30d;
add_header Cache-Control "public";
# 开启图片防盗链
valid_referers none blocked server_names *.example.com;
if ($invalid_referer) {
return 403;
}
# 图片不存在时返回默认图片
try_files $uri /images/default.png =404;
access_log off;
}
# CSS/JS文件
location ~* \.(css|js)$ {
root /data/static;
# CSS/JS缓存1个月
expires 30d;
add_header Cache-Control "public";
# 字符集设置
charset utf-8;
access_log off;
try_files $uri =404;
}
# 字体文件
location ~* \.(woff|woff2|ttf|otf|eot)$ {
root /data/static/fonts;
expires 1y;
add_header Cache-Control "public, immutable";
# 字体需要CORS
add_header Access-Control-Allow-Origin *;
access_log off;
try_files $uri =404;
}
# ==================== 动态请求处理 ====================
# API接口转发
location /api/ {
# 记录详细日志
access_log /var/log/nginx/dynamic/api.log main;
# 反向代理到后端
proxy_pass http://backend_servers;
# 代理头设置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# HTTP/1.1支持长连接
proxy_http_version 1.1;
proxy_set_header Connection "";
# 超时设置
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲设置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
# API响应一般不缓存
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
# 管理后台
location /admin/ {
access_log /var/log/nginx/dynamic/admin.log main;
# 限制访问IP
allow 10.0.0.0/8;
allow 192.168.0.0/16;
deny all;
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# PHP处理(如果需要)
location ~ \.php$ {
access_log /var/log/nginx/dynamic/php.log main;
root /data/www/html;
# 安全检查:确保文件存在
try_files $uri =404;
# FastCGI配置
fastcgi_pass unix:/run/php-fpm/www.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# FastCGI缓冲
fastcgi_buffer_size 32k;
fastcgi_buffers 8 32k;
fastcgi_busy_buffers_size 64k;
# 超时
fastcgi_connect_timeout 30s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
}
# 默认动态请求处理
location / {
# 先尝试静态文件,找不到再转发后端
try_files $uri $uri/ @backend;
}
# 后端处理
location @backend {
access_log /var/log/nginx/dynamic/backend.log main;
proxy_pass http://backend_servers;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# ==================== 安全配置 ====================
# 禁止访问隐藏文件
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
# 禁止访问备份文件
location ~* \.(bak|swp|old|orig|save)$ {
deny all;
access_log off;
log_not_found off;
}
# robots.txt
location = /robots.txt {
allow all;
access_log off;
log_not_found off;
}
# favicon.ico
location = /favicon.ico {
access_log off;
log_not_found off;
expires 30d;
}
}
2.3 启动和验证
配置检查
# 检查配置语法
nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful
# 查看完整配置(调试用)
nginx -T
# 查看编译参数
nginx -V
启动服务
# 启动Nginx
systemctl start nginx
# 设置开机自启
systemctl enable nginx
# 查看状态
systemctl status nginx
# 查看进程
ps aux | grep nginx
验证动静分离效果
# 创建测试静态文件
echo "body { color: #333; }" > /data/static/css/test.css
echo "console.log('test');" > /data/static/js/test.js
echo '<html><body>Dynamic Content</body></html>' > /data/www/html/index.html
# 测试静态CSS访问
curl -I http://localhost/static/css/test.css
# HTTP/1.1 200 OK
# Cache-Control: public, immutable
# Expires: Thu, 01 Jan 2026 00:00:00 GMT
# 测试静态JS访问
curl -I http://localhost/static/js/test.js
# 测试动态页面
curl -I http://localhost/
# 查看响应头中的缓存设置差异
性能测试
# 安装ab测试工具
dnf install httpd-tools -y # Rocky Linux
apt install apache2-utils -y # Ubuntu
# 测试静态文件性能
ab -n 10000 -c 100 http://localhost/static/css/test.css
# Requests per second: 85000+ (取决于硬件)
# 测试动态请求性能
ab -n 1000 -c 50 http://localhost/api/test
示例代码和配置
3.1 完整配置示例
多域名动静分离配置
在实际生产环境中,为静态资源配置独立域名是常见的优化手段,这能带来诸多好处:突破浏览器对同一域名的并发连接数限制、避免静态请求携带无用的Cookie信息、更便于CDN加速配置等。
# 静态资源域名配置 static.example.com
server {
listen 80;
server_name static.example.com;
root /data/static;
# 全局缓存设置
expires 30d;
add_header Cache-Control "public";
# 关闭access_log提升性能
access_log off;
# 所有请求都是静态文件
location / {
try_files $uri =404;
}
# 带版本号的资源永久缓存
location ~* \.(css|js)\?v= {
expires max;
add_header Cache-Control "public, immutable";
}
# 防盗链
valid_referers none blocked server_names
*.example.com example.com;
if ($invalid_referer) {
return 403;
}
}
# 主站配置 www.example.com
server {
listen 80;
server_name www.example.com example.com;
root /data/www/html;
index index.html;
access_log /var/log/nginx/www.access.log main;
# 静态资源重定向到静态域名
location ~* \.(css|js|jpg|jpeg|png|gif|ico|woff|woff2)$ {
return 301 http://static.example.com$request_uri;
}
# 其他请求走后端
location / {
try_files $uri $uri/ @backend;
}
location @backend {
proxy_pass http://backend_servers;
include proxy_params;
}
}
代理参数复用文件 /etc/nginx/proxy_params
# 通用代理参数配置
# 避免在每个location中重复配置
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
# HTTP/1.1长连接
proxy_http_version 1.1;
proxy_set_header Connection "";
# 超时设置
proxy_connect_timeout 30s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 缓冲设置
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
# 失败重试
proxy_next_upstream error timeout invalid_header http_500 http_502 http_503;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
3.2 实际应用案例
案例一:电商网站动静分离
以下是对一个日均PV达500万的电商网站进行动静分离改造的架构示例。改造带来了显著的性能与成本优化。
| 指标 |
改造前 |
改造后 |
提升幅度 |
| 首页加载时间 |
3.2s |
0.8s |
75% |
| 静态资源响应时间 |
200ms |
15ms |
92% |
| 后端服务器CPU |
85% |
35% |
59% |
| 带宽成本 |
100% |
40% |
60% |
# 电商网站配置架构
# 三层结构:CDN -> Nginx -> 应用服务器
# 商品图片处理
location /goods/ {
alias /data/images/goods/;
# 图片处理参数(配合image_filter模块)
# 实时生成缩略图
location ~* /goods/(\d+)_(\d+)x(\d+)\.(jpg|png)$ {
alias /data/images/goods/;
set $image_id $1;
set $width $2;
set $height $3;
set $ext $4;
# 优先返回已生成的缩略图
try_files /cache/${image_id}_${width}x${height}.${ext}
@generate_thumb;
}
expires 30d;
add_header Cache-Control "public";
}
# 商品详情页(半静态化)
location ~* ^/item/(\d+)\.html$ {
set $item_id $1;
# 优先返回静态化的页面
try_files /cache/item/$item_id.html @dynamic_item;
expires 5m;
add_header Cache-Control "public, must-revalidate";
}
location @dynamic_item {
proxy_pass http://backend_servers;
include proxy_params;
# 开启代理缓存
proxy_cache proxy_cache;
proxy_cache_valid 200 5m;
proxy_cache_key "$scheme$host$request_uri";
add_header X-Cache-Status $upstream_cache_status;
}
案例二:前后端分离SPA应用
现代前端应用(React/Vue/Angular)打包后本质上是静态文件,天然契合动静分离架构。
# SPA应用配置
server {
listen 80;
server_name app.example.com;
root /data/www/spa/dist;
index index.html;
# 前端构建产物(带hash)
location /assets/ {
expires max;
add_header Cache-Control "public, immutable";
access_log off;
}
# 前端路由支持
# 所有路由都返回index.html,由前端路由处理
location / {
try_files $uri $uri/ /index.html;
# index.html不缓存,保证用户获取最新版本
location = /index.html {
expires -1;
add_header Cache-Control "no-store, no-cache, must-revalidate";
}
}
# API转发到后端
location /api/ {
proxy_pass http://api_servers;
include proxy_params;
# API跨域设置
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization, Content-Type";
add_header Access-Control-Allow-Credentials true;
# OPTIONS预检请求直接返回
if ($request_method = OPTIONS) {
return 204;
}
}
# WebSocket支持(如果需要)
location /ws/ {
proxy_pass http://websocket_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
}
}
案例三:多租户SaaS平台
# 多租户静态资源隔离
# 每个租户有独立的静态目录
map $host $tenant_id {
default "default";
~^(?<tenant>.+)\.example\.com$ $tenant;
}
server {
listen 80;
server_name *.example.com;
# 租户静态资源目录
set $static_root /data/tenants/$tenant_id/static;
location /static/ {
alias $static_root/;
# 检查租户目录是否存在
if (!-d $static_root) {
return 404;
}
expires 30d;
access_log off;
}
# 租户自定义主题
location /theme/ {
alias /data/tenants/$tenant_id/theme/;
expires 1d;
}
# 公共资源(所有租户共享)
location /common/ {
alias /data/common/;
expires 30d;
access_log off;
}
location / {
proxy_pass http://backend_servers;
proxy_set_header X-Tenant-ID $tenant_id;
include proxy_params;
}
}
最佳实践和注意事项
4.1 最佳实践
静态资源版本控制
为了避免因浏览器缓存旧文件导致的功能异常,必须对静态资源进行版本控制。推荐以下两种方案,其中第二种更优:
# 方案一:查询参数版本号
/static/app.js?v=1.2.3
/static/style.css?v=20250107
# 方案二:文件名hash(推荐)
/assets/app.8a7f3e2d.js
/assets/style.b4c2a1f9.css
方案二(文件名哈希)的优势在于:CDN对带查询参数URL的缓存策略可能不一致,而基于文件内容的哈希可以实现真正的永久缓存,且现代前端构建工具(如Webpack、Vite)都原生支持。
Location匹配优先级
理解Nginx的location匹配优先级至关重要,错误的顺序可能导致预期外的行为。匹配优先级从高到低依次为:
# 1. 精确匹配 =
location = /favicon.ico { }
# 2. 前缀匹配 ^~(匹配后停止搜索正则)
location ^~ /static/ { }
# 3. 正则匹配 ~(区分大小写)
location ~ \.(jpg|png)$ { }
# 4. 正则匹配 ~*(不区分大小写)
location ~* \.(jpg|png)$ { }
# 5. 普通前缀匹配(最长匹配)
location /api/ { }
# 6. 通用匹配
location / { }
合理使用try_files
try_files指令是处理文件检查的推荐方式,应避免使用低效且复杂的if判断。
# 好的写法
location / {
try_files $uri $uri/ @backend;
}
# 不好的写法(多余的判断)
location / {
if (-f $request_filename) {
break;
}
if (-d $request_filename) {
break;
}
proxy_pass http://backend;
}
利用浏览器缓存层级
针对不同类型的资源,设置差异化的缓存策略。
# 长期不变的资源:字体、第三方库
location ~* \.(woff|woff2|ttf|otf|eot)$ {
expires max; # 10年
add_header Cache-Control "public, immutable";
}
# 经常变化的资源:业务图片
location ~* \.(jpg|jpeg|png|gif|webp)$ {
expires 30d;
add_header Cache-Control "public";
# 支持协商缓存
etag on;
}
# 频繁更新的资源:HTML入口
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
etag on;
}
4.2 注意事项
常见错误表
| 错误场景 |
错误配置 |
正确配置 |
影响 |
| 静态目录权限 |
chmod 777 |
chmod 755 + chown nginx |
安全风险 |
| alias路径 |
alias /data/static |
alias /data/static/ |
路径拼接错误 |
| root vs alias |
location /img/ { root /data/static/img/; } |
location /img/ { alias /data/static/img/; } |
404错误 |
| 正则捕获 |
location ~ /goods/(.*).jpg |
location ~ ^/goods/(.*).jpg$ |
匹配范围过大 |
| expires设置 |
expires 30d;(放在server级别) |
放在具体location中 |
动态请求被缓存 |
| CORS头重复 |
多处add_header |
统一位置配置 |
浏览器报错 |
安全注意事项
# 1. 禁止目录遍历
autoindex off;
# 2. 禁止访问敏感文件
location ~* \.(htaccess|htpasswd|ini|log|sh|sql|bak)$ {
deny all;
}
# 3. 限制上传目录执行权限
location /uploads/ {
location ~ \.(php|jsp|py|pl|cgi)$ {
return 403;
}
}
# 4. 防止MIME类型嗅探
add_header X-Content-Type-Options "nosniff";
# 5. 限制请求方法
if ($request_method !~ ^(GET|HEAD|POST)$) {
return 405;
}
性能陷阱
# 陷阱1:过多的if判断
# Nginx的if是"evil"的,能用try_files替代就不用if
# 错误示例
if (-f $request_filename) {
set $is_static 1;
}
if ($is_static = 1) {
expires 30d;
}
# 正确做法
try_files $uri $uri/ @backend;
# 陷阱2:gzip对已压缩文件二次压缩
# 图片、视频已经压缩过,再gzip会浪费CPU
gzip_types text/plain text/css application/json application/javascript;
# 不要加 image/jpeg image/png 等
# 陷阱3:access_log缓冲不当
# 高并发时日志I/O会成为瓶颈
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
# 陷阱4:open_file_cache配置过大
# 内存有限,缓存项过多会适得其反
open_file_cache max=10000 inactive=60s; # 根据实际文件数调整
故障排查和监控
5.1 故障排查
问题一:静态文件404
# 排查步骤
# 1. 确认文件存在
ls -la /data/static/css/test.css
# 2. 确认Nginx用户权限
sudo -u nginx cat /data/static/css/test.css
# 3. 检查SELinux(Rocky Linux常见问题)
getenforce
# 如果是Enforcing,执行:
restorecon -Rv /data/static/
# 或者调整SELinux策略
chcon -R -t httpd_sys_content_t /data/static/
# 4. 检查location配置
nginx -T | grep -A 20 'location.*static'
# 5. 查看错误日志
tail -f /var/log/nginx/error.log
问题二:静态资源被转发到后端
症状:静态资源响应时间长,响应头中出现X-Powered-By等后端框架特有的标记。
原因:通常是location匹配顺序或规则问题,导致静态请求命中了代理动态请求的规则。
排查:使用curl -I查看响应头,判断实际走哪个处理逻辑。
解决:调整location顺序,或对静态路径使用^~前缀匹配以阻止后续的正则匹配。
问题三:缓存不生效
# 检查响应头
curl -I http://localhost/static/test.css
# 应该看到类似:
# Cache-Control: public, max-age=2592000
# Expires: Wed, 06 Feb 2025 00:00:00 GMT
# ETag: "65a1b2c3-1234"
# Last-Modified: Wed, 07 Jan 2025 00:00:00 GMT
# 如果没有这些头,检查:
# 1. expires指令位置是否正确
# 2. 是否有其他location覆盖了配置
# 3. 是否开启了proxy_ignore_headers
问题四:权限拒绝(403)
# 1. 检查目录可执行权限
namei -l /data/static/css/test.css
# 所有父目录都需要x权限
# 2. 检查Nginx运行用户
ps aux | grep nginx
# 确认worker进程用户
# 3. 检查文件所有者
ls -la /data/static/css/
# 文件应该对Nginx用户可读
5.2 性能监控
Nginx状态监控
# 启用stub_status模块
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
allow 10.0.0.0/8;
deny all;
}
# 查看状态
curl http://localhost/nginx_status
# 输出示例:
# Active connections: 256
# server accepts handled requests
# 12345678 12345678 98765432
# Reading: 5 Writing: 128 Waiting: 123
# 指标解释:
# Active connections: 当前活动连接数(包括等待的)
# accepts: 已接受的连接总数
# handled: 已处理的连接总数(正常情况等于accepts)
# requests: 已处理的请求总数
# Reading: 正在读取请求头的连接数
# Writing: 正在发送响应的连接数
# Waiting: 保持连接等待下次请求的空闲连接数
Prometheus监控集成
# nginx-prometheus-exporter配置
# docker-compose.yml
version: '3'
services:
nginx-exporter:
image: nginx/nginx-prometheus-exporter:1.1
ports:
- "9113:9113"
command:
- '-nginx.scrape-uri=http://nginx:80/nginx_status'
日志分析脚本
#!/bin/bash
# nginx_stats.sh - 快速分析Nginx访问日志
LOG_FILE=${1:-/var/log/nginx/access.log}
echo "=== Nginx访问统计 ==="
echo ""
echo "--- 请求总数 ---"
wc -l $LOG_FILE
echo ""
echo "--- 静态vs动态请求占比 ---"
awk '{
if ($7 ~ /\.(css|js|jpg|png|gif|ico|woff)/) {
static++
} else {
dynamic++
}
}
END {
total = static + dynamic
printf "静态请求: %d (%.1f%%)\n", static, static/total*100
printf "动态请求: %d (%.1f%%)\n", dynamic, dynamic/total*100
}' $LOG_FILE
echo ""
echo "--- 响应状态码分布 ---"
awk '{print $9}' $LOG_FILE | sort | uniq -c | sort -rn | head -10
echo ""
echo "--- Top 10 慢请求(静态) ---"
awk '$7 ~ /\.(css|js|jpg|png|gif)/ {print $0}' $LOG_FILE | \
awk -F'rt=' '{print $2}' | \
sort -rn | head -10
echo ""
echo "--- 静态资源带宽统计 ---"
awk '$7 ~ /\.(css|js|jpg|png|gif)/ {sum+=$10} END {printf "总计: %.2f MB\n", sum/1024/1024}' $LOG_FILE
5.3 备份与恢复
配置备份脚本
#!/bin/bash
# backup_nginx_config.sh
BACKUP_DIR="/backup/nginx"
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/nginx_config_$DATE.tar.gz"
# 创建备份目录
mkdir -p $BACKUP_DIR
# 备份配置文件
tar -czf $BACKUP_FILE \
/etc/nginx/ \
/data/static/ \
--exclude='*.log'
# 保留最近30天的备份
find $BACKUP_DIR -name "nginx_config_*.tar.gz" -mtime +30 -delete
echo "备份完成: $BACKUP_FILE"
快速恢复流程
# 1. 停止Nginx
systemctl stop nginx
# 2. 恢复配置
cd /
tar -xzf /backup/nginx/nginx_config_20250107_120000.tar.gz
# 3. 检查配置
nginx -t
# 4. 启动服务
systemctl start nginx
# 5. 验证
curl -I http://localhost/static/test.css
总结
6.1 技术要点回顾
动静分离架构的核心可以归纳为三点:
- 精准分流:通过精心设计的Nginx
location规则,将静态请求与动态请求准确分开处理。
- 高效传输:充分利用
sentfile、tcp_nopush、gzip等特性,最大化静态资源的网络传输效率。
- 智能缓存:构建从浏览器、CDN到Nginx本身的多级缓存体系,并合理设置缓存策略。
关键配置参数总结:
# 静态资源必备配置
sendfile on;
tcp_nopush on;
expires 30d;
open_file_cache max=10000 inactive=60s;
# 动态代理必备配置
proxy_http_version 1.1;
proxy_set_header Connection "";
keepalive 32;
6.2 进阶学习方向
- HTTP/2和HTTP/3:利用多路复用等特性进一步提升静态资源加载效率。
- 边缘计算:将动静分离的思想延伸到CDN边缘节点,实现更快的用户响应。
- Service Mesh:在云原生架构下探索动静分离的新实践。
- WebP/AVIF自适应:根据客户端能力,动态返回最优的图片格式以节省带宽。
6.3 参考资料
附录
A. 命令速查表
| 命令 |
说明 |
nginx -t |
检查配置语法 |
nginx -T |
显示完整配置 |
nginx -s reload |
平滑重载配置 |
nginx -s stop |
快速停止 |
nginx -s quit |
优雅停止 |
nginx -V |
显示版本和编译参数 |
B. 配置参数详解
| 参数 |
默认值 |
推荐值 |
说明 |
worker_processes |
1 |
auto |
工作进程数 |
worker_connections |
512 |
65535 |
单进程最大连接数 |
sendfile |
off |
on |
高效文件传输 |
tcp_nopush |
off |
on |
减少网络包 |
keepalive_timeout |
75s |
65s |
长连接超时 |
gzip_comp_level |
1 |
5 |
压缩级别 |
open_file_cache |
off |
max=10000 |
文件描述符缓存 |
C. 术语表
| 术语 |
解释 |
| 动静分离 |
将动态请求和静态资源请求分开处理的架构模式 |
| sendfile |
Linux内核系统调用,实现零拷贝文件传输 |
| upstream |
Nginx反向代理的后端服务器组 |
| location |
Nginx配置中的请求匹配规则块 |
| try_files |
按顺序检查文件存在性的指令 |
| proxy_pass |
将请求转发到指定后端服务器的指令 |
| expires |
设置HTTP缓存过期时间的指令 |
| ETag |
HTTP协商缓存的实体标签 |
本文介绍的技术实践和配置经验,均可在 云栈社区 找到更多相关的深度讨论和实战案例,欢迎开发者们一起交流学习。