Nginx 处在所有流量的必经路径上,它的运行状态直接决定整体可用性:连接数是否逼近上限、错误率是否上升、延迟是否劣化、带宽是否打满。但 Nginx 默认是「黑盒」,只有故障发生时才会在 error_log 里留下痕迹。把 Nginx 变成可观测的组件,需要三件事:采集指标(stub_status 与 Prometheus exporter)、采集日志(结构化访问日志)、设计告警与容量规划流程。本文完整讲解这套体系,并给出可落地的配置、PromQL 查询与告警规则。
一句话总结: Nginx 可观测性由指标、日志与追踪三支柱构成,stub_status 提供连接与请求基线,Prometheus 集成让指标进入统一监控面。
1. 可观测性的三大支柱
一句话总结: 指标回答「现在整体状态如何」,日志回答「单个请求发生了什么」,追踪回答「这个请求经过了哪些环节」。
可观测性通常拆成三支柱。指标(Metrics)是聚合后的数值:连接数、请求速率、错误码分布、延迟分位,用时间序列记录,适合告警与容量分析。日志(Logs)是逐个请求的结构化记录:谁在什么时候请求了什么、返回了什么状态、耗时多少,适合检索与审计。追踪(Tracing)还原单个请求的完整路径,Nginx 场景下用 request_id 贯穿「客户端 → Nginx → 下游服务」即可实现轻量级的链路串联。
# 观察 Nginx 进程的实时连接状态(第一手观测)
ss -tnp | grep nginx | awk '{print $1}' | sort | uniq -c
# 输出示例:
# 123 ESTAB
# 5 SYN-SENT
# 3 TIME-WAIT
三支柱各有各的工具链。指标侧用 Prometheus 采集 + Grafana 可视化 + Alertmanager 告警;日志侧用 Filebeat/vector 采集 + Elasticsearch/Loki 存储 + Kibana/Grafana 检索;追踪侧用 $request_id 透传 + 日志关联。三者共享一个原则:结构化先行,指标要有清晰语义,日志要字段化而不是一段字符串,这样才能支撑后续的自动化分析。
2. stub_status 与内置指标
一句话总结: stub_status 以极低成本暴露连接与请求计数,是 Nginx 指标采集的基础,每个实例都应开启并限制访问。
Nginx 内置的 stub_status 模块通过一个特殊 location 暴露运行指标。它只占极小开销,却提供了连接与请求的核心读数,是所有 Nginx 监控体系的起点:
# stub_status 端点:应限定内网或加访问控制
server {
listen 80;
server_name nginx-status.internal;
location /stub_status {
stub_status on;
access_log off;
allow 10.0.0.0/8; # 仅内网监控网段
deny all;
}
}
访问端点会得到一段文本指标,每一行都有明确含义:
curl -s http://127.0.0.1/stub_status
# Active connections: 23
# server accepts handled requests
# 123456 123456 456789
# Reading: 0 Writing: 1 Waiting: 22
| 指标 | 含义 | 观察要点 |
|---|---|---|
| Active connections | 当前活跃连接数 | 与 worker_connections × worker_processes 对比 |
| accepts | 累计接受连接数 | 单调递增,看增量速率 |
| handled | 累计处理连接数 | 与 accepts 差距大说明握手失败 |
| requests | 累计请求数 | 除以时间得到请求速率 |
| Reading | 正在读取请求头的连接 | 高说明小请求洪峰 |
| Writing | 正在写响应的连接 | 高说明大响应或慢客户端 |
| Waiting | 空闲 keepalive 连接 | 高说明长连接池充足 |
Reading/Writing/Waiting 三个读数对排错很有价值:Writing 长期偏高通常意味着有慢客户端在拖住连接,或下游响应过慢;Waiting 接近 Active 说明连接大部分处于 keepalive 空闲,是健康的长连接池形态。
3. Prometheus exporter 集成
一句话总结: nginx-prometheus-exporter 把 stub_status 与 nginx-plus 的 JSON 指标转成 Prometheus 格式,让 Nginx 指标进入统一的时序监控面。
stub_status 的文本格式不是 Prometheus 原生格式。nginx-prometheus-exporter 是官方的桥接组件:它周期性抓取 stub_status 文本,转成 Prometheus 指标并暴露在 /metrics 端点。部署方式可以是一台机器一个 exporter,也可以采用 per-process 抓取脚本方案。
# 运行 nginx-prometheus-exporter(官方镜像)
docker run -d --name nginx-exporter \
-e SCRAPE_URI=http://127.0.0.1:80/stub_status \
-p 9113:9113 \
nginx/nginx-prometheus-exporter:latest
exporter 暴露的核心指标以 nginx_connections_* 与 nginx_http_requests_total 为主:
# HELP nginx_connections_accepted Accepted client connections
# TYPE nginx_connections_accepted counter
nginx_connections_accepted 123456
# HELP nginx_connections_active Active client connections
# TYPE nginx_connections_active gauge
nginx_connections_active 23
# HELP nginx_http_requests_total Total http requests
# TYPE nginx_http_requests_total counter
nginx_http_requests_total 456789
Prometheus 的抓取配置把 exporter 加入 target 列表:
# prometheus.yml
scrape_configs:
- job_name: nginx
static_configs:
- targets: ["10.0.0.11:9113", "10.0.0.12:9113", "10.0.0.13:9113"]
抓取间隔要权衡精度与开销,15s 是常用默认值。生产环境的指标面不止 Nginx,还有上游服务、云 LB 与主机资源(CPU/内存/IO),Nginx 指标要和其他指标放在同一张 Grafana dashboard,才能做「错误率上升到底来自 Nginx 还是下游」的联合判断。
4. 日志采集与可视化
一句话总结: 结构化访问日志把每个请求的响应码、耗时与字节数字段化,经采集管道汇入检索后端,支撑仪表盘与错误分析。
指标回答「整体如何」,日志回答「具体哪个请求」。要把日志变成可检索、可聚合的数据,第一步是结构化:在 log_format 里把字段用分隔符或 JSON 组织,方便采集器解析。
# JSON 格式访问日志,直接对接日志管道
log_format json_combined escape=json
'{'
'"time_local":"$time_local",'
'"remote_addr":"$remote_addr",'
'"request":"$request",'
'"status":$status,'
'"request_time":$request_time,'
'"body_bytes_sent":$body_bytes_sent,'
'"http_referer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"upstream_addr":"$upstream_addr",'
'"request_id":"$request_id"'
'}';
access_log /var/log/nginx/access.json json_combined;
采集链路常用的组合是 Filebeat/vector 采集 → Kafka/Loki/Elasticsearch 存储 → Kibana/Grafana 展示。可视化层面有三张核心仪表盘值得优先建立:请求量与吞吐趋势、状态码分布、延迟分位(p50/p95/p99)。延迟分位需要日志侧聚合,Prometheus 侧可以通过 histogram 记录 $request_time 的桶分布实现等价效果:
# 用 Prometheus 直方图暴露请求延迟(借助 lua 或 prometheus 模块)
# OpenResty: prometheus.lua 记录 request_time 直方图
location / {
log_by_lua_block {
prometheus:histogram_observe("nginx_http_request_duration_seconds",
ngx.var.request_time)
}
proxy_pass http://backend;
}
错误分析是日志可视化最有价值的场景:把 status >= 500 的请求过滤出来,按 upstream_addr 与 request_uri 分组,立刻能定位到「哪个上游、哪个接口在持续报错」。字段化的 upstream_addr 与 request_id 让这类查询从字符串 grep 升级为精确的聚合分析。
5. 告警规则设计
一句话总结: Nginx 告警围绕连接饱和、错误率、延迟劣化与容量水位四条主线设计,配合去重、分组与静默避免告警风暴。
指标与日志建立之后,告警把「状态异常」转化为「有人处理」。Nginx 相关的告警规则有四条主线。第一条是连接饱和:Active connections 接近 worker_processes × worker_connections 时,新连接会被拒或排队,这是容量告警的最后防线:
# Prometheus 告警规则示例
groups:
- name: nginx.rules
rules:
- alert: NginxConnectionsHigh
expr: nginx_connections_active > 0.85 * on(instance) group_left nginx_connections_accepted
for: 5m
labels:
severity: warning
annotations:
summary: "Nginx 活跃连接超过阈值 85%"
- alert: Nginx5xxRateHigh
expr: rate(nginx_http_responses_total{status=~"5.."}[5m]) > 0.05 * rate(nginx_http_requests_total[5m])
for: 5m
labels:
severity: critical
annotations:
summary: "Nginx 5xx 错误率超过 5%"
- alert: NginxLatencyHigh
expr: histogram_quantile(0.95, sum(rate(nginx_http_request_duration_seconds_bucket[10m])) by (le)) > 0.5
for: 10m
labels:
severity: warning
annotations:
summary: "Nginx p95 延迟超过 500ms,检查下游与连接池"
第二条是错误率:5xx 比例异常上升,优先关联下游服务状态。第三条是延迟劣化:p95 延迟超过基线,用历史基线而非固定值,避免误报。第四条是容量水位:磁盘写满(访问日志)、连接数趋势、带宽接近上限。
告警设计的核心是「让告警可执行」。每条告警都应包含排查指引:实例地址、持续时长、相关 dashboard 链接。同时配置合理的 for 时长与分组规则——连接数瞬时冲高可能只是促销洪峰,持续 5 分钟才真正需要处理。告警风暴往往源于规则过敏感或缺少静默,Nginx 发布窗口内的告警应当由发布流程统一静默,避免噪音掩盖真问题。
6. 容量规划与压测
一句话总结: 容量规划用「峰值流量 × 冗余系数」估算节点数,压测先定位 Nginx 自身的吞吐与延迟拐点,再叠加真实流量特征修正。
监控的意义不止于告警,还在于回答「要不要扩容」。容量规划的第一步是建立基线:记录正常业务日的请求速率、活跃连接数、带宽与延迟分位。第二步是估算峰值:促销、秒杀等活动日的流量倍数,乘以冗余系数(通常 1.5~2 倍)得到设计容量。第三步换算成资源:连接数除以单节点可承载连接数,得到节点数。
节点数的估算可以用两个公式套算。请求类容量:节点数 = ceil(峰值请求速率 × 单请求成本 / 单节点承载能力),其中单节点承载能力由压测得到,单请求成本从访问日志聚合的平均 request_time 推算。连接类容量:节点数 = ceil(峰值并发连接数 × 冗余系数 / (worker_processes × worker_connections))。两条公式算出的节点数取较大者,并至少留出一台冗余,保证单节点故障时整体不降级。
# 从访问日志聚合每日请求速率基线(JSON 日志按秒聚合)
awk -F'"time_local":"' '{split($2,a," +"); ts=a[2]":"a[3]}' /var/log/nginx/access.json \
| sort | uniq -c | sort -rn | head -5
# 取业务高峰小时的总请求数除以 3600,得到峰值速率基线
压测是容量规划的数据来源。压测要分层进行:先用 ab 或 wrk 对纯 Nginx 静态端点打流量,定位 Nginx 自身的吞吐与延迟拐点;再打真实业务路径(含反代与下游),观察瓶颈是在 Nginx 还是下游。
# 用 wrk 压测 Nginx 静态资源端点
wrk -t 4 -c 400 -d 60s http://10.0.0.11/static/app.js
# 观察拐点:提高并发,记录 rps 与延迟的变化
for c in 100 200 400 800 1600; do
wrk -t 4 -c $c -d 30s http://10.0.0.11/static/app.js 2>/dev/null \
| grep 'Requests/sec'
done
压测输出要同时看 rps、延迟均值与延迟分位。rps 上升但 p99 延迟急剧恶化的点,就是容量拐点,节点数应低于该点运行。容量规划不是一次性工作:指标水位(连接数、带宽、p95 延迟)要设成月度回顾的输入,结合流量增长曲线提前安排扩容,而不是等告警响了才动手。
7. 全链路追踪与排错
一句话总结: 用
$request_id贯穿客户端、Nginx 与下游服务,故障时按 ID 检索日志,一次定位完整调用链。
当问题表现为「某个用户请求很慢」或「某个请求 502」时,指标只能告诉你「有错误」,日志能告诉你「哪个请求」,追踪能告诉你「这个请求经历了什么」。Nginx 用 $request_id 就能实现轻量级追踪:每个请求生成唯一 ID,写入访问日志并透传给下游,下游服务在自己的日志里记录同一个 ID。
# 生成并透传 request_id
map $http_x_request_id $request_id {
default $request_id_auto;
"~^.{8,64}$" $http_x_request_id; # 客户端已有 ID 则沿用,否则自动生成
}
server {
location / {
proxy_set_header X-Request-ID $request_id;
proxy_pass http://backend;
}
}
Nginx 内置的 $request_id 变量在 http2 模式下默认存在,取值是随机 32 位十六进制。配合日志检索,排查一个失败请求的路径是:在 Grafana 或 Kibana 里用请求 ID 过滤,同时查到 Nginx 访问日志、error_log 与下游服务的日志记录,按时间线还原「Nginx 何时转发、上游何时响应、错误发生在哪一环」。
# 用 request_id 检索完整链路
grep '5f3a2b7c8d9e0f1a' /var/log/nginx/access.json
grep '5f3a2b7c8d9e0f1a' /var/log/nginx/error.log
grep '5f3a2b7c8d9e0f1a' /var/log/app/order-service.log
如果下游是 OpenTelemetry 体系,可以在 Nginx 侧用 lua 把 $request_id 映射为 trace ID 并注入分布式追踪上下文,实现与原生链路追踪的无缝衔接:
-- OpenResty: 把 request_id 注入下游追踪头(示意)
location / {
set_by_lua_block $trace_id { return ngx.var.request_id }
proxy_set_header X-Trace-Id $trace_id;
proxy_set_header X-Trace-Parent "00-$trace_id-0000000000000001-01";
proxy_pass http://backend;
}
排错时把握「从入口到出口」的顺序:先确认请求是否到达 Nginx(access 日志)、再确认是否到达上游(upstream_addr)、最后确认上游返回了什么(status 与 response_time),每一环都有日志佐证。追踪头一旦约定,下游服务只需在自己的日志里记录同一 X-Trace-Id,就能与 Nginx 侧日志无缝关联。
8. 总结
| 环节 | 要点 |
|---|---|
| 三支柱 | 指标看整体、日志看单请求、追踪串链路 |
| stub_status | 连接与请求计数,Reading/Writing/Waiting 排错 |
| Prometheus | exporter 转换指标,15s 抓取进入统一监控面 |
| 日志可视化 | JSON 结构化日志,延迟分位与错误聚合仪表盘 |
| 告警设计 | 连接、错误率、延迟、容量四条主线,可执行可静默 |
| 容量规划 | 峰值 × 冗余系数定节点数,压测找容量拐点 |
| 全链路追踪 | request_id 贯穿链路,按 ID 聚合多端日志 |
| 排错顺序 | 入口 → 上游 → 响应,每环都要有日志佐证 |
Nginx 可观测性建设的本质,是把「故障后的排查」前置为「运行中的可见」。stub_status 提供基线,Prometheus 与日志管道把指标和请求汇入统一监控面,告警规则把异常转化为可执行的任务,request_id 让单请求的完整路径变得可追溯。这套体系建立起来之后,Nginx 就不再是黑盒,而是整个接入层可量化、可预测的组件。下一步看看云原生环境下 Nginx 的另一种形态——Kubernetes Ingress 网关。
延伸阅读
- Nginx 日志分析与性能调优 — log_format 与 JSON 日志基础
- Nginx 核心配置结构 — 配置上下文与变量体系
- Nginx 限流与安全防护 — 限流指标与安全观测
- Nginx 缓存与压缩优化 — 命中率观测与压缩指标
- Nginx 反向代理与负载均衡 — upstream 状态与健康检查观测
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。