日志分析与性能调优
1. 日志格式
1.1 默认 combined
log_format combined '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
1.2 推荐生产 JSON 格式
log_format json_combined escape=json '{'
'"time":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"x_forwarded_for":"$http_x_forwarded_for",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":$status,'
'"body_bytes_sent":$body_bytes_sent,'
'"request_time":$request_time,'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_addr":"$upstream_addr",'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"request_id":"$request_id"'
'}';
access_log /var/log/nginx/access.log json_combined;
JSON 日志的好处:ELK / Loki 直接索引字段,jq 分析不需要正则。
1.3 关键变量
| 变量 | 含义 |
|---|---|
$request_time | Nginx 处理请求总时长(含上游等待) |
$upstream_response_time | 上游响应时间(不含 Nginx 自身) |
$upstream_connect_time | 到上游的连接建立时间 |
$upstream_addr | 具体连到哪个上游 |
$request_id | 唯一请求 ID(1.11.0+),可传给后端做链路追踪 |
$bytes_sent | 发出总字节(含 header) |
$body_bytes_sent | body 字节 |
$connection | 连接序号 |
$connection_requests | 该连接上第几次请求(看 keepalive 效果) |
$ssl_protocol / $ssl_cipher | TLS 版本和算法 |
1.4 按条件记日志
# 静态资源不记(减少 IO)
map $uri $loggable {
~*\.(js|css|png|jpg|svg|woff2)$ 0;
default 1;
}
access_log /var/log/nginx/access.log json_combined if=$loggable;
# 健康检查不记
map $request_uri $loggable {
/health 0;
default 1;
}
2. 日志实时分析
2.1 命令行快速分析
# Top 10 IP(看是否有异常流量)
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head
# 状态码分布
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# 5xx 的 URL
awk '$9 >= 500 {print $7}' access.log | sort | uniq -c | sort -rn | head
# 请求时间 > 3 秒的慢请求
awk '$NF > 3 {print $7, $NF}' access.log | sort -k2 -rn | head
# JSON 格式用 jq
cat access.log | jq -r 'select(.status >= 500) | .uri' | sort | uniq -c | sort -rn
cat access.log | jq -r 'select(.request_time > 2) | "\(.uri) \(.request_time)"'
# P95 响应时间
cat access.log | jq '.request_time' | sort -n | awk '{a[NR]=$1} END{print a[int(NR*0.95)]}'
2.2 GoAccess 实时仪表盘
apt install goaccess
goaccess /var/log/nginx/access.log -o /var/www/report.html --real-time-html --log-format=COMBINED
浏览器打开即有实时流量面板。
2.3 ELK / Loki
结构化日志 → Filebeat / Promtail 收集 → Elasticsearch / Loki 索引 → Kibana / Grafana 看。
详见模块 08。
3. 性能调优
3.1 worker 配置
worker_processes auto; # = CPU 核数
worker_cpu_affinity auto; # 绑核
worker_rlimit_nofile 65535;
events {
worker_connections 65535;
use epoll;
multi_accept on;
}
3.2 连接与超时
http {
# keepalive
keepalive_timeout 65; # 空闲多久断连
keepalive_requests 1000; # 每连接最多请求数
# 超时
client_header_timeout 15s;
client_body_timeout 15s;
send_timeout 15s;
# 避免慢请求占住 worker
reset_timedout_connection on; # 超时后直接 RST 不等 FIN
}
3.3 Buffer 优化
# 客户端请求
client_header_buffer_size 4k;
large_client_header_buffers 4 32k; # 大 header(如长 cookie)
client_body_buffer_size 128k;
# 代理 buffer
proxy_buffer_size 4k; # 读响应头
proxy_buffers 8 32k; # body buffer
proxy_busy_buffers_size 64k; # 忙时最大发送量
proxy_max_temp_file_size 1024m; # 超出 buffer 写磁盘上限
3.4 文件缓存
open_file_cache max=10000 inactive=60s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
缓存文件描述符、文件大小、修改时间。高流量静态站提升 10-30%。
3.5 日志写 buffer
access_log /var/log/nginx/access.log json_combined buffer=256k flush=5s;
# 256KB 满或 5 秒刷一次,减少磁盘 IO
3.6 proxy_cache(Nginx 缓存层)
把后端响应缓存在 Nginx:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=api_cache:10m max_size=10g inactive=60m use_temp_path=off;
location /api/public/ {
proxy_cache api_cache;
proxy_cache_valid 200 10m;
proxy_cache_valid 404 1m;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_key "$scheme$request_method$host$request_uri";
add_header X-Cache-Status $upstream_cache_status; # HIT / MISS / EXPIRED
proxy_pass http://backend;
}
proxy_cache_use_stale 是关键:后端挂了也能返回旧缓存。
3.7 内核参数
模块 02 TCP 部分已覆盖,这里总结 Nginx 相关:
# /etc/sysctl.conf
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
net.core.netdev_max_backlog = 65535
net.ipv4.ip_local_port_range = 10000 65535
net.ipv4.tcp_tw_reuse = 1
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.tcp_rmem = 4096 87380 16777216
net.ipv4.tcp_wmem = 4096 65536 16777216
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
4. 压测与基准
# wrk(推荐,高效)
wrk -t4 -c100 -d30s https://example.com
# ab(Apache Bench,简单)
ab -n 10000 -c 100 https://example.com/
# k6(有脚本能力)
k6 run script.js
看指标:
- Requests/sec:QPS
- Latency P50/P95/P99:响应时间分布
- Errors:5xx 比例
- Socket errors:连接层问题
5. 慢请求定位
# 从日志找慢请求
cat access.log | jq -r 'select(.request_time > 2) | "\(.request_time)s \(.method) \(.uri) upstream:\(.upstream_response_time)"'
# 如果 upstream_response_time 也大 → 后端慢
# 如果 request_time 大但 upstream_response_time 小 → Nginx buffer / 网络 / 客户端慢
常见原因:
upstream_response_time大:后端慢查询、GC、线程池满$request_time - $upstream_response_time大:客户端上传慢(大文件)、Nginx buffer 落盘- 间歇性慢:keepalive 到上游失效重建连接
6. 故障排查
6.1 worker_connections are not enough
worker_connections are not enough while connecting to upstream
worker_connections 不够(含到客户端 + 到上游 + 日志 fd + 缓存 fd)。调大 + 调大 worker_rlimit_nofile。
6.2 shared memory 不够
could not allocate memory in zone "api_limit"
limit_req_zone 分配的 10m 不够(10m ≈ 16 万个 IP)。高并发场景调大到 50m。
6.3 too many open files
open() "/var/www/dist/app.js" failed (24: Too many open files)
系统 ulimit 或 worker_rlimit_nofile 不够。
6.4 upstream response is buffered to a temporary file
非错误,是提示响应太大写入磁盘临时文件。调大 proxy_buffers 或调大 proxy_max_temp_file_size。
7. 常见反模式
- 不记
$request_time:慢请求排查无从下手 - 日志不 buffer:高 QPS 磁盘 IO 瓶颈
- 不记
$upstream_response_time:分不清 Nginx 慢还是后端慢 access_log off全局关:出问题无据可查- 不分静态/动态日志:静态资源日志量大,淹没有用信息
- buffer 太小频繁落盘:响应大的 API proxy_buffer 要调大
- open_file_cache 配太大:占内存、文件变化感知慢
- proxy_cache 不加
use_stale:源站挂了直接给用户 502 - 压测不用 keepalive:测出来的数据和实际差异大