写在前面:Nginx 是我认为“性价比”最高的技术之一——学习成本低,但能解决的问题极多。本文不作教科书式的罗列,只分享实际生产环境中踩过坑、验证过的实战技巧。
来源:Nginx 能做什么好玩的事情? – 花宝宝的回答 – 知乎 https://www.zhihu.com/question/21483073/answer/1987553926904580027
1. 流量控制:用 map + split_clients 做真正的灰度发布
痛点:普通灰度发布仅调整 upstream 权重,导致同一用户刷新页面时版本跳变(一会儿新版,一会儿老版),体验极差。
核心思路:按用户 ID 的哈希值分流,保证同一用户始终访问同一版本。
配置示例
# split_clients 会对 cookie_user_id 做哈希,同一个 ID 永远落到同一个桶里
split_clients "${cookie_user_id}" $variant {
10% new; # 10% 的用户走新版
* old; # 剩余走旧版
}
map $variant $backend {
new new_backend;
old old_backend;
}
upstream new_backend {
server 192.168.1.11:8080;
}
upstream old_backend {
server 192.168.1.10:8080;
}
server {
location / {
proxy_pass http://$backend;
}
}
实战经验:
上线大改版时,建议路径:
1% (观察三天)->10%->100% (全量)。曾遇到新版页面白屏,直接将比例改为
0%,用户端无感知瞬间回滚。
2. 负载均衡详解:权重、容灾与健康检查
场景:后端有多台服务器,性能可能不一,且需要考虑单点故障的自动剔除。这里不讲最简单的轮询,讲点生产环境必须具备的策略。
配置示例
upstream backend_cluster {
# 策略1:IP Hash
# ip_hash; # 开启后同一 IP 固定访问同一台,解决 Session 丢失问题(已有 Redis Session 则不需要)
# 策略2:权重分配 (性能强的多分担)
server 192.168.1.10:8080 weight=5;
server 192.168.1.11:8080 weight=1;
# 策略3:容灾兜底 (Backup)
# 正常情况不访问,只有当上面两台主服务器全挂了,才会请求这台
# 常用于挂载"系统维护中"的静态页面服务
server 192.168.1.12:8080 backup;
# 策略4:停机维护 (Down)
# 标记为 down,Nginx 会自动跳过,无需注释代码
server 192.168.1.13:8080 down;
# 策略5:被动健康检查 (关键)
# 30秒内失败(超时/报错)达3次,则认为该节点挂掉,30秒内不再转发请求给它
server 192.168.1.14:8080 max_fails=3 fail_timeout=30s;
}
server {
location / {
proxy_pass http://backend_cluster;
# 必须加,否则后端拿不到真实 IP
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# 配合 max_fails 使用,定义什么是"失败"(默认只有连接不上才算)
# 加上 http_500 http_502,后端报错也算一次失败
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
}
}
避坑指南:
- ip_hash:如果你的 Nginx 前面还有一层负载均衡(如阿里云 SLB),且没有透传真实 IP,那么
ip_hash可能会失效(所有请求被认为是同一个 IP)。- backup:这是个救命参数。当所有后端都挂了,backup 机器可以返回一个友好的“系统升级中”页面,而不是直接报错。
3. 限流避坑:彻底理解 burst 和 nodelay
痛点:配置了限流,但效果要么太严(误杀),要么没效果,核心是不理解这两个参数的组合逻辑。
基础配置
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
location /api/ {
# 关键配置
limit_req zone=api burst=20 nodelay;
}
参数深度解析
- rate=10r/s:令牌桶每秒放入 10 个令牌(即每 100ms 放入 1 个)。
- burst=20:令牌桶容量,最多允许积压 20 个令牌(应对突发流量)。
- nodelay:关键点。有令牌就立刻放行,不排队。
场景模拟
- 不加 nodelay:用户 1 秒内发 15 个请求。Nginx 会让这 15 个请求排队,每 100ms 处理一个。结果:用户感觉接口响应变慢,但不会报错。
- 加了 nodelay (推荐):桶里有 20 个令牌,瞬间来 15 个请求 -> 全部立刻放行,桶剩 5 个。若桶空了,第 21 个请求直接返回 503。
建议:
- API 接口:建议加
nodelay,宁可限制报错,也别让用户排队空等。- 静态资源:可以不加,让请求排队慢慢加载,保护带宽。
- 折中方案:
limit_req zone=api burst=20 delay=10;(前 10 个立刻放行,后 10 个排队,兼顾体验与削峰)。
4. 性能优化:把 Nginx 当 CDN 用 (Proxy Cache)
场景:后端接口数据更新频率低(如热点文章列表),但请求量大,直接打数据库不仅浪费且易崩。
配置示例
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:100m inactive=60m max_size=1g;
server {
location /api/hot-articles {
proxy_cache api_cache;
proxy_cache_valid 200 5m; # 200 状态码缓存 5 分钟
proxy_cache_valid 404 1m; # 404 缓存 1 分钟
proxy_cache_key "$request_uri";
# 核心容灾配置:后端挂了或超时,直接返回旧缓存,保命神器
proxy_cache_use_stale error timeout updating;
# 调试用 header
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://backend;
}
}
实战效果:某热点接口 QPS 从 2000 降至后端仅 400(命中率 80%),数据库压力骤降。
骚操作:配置
proxy_cache_bypass $cookie_nocache $arg_nocache;,运营人员访问时带上?nocache=1即可强制刷新看最新效果。
5. 前端部署:try_files 的妙用与多项目共存
场景:Vue/React SPA 项目,刷新页面出现 404;或者一个域名下挂载多个前端项目。
标准 SPA 配置
location / {
root /var/www/dist;
index index.html;
# 找不到文件就返回 index.html,交给前端路由处理
try_files $uri $uri/ /index.html;
}
进阶:多项目共存 (root vs alias)
# 访问 /admin -> /var/www/admin-dist/index.html
location /admin {
alias /var/www/admin-dist; # 注意这里用 alias
try_files $uri $uri/ /admin/index.html;
}
# 访问 /h5 -> /var/www/h5-dist/index.html
location /h5 {
alias /var/www/h5-dist;
try_files $uri $uri/ /h5/index.html;
}
踩坑预警:
root+location /admin:实际寻找/var/www/admin。alias+location /admin:实际寻找/var/www/admin-dist(替换逻辑)。- 这是 Nginx 新手最容易混淆的地方。
6. 日志分析:找出慢请求的真凶
痛点:默认日志看不出是网络慢、Nginx 慢还是后端慢。
方案:自定义 log_format,增加时间维度。
配置示例
log_format detailed '$remote_addr [$time_local] "$request" '
'$status $body_bytes_sent '
'rt=$request_time ' # 总时间
'uct=$upstream_connect_time ' # 连后端耗时
'uht=$upstream_header_time ' # 后端响应头耗时
'urt=$upstream_response_time ' # 后端完整响应耗时
'"$http_user_agent"';
判责指南
- uht/urt 很大 (
rt=5.2 uct=0.01 uht=5.1):后端背锅。处理逻辑慢,或者数据库慢。 - uct 很大 (
uct=5.0):连接层背锅。后端连接数满了,或者网络不通。 - rt 很大,但 uct/uht 很小:Nginx 背锅。可能是 Gzip 压缩大文件耗时,或者客户端网速太慢(发送数据慢)。
7. 扩展能力:用 Nginx + Lua 做简易 API 网关
场景:不想上 Kong/APISIX 等重型网关,但需要做灵活的动态限流或鉴权。
方案:OpenResty 或 ngx_http_lua_module。
Redis 动态限流示例
location /api/ {
access_by_lua_block {
local limit = 100
local key = ngx.var.remote_addr
local redis = require "resty.redis"
-- 连接 Redis 逻辑...
local current = red:incr(key)
if current == 1 then
red:expire(key, 1)
end
if current > limit then
ngx.exit(429)
end
}
proxy_pass http://backend;
}
应用:比自带的
limit_req灵活,可实现按 UserID 限流、黑名单拦截、简单签名验证。
8. 好玩实战:搭建私人直播服务器
工具:nginx-rtmp-module
流程:OBS 推流 -> Nginx 处理 -> 浏览器 HLS 播放。
核心配置
rtmp {
server {
listen 1935;
application live {
live on;
hls on;
hls_path /var/www/hls;
hls_fragment 3; # 切片大小,影响延迟
}
}
}
http {
server {
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
}
root /var/www;
add_header Cache-Control no-cache;
}
}
}
留言