Nginx 高级应用
Nginx 架构回顾
Nginx 采用 Master-Worker 多进程架构,结合事件驱动模型与异步非阻塞 I/O,在高并发场景下以极低的资源消耗实现卓越性能。
Master-Worker 进程模型
Nginx 启动后产生一个 master 进程和多个 worker 进程:
- Master 进程:负责读取和校验配置文件、管理 worker 进程(启动、停止、平滑重载)、绑定端口、处理信号。
- Worker 进程:实际处理客户端请求。每个 worker 进程独立运行,彼此不共享连接,通过操作系统提供的原子操作实现互斥锁,避免惊群效应。
master
├── worker-1 (CPU-0)
├── worker-2 (CPU-1)
├── worker-3 (CPU-2)
└── worker-4 (CPU-3)配置 worker 数量通常与 CPU 核数一致:
worker_processes auto; # Nginx 1.13+ 支持 auto,自动检测 CPU 核数
worker_cpu_affinity auto; # 将 worker 进程绑定到固定 CPU 核心事件驱动与异步非阻塞
Nginx 不依赖线程池处理请求,而是基于操作系统的高效事件通知机制,在 Linux 上使用 epoll,在 FreeBSD 上使用 kqueue。
与传统架构对比:
| 特性 | Apache (prefork) | Apache (worker) | Nginx |
|---|---|---|---|
| 并发模型 | 进程/线程 per 连接 | 线程 per 连接 | 事件驱动 |
| 内存占用 | 线性增长 | 较高 | 恒定(数万连接仅几十 MB) |
| C10K 支持 | 弱 | 一般 | 强 |
| 稳定性 | 进程隔离 | 线程共享内存 | 进程隔离 |
核心配置:
events {
use epoll; # Linux 默认使用 epoll
worker_connections 10240; # 单 worker 最大连接数
multi_accept on; # 一次 accept 多个新连接
accept_mutex on; # 防止惊群效应
}Nginx 处理请求的生命周期可划分为多个阶段,这是后续模块扩展的基础:
NGX_HTTP_POST_READ → SERVER_REWRITE → FIND_CONFIG → REWRITE → POST_REWRITE
→ PREACCESS → ACCESS → POST_ACCESS → PRECONTENT → CONTENT → LOGNginx Lua 模块
lua-nginx-module 简介
lua-nginx-module 将 Lua 脚本引擎嵌入 Nginx,允许开发者在 Nginx 各个处理阶段嵌入 Lua 逻辑。它将 LuaJIT 虚拟机直接集成到 Nginx worker 进程中,脚本执行性能极高(接近 C 扩展)。
安装
方式一:从 OpenResty 直接使用(内置 Lua 模块)。 方式二:在标准 Nginx 中通过动态模块加载:
# 使用 ngx_openresty 提供的编译脚本
./configure --add-module=/path/to/ngx_devel_kit \
--add-module=/path/to/lua-nginx-module \
--add-module=/path/to/echo-nginx-module
make -j$(nproc)
make install各阶段 Hook
Nginx 请求处理按阶段划分,lua-nginx-module 提供了对应阶段的指令:
| 阶段 | 指令 | 执行时机 | 典型用途 |
|---|---|---|---|
| 改写阶段 | rewrite_by_lua | URL 重写时 | 动态 URL 改写、参数校验 |
| 访问阶段 | access_by_lua | 访问控制时 | 认证鉴权、IP 黑名单 |
| 内容阶段 | content_by_lua_block | 生成响应时 | 动态响应生成 |
| 日志阶段 | log_by_lua | 记录日志时 | 日志自定义、数据上报 |
| 头部过滤 | header_filter_by_lua | 输出响应头时 | 修改响应头 |
| 正文过滤 | body_filter_by_lua | 输出响应体时 | 响应体压缩/替换 |
| SSL 阶段 | ssl_certificate_by_lua | SSL 握手时 | 动态证书选择 |
| 初始化 | init_by_lua | master 启动时 | 全局预加载 |
| worker 初始化 | init_worker_by_lua | worker 启动时 | 定时任务、心跳 |
示例:使用 content_by_lua_block 动态生成 JSON 响应:
server {
listen 80;
server_name api.example.com;
location /hello {
default_type 'application/json';
content_by_lua_block {
local args = ngx.req.get_uri_args()
local name = args.name or "World"
ngx.say('{"message": "Hello, ' .. name .. '!"}')
}
}
}示例:使用 access_by_lua 实现 Token 校验:
location /api {
access_by_lua_block {
local token = ngx.var.http_authorization
if not token then
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
-- 调用 Redis 验证 Token
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.log(ngx.ERR, "redis connect error: ", err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end
local uid, err = red:get("token:" .. token)
if not uid then
ngx.exit(ngx.HTTP_UNAUTHORIZED)
end
ngx.var.uid = uid -- 传递到后续处理
}
proxy_pass http://backend;
}共享内存字典(lua_shared_dict)
共享字典是 Nginx worker 间共享的键值存储,基于红黑树 + 锁实现,可用于限流、缓存、计数器等场景。
# 定义共享内存,大小 10MB
lua_shared_dict my_cache 10m;
lua_shared_dict rate_limit 1m;
server {
listen 80;
location /cache {
content_by_lua_block {
local dict = ngx.shared.my_cache
local key = ngx.var.uri
local val = dict:get(key)
if val then
ngx.say("CACHED: ", val)
return
end
-- 模拟耗时的数据生成
local result = "computed: " .. os.time()
dict:set(key, result, 60) -- 缓存 60 秒
ngx.say(result)
}
}
}实战:动态限流
使用 Lua + 共享字典实现基于 IP + URL 的动态限流:
-- rate_limit.lua
local limit = ngx.shared.rate_limit
local key = ngx.var.binary_remote_addr .. ":" .. ngx.var.uri
local limit_count = 10 -- 每 10 秒最多 10 次
local window = 10 -- 时间窗口(秒)
local current, err = limit:get(key)
if current then
if current >= limit_count then
ngx.log(ngx.WARN, "rate limit exceeded: ", key)
ngx.exit(ngx.HTTP_TOO_MANY_REQUESTS) -- 429
end
limit:incr(key, 1)
else
limit:set(key, 1, window)
endlocation /api/ {
access_by_lua_file /etc/nginx/lua/rate_limit.lua;
proxy_pass http://backend;
}实战:动态路由
根据请求的 URL 参数或 Header 动态选择后端服务:
-- dynamic_route.lua
local router = {
v1 = "http://backend-v1:8080",
v2 = "http://backend-v2:8080",
v3 = "http://backend-v3:8080",
}
local version = ngx.var.http_x_api_version or "v1"
local backend = router[version]
if not backend then
ngx.status = ngx.HTTP_BAD_REQUEST
ngx.say('{"error": "unsupported version"}')
ngx.exit(ngx.HTTP_OK)
end
-- 动态改写 upstream
local ok, err = ngx.exec("@proxy_" .. version)
if not ok then
ngx.log(ngx.ERR, "failed to route: ", err)
ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
endOpenResty
OpenResty 简介
OpenResty 是一个基于 Nginx 与 LuaJIT 的全功能 Web 应用服务器。它将 Nginx 扩展为一个完整的应用平台,集成了:
- Nginx(核心 Web 服务器)
- LuaJIT(高性能即时编译 Lua 引擎)
- lua-nginx-module(各阶段 Lua 注入)
- 大量 Lua 库(resty.redis、resty.mysql、resty.http 等)
cosocket 原理
cosocket(协同套接字)是 OpenResty 的核心创新。它通过在 Lua 协程中封装异步 I/O 操作,让开发者可以用同步的编程风格编写异步代码,无需回调。
-- 看似同步阻塞,实际内部是异步非阻塞
local function query_db()
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000)
-- cosocket:此处 yield 协程,worker 继续处理其他请求
local ok, err = red:connect("127.0.0.1", 6379)
-- 数据就绪后 resume 协程,继续执行
local res, err = red:get("some_key")
-- ...
endcosocket 工作流程:
请求进入 → 调用 red:get()
→ Lua 协程 yield(挂起)
→ worker 转去处理其他请求
→ Redis 返回数据 → epoll 通知就绪
→ Lua 协程 resume(恢复)
→ 返回结果给调用方得益于 cosocket,OpenResty 在发起外部请求(数据库、HTTP 调用)时,不会阻塞 worker 进程,单个 worker 可以同时管理数千个 cosocket 连接。
OpenResty 与标准 Nginx 对比
| 特性 | 标准 Nginx | OpenResty |
|---|---|---|
| 语言扩展 | C 模块 | Lua/LuaJIT |
| 开发效率 | 低(需 C 编译) | 高(脚本热加载) |
| 数据库直连 | 不支持 | 支持(cosocket) |
| 动态路由 | 不支持 | 支持 |
| 运行时逻辑 | 弱 | 强 |
| 性能 | 极高 | 极高(LuaJIT 接近原生) |
| 模块生态 | C 模块生态 | Lua 库 + C 模块 |
| 配置复杂度 | 中等 | 中等偏高 |
示例:OpenResty 中直接查询 MySQL 并返回 JSON:
server {
listen 80;
location /user {
default_type 'application/json';
content_by_lua_block {
local mysql = require "resty.mysql"
local db, err = mysql:new()
db:set_timeout(1000)
local ok, err = db:connect({
host = "127.0.0.1",
port = 3306,
database = "test",
user = "root",
password = "secret"
})
local uid = ngx.var.arg_id or 1
local res, err = db:query(
"SELECT id, name, email FROM users WHERE id = " .. uid
)
if res and #res > 0 then
ngx.say('{"id":' .. res[1].id .. ',"name":"' ..
res[1].name .. '","email":"' .. res[1].email .. '"}')
else
ngx.say('{"error":"not found"}')
end
db:close()
}
}
}gRPC 代理
Nginx 从 1.13.10 版本开始原生支持 HTTP/2,并可以作为 gRPC 的反向代理。gRPC 基于 HTTP/2 传输,要求代理层必须支持 HTTP/2 端到端。
HTTP/2 支持
Nginx 启用 HTTP/2:
server {
listen 443 ssl http2;
server_name grpc.example.com;
ssl_certificate /etc/nginx/certs/server.crt;
ssl_certificate_key /etc/nginx/certs/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
}grpc_pass 指令
grpc_pass 是 Nginx 用于将请求转发到 gRPC 后端的专用指令:
server {
listen 80 http2;
server_name grpc.example.com;
location /helloworld.Greeter {
grpc_pass grpc://backend:50051;
}
}gRPC 基于 HTTP/2 流式传输,需确保客户端 → Nginx → 后端均为 HTTP/2:
upstream grpc_backend {
server backend1:50051 weight=5;
server backend2:50051 weight=3;
server backend3:50051 backup;
}
server {
listen 443 ssl http2;
server_name grpc.example.com;
ssl_certificate /etc/nginx/certs/server.crt;
ssl_certificate_key /etc/nginx/certs/server.key;
location /helloworld.Greeter {
grpc_pass grpcs://grpc_backend; # grpcs 表示使用 SSL 连接后端
grpc_ssl_certificate /etc/nginx/certs/client.crt;
grpc_ssl_certificate_key /etc/nginx/certs/client.key;
grpc_read_timeout 60s;
grpc_send_timeout 60s;
}
# gRPC 健康检查
location /grpc.health.v1.Health {
grpc_pass grpc://grpc_backend;
}
}gRPC SSL 配置完整示例
# gRPC 服务的全局 SSL 配置
server {
listen 443 ssl http2;
server_name api.grpc.example.com;
# 服务器证书
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
ssl_trusted_certificate /etc/nginx/ssl/ca.crt;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# mTLS:验证客户端证书(可选)
ssl_client_certificate /etc/nginx/ssl/ca.crt;
ssl_verify_client on;
# gRPC 反向代理
location / {
grpc_pass grpcs://grpc_servers;
# 后端 SSL 验证
grpc_ssl_certificate /etc/nginx/ssl/client.crt;
grpc_ssl_certificate_key /etc/nginx/ssl/client.key;
grpc_ssl_trusted_certificate /etc/nginx/ssl/ca.crt;
grpc_ssl_verify on;
grpc_ssl_name grpc.service.internal;
# 超时设置
grpc_connect_timeout 5s;
grpc_read_timeout 30s;
grpc_send_timeout 30s;
# 缓冲区设置
grpc_buffer_size 4k;
grpc_set_header X-Real-IP $remote_addr;
}
}
upstream grpc_servers {
least_conn;
server backend1:50051 weight=5 max_fails=3 fail_timeout=10s;
server backend2:50051 weight=5 max_fails=3 fail_timeout=10s;
}限流
Nginx 提供两个官方模块实现限流:ngx_http_limit_req_module(请求速率限流)和 ngx_http_limit_conn_module(并发连接数限流)。此外,结合 Lua 可实现更灵活的动态限流。
ngx_http_limit_req_module(漏桶算法)
漏桶算法原理:将请求视为水滴,以固定速率从桶底漏出,桶满则溢出拒绝。
请求流入 → ┌─────────────┐ → 以 rate 速率流出
│ 漏桶 (burst) │
└─────────────┘
溢出 (nodelay)http {
# 定义限流区域,10MB 共享内存,速率 10 请求/秒
limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;
server {
listen 80;
location /api/ {
# burst=20:最大突发请求 20 个
# nodelay:请求不延迟等待,直接以 rate 速率处理,超出的立即拒绝
limit_req zone=mylimit burst=20 nodelay;
# 可选:设置拒绝状态码(默认 503)
limit_req_status 429;
proxy_pass http://backend;
}
location /login/ {
# 对登录接口采用更严格的限流
limit_req zone=mylimit burst=5 nodelay;
proxy_pass http://backend;
}
}
}多维度限流:
http {
# 按 IP 限流
limit_req_zone $binary_remote_addr zone=per_ip:10m rate=10r/s;
# 按 URI 限流
limit_req_zone $uri zone=per_uri:10m rate=50r/s;
# 按 IP + URI 组合限流(需自定义变量)
geo $limited {
default 1;
10.0.0.0/8 0; # 内网不限流
}
limit_req_zone $binary_remote_addr$uri zone=combined:10m rate=5r/s;
server {
location /api/ {
limit_req zone=per_ip burst=20 nodelay;
limit_req zone=per_uri burst=200 nodelay;
# 跳过内网限流
if ($limited) {
limit_req zone=combined burst=10 nodelay;
}
proxy_pass http://backend;
}
}
}ngx_http_limit_conn_module
限制单个 IP 的并发连接数:
http {
limit_conn_zone $binary_remote_addr zone=addr:10m;
server {
listen 80;
location /download/ {
limit_conn addr 10; # 单个 IP 最多 10 个并发连接
limit_conn_status 429; # 拒绝状态码
limit_conn_log_level warn; # 日志级别
# 限制每个连接的带宽
limit_rate 500k;
proxy_pass http://backend;
}
location /api/ {
limit_conn addr 50;
proxy_pass http://backend;
}
}
}Lua 动态限流
通过 Lua 实现更精细化的限流策略,例如按用户 ID、按时间段动态调整阈值:
-- dynamic_rate_limit.lua
local limit = ngx.shared.rate_limit
local client_ip = ngx.var.binary_remote_addr
-- 从 Redis 获取用户级别动态配置
local function get_user_rate_limit(uid)
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(500)
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
return 100 -- 默认限流值
end
local rate, err = red:get("rate_limit:" .. uid)
red:close()
if rate then
return tonumber(rate)
end
return 100
end
local uid = ngx.var.arg_uid or "anonymous"
local max_requests = get_user_rate_limit(uid)
local window = 60 -- 60 秒窗口
local key = "rl:" .. uid .. ":" .. os.date("%Y%m%d%H%M")
local current = limit:get(key)
if current and current >= max_requests then
ngx.status = 429
ngx.header["X-RateLimit-Reset"] = 60 - (os.time() % 60)
ngx.say('{"error":"rate limit exceeded"}')
ngx.exit(ngx.HTTP_OK)
elseif current then
limit:incr(key, 1)
else
limit:set(key, 1, window)
end动态上游(upstream)
传统 Nginx upstream 配置变更需重载配置(nginx -s reload)。动态上游方案允许在不中断服务的情况下增删后端节点。
nginx-upsync 模块
nginx-upsync 模块将上游服务器列表与外部注册中心(Consul、Etcd)同步,实现实时服务发现。
http {
upstream backend {
server 127.0.0.1:11111; # 占位 server,必须存在
# 从 Consul 同步 upstream 列表
upsync 127.0.0.1:8500/v1/health/service/my-service upsync_timeout=6m upsync_interval=500ms;
# 动态解析 DNS(配合 Consul DNS)
upsync_dump_path /etc/nginx/conf.d/servers.conf;
}
upstream web {
server 127.0.0.1:11111;
# 从 Etcd 同步
upsync 127.0.0.1:2379/v2/keys/upstreams/web upsync_timeout=6m upsync_interval=500ms;
upsync_type etcd;
upsync_dump_path /etc/nginx/conf.d/web_servers.conf;
}
server {
listen 80;
location /api/ {
proxy_pass http://backend;
}
}
}Consul + Nginx 服务发现
Consul 提供了完整的服务注册与健康检查能力,配合 nginx-upsync 或 consul-template 实现动态发现。
方案一:nginx-upsync 直连 Consul HTTP API(见上)。
方案二:consul-template + 本地文件同步:
# upstream.conf.tmpl(consul-template 模板)
upstream dynamic_backend {
{{ range service "my-service" }}
server {{ .Address }}:{{ .Port }} weight=1 max_fails=3 fail_timeout=10s;
{{ end }}
}# consul-template 守护进程
consul-template \
-template "/etc/nginx/conf.d/upstream.conf.tmpl:/etc/nginx/conf.d/upstream.conf:nginx -s reload" \
-consul-addr "127.0.0.1:8500"方案三:使用 OpenResty Lua 实现动态 upstream:
-- dynamic_upstream.lua
local http = require "resty.http"
local cjson = require "cjson"
-- 从 Consul 获取可用后端列表
local function fetch_services()
local httpc = http.new()
local res, err = httpc:request_uri(
"http://127.0.0.1:8500/v1/health/service/my-service?passing=true",
{ timeout = 2000 }
)
if not res then
ngx.log(ngx.ERR, "consul query failed: ", err)
return nil
end
local services = cjson.decode(res.body)
local backends = {}
for _, svc in ipairs(services) do
local addr = svc.Service.Address
local port = svc.Service.Port
table.insert(backends, addr .. ":" .. port)
end
-- 随机选择一个后端
math.randomseed(os.time())
return backends[math.random(#backends)]
end
local backend = fetch_services()
if backend then
ngx.var.backend = backend -- 用于 proxy_pass
endserver {
listen 80;
location /api/ {
set $backend "";
access_by_lua_file /etc/nginx/lua/dynamic_upstream.lua;
proxy_pass http://$backend;
}
}Nginx 安全
WAF 模块(ModSecurity)
ModSecurity 是一个开源的 Web 应用防火墙(WAF)引擎,作为 Nginx 动态模块运行。
# 编译 ModSecurity 为 Nginx 动态模块
git clone --depth 1 https://github.com/SpiderLabs/ModSecurity.git
cd ModSecurity
./build.sh
./configure
make
make install
# 连接 Nginx
./configure --with-compat --add-dynamic-module=../ModSecurity/nginx/modsecurity
make moduleshttp {
# 加载 ModSecurity 模块
load_module modules/ngx_http_modsecurity_module.so;
# 全局配置
modsecurity on;
modsecurity_rules_file /etc/nginx/modsec/main.conf;
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
}ModSecurity 规则示例(/etc/nginx/modsec/main.conf):
# OWASP CRS 核心规则集
Include /etc/nginx/modsec/crs/crs-setup.conf
Include /etc/nginx/modsec/crs/rules/*.conf
# 自定义规则:SQL 注入防护
SecRule ARGS "@detectSQLi" \
"id:10001,phase:2,deny,status:403,msg:'SQL Injection Detected'"
# 自定义规则:XSS 防护
SecRule ARGS "@detectXSS" \
"id:10002,phase:2,deny,status:403,msg:'XSS Detected'"
# 限制请求体大小
SecRequestBodyLimit 10485760
# 文件上传大小限制
SecRequestBodyInMemoryLimit 1048576Referer / UserAgent 过滤
通过 Nginx 内置变量配合 if 或 map 实现基本过滤:
http {
# 定义爬虫 User-Agent 黑名单
map $http_user_agent $bad_bot {
default 0;
# 恶意爬虫
~*(curl|wget|python-requests|scrapy) 1;
# 特定 UA 屏蔽
~*(Masscan|Nmap|Nikto|SqlMap) 1;
}
# 防盗链:仅允许指定 Referer
map $http_referer $valid_referer {
default 0;
"~https?://(.+\\.)?example\\.com" 1;
"~https?://(.+\\.)?partner\\.com" 1;
"" 0; # 禁止空 Referer
}
server {
listen 80;
server_name example.com;
# 屏蔽恶意爬虫
if ($bad_bot) {
return 444;
}
location /static/ {
# 防盗链
if ($valid_referer = 0) {
return 403;
}
alias /data/static/;
expires 7d;
}
}
}IP 黑名单
方式一:配置文件维护:
http {
# 地理封禁(配合 geo 模块)
geo $blocked_country {
default 0;
CN 0; # 允许中国
US 1; # 阻止美国
RU 1; # 阻止俄罗斯
}
# IP 黑名单
geo $blocked_ip {
ranges;
default 0;
10.0.0.0/8 0; # 内网放行
192.168.0.0/16 0;
1.2.3.4/32 1; # 封禁特定 IP
5.6.7.0/24 1; # 封禁 IP 段
}
server {
if ($blocked_country) {
return 403;
}
if ($blocked_ip) {
return 403;
}
}
}方式二:开放式限流 + 黑名单动态更新(Lua):
-- ip_blacklist.lua
local blacklist = ngx.shared.blacklist
local ip = ngx.var.remote_addr
if blacklist:get(ip) then
ngx.log(ngx.WARN, "blocked IP: ", ip)
ngx.exit(ngx.HTTP_FORBIDDEN)
end
-- 检测恶意行为:连续 3 次 404 则封禁
local fail_count_key = "fail:" .. ip
local fails = blacklist:get(fail_count_key) or 0
if ngx.status == ngx.HTTP_NOT_FOUND then
fails = fails + 1
blacklist:set(fail_count_key, fails, 60) -- 60 秒内计数
if fails >= 3 then
blacklist:set(ip, 1, 3600) -- 封禁 1 小时
ngx.log(ngx.WARN, "IP blocked due to abuse: ", ip)
end
endCORS 配置
跨域资源共享(CORS)配置:
server {
listen 80;
server_name api.example.com;
# 预检请求处理
location / {
# 允许跨域
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "DNT, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Range, Authorization" always;
add_header Access-Control-Expose-Headers "Content-Length, Content-Range" always;
add_header Access-Control-Max-Age 86400 always;
# 处理 OPTIONS 预检请求
if ($request_method = 'OPTIONS') {
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
proxy_pass http://backend;
}
# 仅允许特定域名跨域
location /restricted/ {
# 动态判断来源
if ($http_origin ~* (https?://(.+\.)?example\.com)) {
add_header Access-Control-Allow-Origin $http_origin always;
add_header Access-Control-Allow-Credentials true always;
}
proxy_pass http://backend;
}
}Nginx 监控
ngx_http_stub_status_module
内置状态监控模块,提供基本的活动连接统计:
http {
server {
listen 80;
# 状态监控端点,需限制访问
location /nginx_status {
stub_status; # 启用状态模块
allow 127.0.0.1; # 仅内网访问
allow 10.0.0.0/8;
deny all;
access_log off;
}
}
}访问 /nginx_status 输出示例:
Active connections: 291
server accepts handled requests
16630948 16630948 31070465
Reading: 6 Writing: 179 Waiting: 106字段说明:
| 字段 | 含义 |
|---|---|
| Active connections | 当前活跃连接数 |
| accepts | 累计接受的连接数 |
| handled | 累计成功处理的连接数 |
| requests | 累计处理的请求数 |
| Reading | 正在读取请求头的连接数 |
| Writing | 正在发送响应的连接数 |
| Waiting | 空闲 Keep-Alive 连接数 |
nginx-prometheus-exporter
将 Nginx stub_status 数据转为 Prometheus 指标格式,配合 Grafana 可视化。
# 使用 Docker 运行
docker run -d -p 9113:9113 \
nginx/nginx-prometheus-exporter:latest \
--nginx.scrape-uri=http://localhost:80/nginx_status# prometheus.yml 配置
scrape_configs:
- job_name: 'nginx'
static_configs:
- targets: ['localhost:9113']Grafana 模板 ID:11434(Nginx 官方仪表板)。
OpenResty Xray
OpenResty Xray 是 OpenResty 官方提供的动态追踪监控工具,能够深入到 Lua 代码级别分析性能瓶颈。
# 安装 OpenResty Xray 代理
wget https://agent.openresty.com/install.sh -O - | bash-- 在 Lua 代码中嵌入 Xray 插件
local xray = require "xray"
xray.configure({
service = "my-api",
server_addr = "127.0.0.1:1999",
})
-- 对指定函数进行性能追踪
xray.register("my_handler")
local function my_handler()
local t0 = xray.now()
-- ... 业务逻辑 ...
xray.report("db_query", xray.now() - t0)
end此外,还可以使用第三方的监控方案,如 Prometheus + nginx-lua-prometheus:
http {
lua_shared_dict prometheus_metrics 10m;
init_worker_by_lua_block {
local prometheus = require "prometheus"
local metrics = prometheus.init("prometheus_metrics")
-- 定义自定义指标
metrics:counter("http_requests_total", "Total HTTP Requests", {"method", "status", "host"})
metrics:histogram("http_request_duration_seconds", "HTTP Request Duration", {"method", "host"})
}
server {
location /metrics {
content_by_lua_block {
local prometheus = require "prometheus"
local metrics = prometheus.init("prometheus_metrics")
metrics:collect()
}
}
}
}性能调优
Nginx 性能调优涉及操作系统内核参数、Nginx 核心配置、业务场景专项调整等多个维度。
worker 进程与连接数
worker_processes auto; # 与 CPU 核数一致
worker_cpu_affinity auto; # 绑定 CPU 核心
worker_rlimit_nofile 65535; # worker 进程文件描述符上限
events {
worker_connections 65535; # 单 worker 最大并发连接数
use epoll; # Linux 高性能事件模型
multi_accept on; # 一次 accept 所有新连接
accept_mutex on; # 防止惊群
accept_mutex_delay 100ms; # 互斥锁等待时间
}总并发连接数 = worker_processes × worker_connections。对于反向代理场景,还需考虑上下游各消耗一个连接,实际处理能力需折半。
sendfile / tcp_nopush / tcp_nodelay
http {
sendfile on; # 启用零拷贝,减少用户态/内核态切换
tcp_nopush on; # 在 sendfile 开启时,优化报文段发送
tcp_nodelay on; # 禁用 Nagle 算法,降低延迟
}| 指令 | 作用 | 适用场景 |
|---|---|---|
sendfile on | 数据直接从磁盘→网卡,绕过用户态内存拷贝 | 静态文件服务 |
tcp_nopush on | 累积数据到 TCP 报文段最大长度再发送 | 大文件/静态资源 |
tcp_nodelay on | 不等待即发送小包 | 实时交互/API 服务 |
Keep-Alive 配置
http {
keepalive_requests 1000; # 单连接最大请求数
keepalive_timeout 65s; # 空闲连接超时
upstream backend {
server 127.0.0.1:8080;
# 到后端的 keepalive 长连接
keepalive 32; # 每个 worker 保留的空闲连接数
keepalive_requests 1000;
keepalive_timeout 60s;
}
server {
listen 80;
location / {
proxy_http_version 1.1; # 后端 keepalive 需 HTTP/1.1
proxy_set_header Connection "";
proxy_pass http://backend;
}
}
}Buffer 调整
根据业务类型合理调整缓冲区大小:
http {
# 客户端请求缓冲区
client_body_buffer_size 128k;
client_body_in_file_only off;
client_body_timeout 60s;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
# 代理缓冲区
proxy_buffer_size 4k; # 响应头缓冲区
proxy_buffers 8 4k; # 响应体缓冲区数量及大小
proxy_busy_buffers_size 8k; # 忙缓冲区上限
proxy_temp_file_write_size 4k; # 临时文件写入大小
proxy_max_temp_file_size 0; # 禁止写入临时文件(内存充足时)
# 后端响应超时
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}操作系统内核参数(Linux)
# /etc/sysctl.conf
# 最大文件描述符
fs.file-max = 1000000
# TIME_WAIT 复用
net.ipv4.tcp_tw_reuse = 1
# TCP 连接队列长度
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# 快速回收(内核 4.12+ 已移除 tcp_tw_recycle)
net.ipv4.tcp_fin_timeout = 15
# 本地端口范围
net.ipv4.ip_local_port_range = 1024 65535
# 读写缓冲区默认值
net.core.rmem_default = 65536
net.core.wmem_default = 65536
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216完整性能调优示例
user nginx;
worker_processes auto;
worker_cpu_affinity auto;
worker_rlimit_nofile 65535;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 65535;
use epoll;
multi_accept on;
accept_mutex on;
accept_mutex_delay 100ms;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# 基础优化
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
server_names_hash_bucket_size 128;
# Keep-Alive
keepalive_requests 10000;
keepalive_timeout 30s;
# Timeout
client_body_timeout 30s;
client_header_timeout 30s;
reset_timedout_connection on;
send_timeout 10s;
# 缓冲区优化
client_body_buffer_size 128k;
client_header_buffer_size 1k;
large_client_header_buffers 4 8k;
output_buffers 32 32k;
postpone_output 1460;
# 代理优化
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 10s;
# 静态文件缓存
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 2;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" $request_time $upstream_response_time';
access_log /var/log/nginx/access.log main buffer=32k flush=5s;
include /etc/nginx/conf.d/*.conf;
}调优检查清单
| 项目 | 检查项 | 推荐值 |
|---|---|---|
| worker 数量 | CPU 密集型 | auto 或等于核数 |
| worker 数量 | I/O 密集型 | 核数的 1.5–2 倍 |
| 连接数 | 最大并发 | worker_processes × worker_connections |
| 文件描述符 | 系统上限 | ≥ worker_rlimit_nofile |
| keepalive | 连接复用 | 30–65s |
| sendfile | 零拷贝 | on |
| 缓冲区 | 代理缓冲 | 匹配业务包大小 |
| 日志 | 写入优化 | 启用 buffer 和 flush |
| 压缩 | Gzip 级别 | 2–4(平衡 CPU 与压缩比) |
参考资源