跳到主要内容

日志分析与性能调优

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_timeNginx 处理请求总时长(含上游等待)
$upstream_response_time上游响应时间(不含 Nginx 自身)
$upstream_connect_time到上游的连接建立时间
$upstream_addr具体连到哪个上游
$request_id唯一请求 ID(1.11.0+),可传给后端做链路追踪
$bytes_sent发出总字节(含 header)
$body_bytes_sentbody 字节
$connection连接序号
$connection_requests该连接上第几次请求(看 keepalive 效果)
$ssl_protocol / $ssl_cipherTLS 版本和算法

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:测出来的数据和实际差异大

8. 延伸阅读