日志是 Nginx 运维的"眼睛":缓存命中率、接口耗时、状态码分布、爬虫与攻击特征,全藏在访问日志里;而 error_log 里的告警则是排错的第一线索。性能调优则要求把 worker 数量、事件模型、keepalive 与缓冲参数调到与业务流量匹配。本文先讲 log_format 与 JSON 结构化日志,再讲日志分析,最后落到 worker/keepalive/缓冲的参数调优与压测方法,形成"观测 → 定位 → 调优"的完整闭环。
核心认知:先有可解析的日志,才有可靠的性能判断。调优必须基于日志指标而非直觉——每改一个参数,都要有日志数据来验证收益。
1. log_format 与日志规范
1.1 自定义日志格式
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 '
'urt=$upstream_response_time';
| 变量 | 含义 |
|---|---|
$request_time | 网关处理请求总耗时 |
$upstream_connect_time | 与上游建立连接耗时 |
$upstream_response_time | 上游响应耗时 |
$upstream_cache_status | 缓存命中状态 |
$status | 响应状态码 |
1.2 按站点分文件
http {
log_format main '...';
server {
access_log /var/log/nginx/example.com.access.log main;
}
}
避坑:
$upstream_response_time是逗号分隔的多值(多次重试会有多个),统计时需要按最后一个值处理,避免误判上游耗时。
2. JSON 结构化日志
2.1 定义 JSON 格式
log_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"cache_status":"$upstream_cache_status"'
'}';
access_log /var/log/nginx/access.json json_combined;
2.2 采集到日志平台
# Filebeat 采集 JSON 日志 → Elasticsearch/Loki
filebeat.inputs:
- type: log
paths: [/var/log/nginx/access.json]
json.keys_under_root: true
| 平台 | 用途 |
|---|---|
| ELK | 全文检索与可视化 |
| Loki | 轻量日志聚合 |
| ClickHouse | 大规模日志分析 |
| 自定义管道 | 消费 JSON 做指标统计 |
2.3 JSON 日志查询示例
-- 以 ClickHouse 为例:查询慢接口与命中率
SELECT request,
quantile(0.95)(request_time) AS p95,
countIf(status >= 500) AS err_cnt,
countIf(cache_status = 'HIT') AS hits,
count() AS total
FROM nginx_access
WHERE time_local >= now() - INTERVAL 1 HOUR
GROUP BY request
ORDER BY p95 DESC
LIMIT 10;
核心认知:结构化日志让"日志"变成"数据"。带上 request_time、status、cache_status 等字段后,可以直接用 SQL/查询语言做耗时与命中率分析,而非靠正则硬啃文本。
3. 访问日志分析
3.1 常用统计命令
# 状态码分布
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 请求耗时 Top10
awk '{print $NF, $7}' /var/log/nginx/access.log \
| sort -rn | head -10
# 5xx 高频接口
awk '$9>=500 {print $7}' /var/log/nginx/access.log \
| sort | uniq -c | sort -rn | head
3.2 goaccess 可视化
goaccess /var/log/nginx/access.log --log-format=COMBINED -o report.html
| 指标 | 用途 |
|---|---|
| 状态码分布 | 快速发现 5xx 异常 |
| 请求耗时 | 定位慢接口 |
| 来源 IP 分布 | 发现异常流量 |
| 命中率 | 验证缓存策略 |
避坑:分析时务必按时间窗口聚合,避免把"活动高峰"误判为"异常暴涨"。建议对 5xx 与耗时设置基线,超过基线再告警。
4. error_log 与排错
4.1 日志级别
error_log /var/log/nginx/error.log warn; # 生产常用 warn
# 排错时临时降到 debug,会有大量输出
error_log /var/log/nginx/error.log debug;
| 级别 | 场景 |
|---|---|
| error | 仅错误 |
| warn | 生产推荐(含 warn 及以上) |
| notice | 含配置通知 |
| info/debug | 仅排错,勿长期开启 |
4.2 典型错误解读
| error_log 关键字 | 含义 | 处理 |
|---|---|---|
upstream timed out | 上游超时 | 调大 proxy_read_timeout |
no resolver defined | 缺 resolver | 为变量 proxy_pass 配 resolver |
worker_connections are not enough | 连接不足 | 提高 worker_connections |
recv() failed | 客户端异常断开 | 检查超时与限流 |
核心认知:error_log 出现
worker_connections are not enough是连接容量告急的信号,比 5xx 更早暴露容量瓶颈。
5. worker 与事件模型调优
5.1 worker 数量
worker_processes auto; # 通常等于 CPU 核数
worker_cpu_affinity auto; # 绑定 CPU,减少迁移
worker_rlimit_nofile 65535; # 文件描述符上限
| 场景 | worker_processes |
|---|---|
| CPU 密集 | 与核数相同 |
| IO 密集/大量连接 | 核数或核数 × 1.5 |
| 容器/低配 | 1~2 即可 |
5.2 事件与连接参数
events {
worker_connections 4096;
use epoll;
multi_accept on;
accept_mutex on;
}
避坑:
worker_rlimit_nofile必须与系统ulimit -n配套。连接上限 = worker_processes × worker_connections,同时受文件描述符约束,三者需一起核算。
6. keepalive 与缓冲调优
6.1 keepalive 参数
server {
keepalive_timeout 30; # 客户端空闲连接保持
keepalive_requests 1000; # 单连接最多处理请求数
keepalive_timeout 30s; # 新版区分长短连接
}
upstream backend {
server 10.0.0.11:8080;
keepalive 32; # 上游空闲连接池
}
6.2 缓冲与文件处理
sendfile on; # 内核态直接发送文件
tcp_nopush on; # 攒满包再发,配合 sendfile
tcp_nodelay on; # 小包即时发送,配合 keepalive
proxy_buffering on; # 上游响应缓冲
client_body_buffer_size 128k;
| 参数 | 作用 | 场景 |
|---|---|---|
sendfile | 减少拷贝 | 静态文件 |
tcp_nopush | 减少小包 | 大响应 |
tcp_nodelay | 降低延迟 | 小响应/交互 |
proxy_buffering | 缓冲上游响应 | 常规代理;SSE 需 off |
避坑:SSE/流式接口必须
proxy_buffering off,否则会被缓冲导致客户端收不到实时推送;静态大文件则用 sendfile + tcp_nopush 提升吞吐。
7. 压测与瓶颈定位
7.1 压测工具
# ab 压测
ab -n 100000 -c 100 http://example.com/api/user
# wrk 多线程压测
wrk -t 8 -c 400 -d 30s --latency http://example.com/
# 观察系统指标
vmstat 1
7.2 指标解读
| 指标 | 健康值 | 异常信号 |
|---|---|---|
| 请求耗时 p95 | < 100ms | 缓慢上升 |
| 错误率 | < 1% | 5xx 突增 |
| worker 连接数 | < 80% 上限 | 接近上限告警 |
| CPU | 多核均衡 | 单核打满 |
| 带宽 | 有富余 | 逼近峰值 |
7.3 调优流程
观察日志指标 → 定位瓶颈(CPU/连接/带宽/后端)
→ 针对性改参数 → 复测对比 → 记录基线
| 瓶颈 | 对应调优 |
|---|---|
| 连接数打满 | 提高 worker_connections / 开 keepalive |
| 单核打满 | 检查 worker_cpu_affinity 与负载均衡 |
| 上游慢 | 缓存 + 限流 + 调大 read 超时 |
| 带宽高 | gzip/Brotli 压缩 |
核心认知:压测必须分阶段——先压静态、再压动态、再压代理链路,逐层定位瓶颈落在 Nginx、网络还是后端。任何参数修改都要保留前后基线做对比。
8. 总结
| 环节 | 要点 |
|---|---|
| 日志格式 | log_format 记录 request_time/upstream/cache 字段 |
| 结构化 | JSON 日志直接进日志平台分析 |
| 分析 | awk 统计状态码/耗时,goaccess 可视化 |
| error_log | warn 级别,按关键字排错 |
| worker | 与核数匹配,rlimit 配套 |
| keepalive | 客户端与上游双端配置 |
| 缓冲 | sendfile/tcp_nopush/proxy_buffering 按场景 |
| 压测 | 分阶段压测,保留基线对比 |
日志让 Nginx 的行为透明,调优让它的性能与流量匹配。把日志指标接入告警、把压测基线沉淀为文档,网关层就能持续演进。本专题六篇文章至此形成一个完整的 Nginx 网关与 Web 服务知识体系,从配置结构到代理、加密、缓存、安全再到观测,环环相扣。
延伸阅读
- Nginx 核心配置结构 — 指令上下文与配置管理
- Nginx 反向代理与负载均衡 — 上游耗时字段解读
- Nginx HTTPS 与 TLS 加固 — TLS 握手性能观测
- Nginx 缓存与压缩优化 — 命中率与压缩观测
- Nginx 限流与安全防护 — 限流日志与封禁分析
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。