Nginx 接口耗时 Prometheus + Grafana 监控实施方案
汇报人:周晓玥
一、方案概述
1.1 监控目标
将 Nginx 代理的批量任务接口耗时数据($request_time、$upstream_response_time、HTTP 状态码)接入 Prometheus + Grafana 监控体系,实现:
- 各中心接口的请求耗时(P50/P95/P99)实时监控
- 后端 upstream 响应耗时与 Nginx 代理延迟的对比
- 异常接口自动告警(耗时突增、成功率下降)
- 历史趋势分析(按小时/天/周维度聚合)
1.2 核心数据流
#mermaid-svg-zpix4lJW9g3rx1yU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-zpix4lJW9g3rx1yU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-zpix4lJW9g3rx1yU .error-icon{fill:#552222;}#mermaid-svg-zpix4lJW9g3rx1yU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-zpix4lJW9g3rx1yU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-zpix4lJW9g3rx1yU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-zpix4lJW9g3rx1yU .marker.cross{stroke:#333333;}#mermaid-svg-zpix4lJW9g3rx1yU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-zpix4lJW9g3rx1yU p{margin:0;}#mermaid-svg-zpix4lJW9g3rx1yU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster-label text{fill:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster-label span{color:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster-label span p{background-color:transparent;}#mermaid-svg-zpix4lJW9g3rx1yU .label text,#mermaid-svg-zpix4lJW9g3rx1yU span{fill:#333;color:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .node rect,#mermaid-svg-zpix4lJW9g3rx1yU .node circle,#mermaid-svg-zpix4lJW9g3rx1yU .node ellipse,#mermaid-svg-zpix4lJW9g3rx1yU .node polygon,#mermaid-svg-zpix4lJW9g3rx1yU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-zpix4lJW9g3rx1yU .rough-node .label text,#mermaid-svg-zpix4lJW9g3rx1yU .node .label text,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape .label,#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape .label{text-anchor:middle;}#mermaid-svg-zpix4lJW9g3rx1yU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-zpix4lJW9g3rx1yU .rough-node .label,#mermaid-svg-zpix4lJW9g3rx1yU .node .label,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape .label,#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape .label{text-align:center;}#mermaid-svg-zpix4lJW9g3rx1yU .node.clickable{cursor:pointer;}#mermaid-svg-zpix4lJW9g3rx1yU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-zpix4lJW9g3rx1yU .arrowheadPath{fill:#333333;}#mermaid-svg-zpix4lJW9g3rx1yU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-zpix4lJW9g3rx1yU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-zpix4lJW9g3rx1yU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zpix4lJW9g3rx1yU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-zpix4lJW9g3rx1yU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zpix4lJW9g3rx1yU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-zpix4lJW9g3rx1yU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster text{fill:#333;}#mermaid-svg-zpix4lJW9g3rx1yU .cluster span{color:#333;}#mermaid-svg-zpix4lJW9g3rx1yU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-zpix4lJW9g3rx1yU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-zpix4lJW9g3rx1yU rect.text{fill:none;stroke-width:0;}#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape p,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-zpix4lJW9g3rx1yU .icon-shape .label rect,#mermaid-svg-zpix4lJW9g3rx1yU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-zpix4lJW9g3rx1yU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-zpix4lJW9g3rx1yU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-zpix4lJW9g3rx1yU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 告警通知
Grafana 节点
Prometheus 节点
Nginx 节点 (2台)
每15s scrape
每15s scrape
规则评估
数据源
仪表盘
通知
通知
Nginx Master
9530端口
nginx-module-vts
指标暴露 :8080/metrics
Nginx Slave
9530端口
nginx-module-vts
指标暴露 :8080/metrics
Prometheus Server
指标采集 + 存储
Alertmanager
告警管理
Grafana Server
可视化 + 告警
企业微信
邮件
1.3 指标采集链路
Nginx请求 → nginx-module-vts 统计 → /metrics端点(JSON)
→ Prometheus scrape → TSDB存储
→ Grafana查询 → 仪表盘 + 告警
二、服务器规划
2.1 节点清单
| 角色 | 主机名 | IP地址 | 配置 | 操作系统 |
|---|---|---|---|---|
| Nginx Master | ngx-batch-01 | 10.1.92.61 | 4C8G / 100G SSD | CentOS 7.9 |
| Nginx Slave | ngx-batch-02 | 10.1.92.62 | 4C8G / 100G SSD | CentOS 7.9 |
| Prometheus | mon-prom-01 | 10.1.92.100 | 4C16G / 200G SSD | CentOS 7.9 |
| Grafana | mon-grafana-01 | 10.1.92.101 | 4C8G / 100G SSD | CentOS 7.9 |
注:若资源有限,Prometheus 和 Grafana 可合并部署在同一节点。
2.2 端口规划
| 服务 | 端口 | 说明 | 防火墙策略 |
|---|---|---|---|
| Nginx 业务端口 | 9530 | 批量任务流量入口 | 允许 Control-M Agent 网段 |
| nginx-module-vts metrics | 8080 | Prometheus 指标暴露 | 允许 Prometheus 节点 IP |
| Prometheus Web UI | 9090 | Prometheus 管理界面 | 允许运维网段 |
| Grafana Web UI | 3000 | Grafana 管理界面 | 允许运维网段 |
| Alertmanager | 9093 | 告警管理 | 允许运维网段 |
2.3 存储规划
| 项目 | 配置 | 说明 |
|---|---|---|
| Prometheus TSDB 存储路径 | /data/prometheus |
建议至少 200G,SSD |
| Prometheus 数据保留 | 30d | 生产环境建议 30-90 天 |
| Grafana 数据路径 | /var/lib/grafana |
不建议存放大文件 |
| Nginx 日志路径 | /var/log/nginx |
建议配置 logrotate 定期切割 |
三、完整实施步骤
阶段一:Nginx 节点改造(ngx-batch-01 / ngx-batch-02)
Step 1:安装 nginx-module-vts 模块
知识点 :
nginx-module-vts(Virtual Host Traffic Status)是一个第三方 Nginx 模块,可实时统计 Nginx 的请求量、耗时、状态码等指标,并通过 HTTP 接口暴露 Prometheus 格式的 metrics。
方案 A:重新编译 Nginx(推荐)
bash
# 1. 下载 nginx-module-vts 模块
cd /usr/local/src
git clone https://github.com/vozlt/nginx-module-vts.git
# 2. 查看当前 Nginx 版本和编译参数
nginx -V
# 记录完整编译参数,例如:
# --prefix=/usr/local/nginx --add-module=../nginx-upsync-module ...
# 3. 重新编译 Nginx(追加 vts 模块)
cd /usr/local/src/nginx-1.24.0
./configure \
--prefix=/usr/local/nginx \
--add-module=../nginx-upsync-module \
--add-module=../nginx-module-vts \ # 新增
--with-http_realip_module \
--with-http_ssl_module \
--with-http_v2_module \
--with-stream
make
# ⚠️ 注意:只执行 make,不要 make install(避免覆盖现有配置)
# 4. 备份原 Nginx 二进制
cp /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.bak.$(date +%Y%m%d)
# 5. 替换二进制(平滑升级)
cp objs/nginx /usr/local/nginx/sbin/nginx
# 6. 平滑升级(不中断服务)
kill -USR2 $(cat /usr/local/nginx/logs/nginx.pid)
sleep 3
kill -WINCH $(cat /usr/local/nginx/logs/nginx.pid.oldbin)
kill -QUIT $(cat /usr/local/nginx/logs/nginx.pid.oldbin)
sleep 3
# 7. 验证模块是否加载成功
nginx -V 2>&1 | grep nginx-module-vts
# 预期输出: --add-module=../nginx-module-vts
方案 B:动态模块加载(Nginx 1.11.5+)
bash
# 编译为动态模块
./configure --add-dynamic-module=../nginx-module-vts
make modules
# 复制到模块目录
cp objs/ngx_http_vhost_traffic_status_module.so /usr/local/nginx/modules/
# 在 nginx.conf 顶部加载
# load_module modules/ngx_http_vhost_traffic_status_module.so;
Step 2:配置 nginx.conf
在现有 Nginx 配置中追加 vts 监控配置:
nginx
# =============================================
# nginx.conf 主配置文件
# =============================================
# 若使用动态模块
# load_module modules/ngx_http_vhost_traffic_status_module.so;
http {
# =============================================
# VTS 模块全局配置
# =============================================
# 开启 vts 统计功能
vhost_traffic_status_zone;
# 按过滤维度分组统计(关键:按接口路径分组)
vhost_traffic_status_filter_by_host on;
vhost_traffic_status_filter_by_set_key $location_name location::$1;
# =============================================
# 原有 upstream 和 location 配置保持不变
# =============================================
upstream batch_center {
upsync 10.1.230.33:8848/nacos/v1/ns/instance/list?serviceName=corpor-batch ...;
# ... 原有配置 ...
}
# 为每个 location 指定过滤标签
server {
listen 9530;
# 设置 location 标签用于 vts 分组
set $location_name "default";
location /corpor-batch/ {
set $location_name "corpor-batch";
vhost_traffic_status_filter_by_set_key $location_name location::$1;
proxy_pass http://batch_center;
proxy_read_timeout 1800s;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
location /corpor-trans/ {
set $location_name "corpor-trans";
vhost_traffic_status_filter_by_set_key $location_name location::$1;
proxy_pass http://trans_center;
proxy_read_timeout 1800s;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
# ... 其他 location 类似 ...
# =============================================
# VTS 监控接口(仅限内网访问)
# =============================================
location /vts_status {
vhost_traffic_status_display;
vhost_traffic_status_display_format html;
allow 10.1.0.0/16;
allow 127.0.0.1;
deny all;
}
# Prometheus metrics 格式接口
location /metrics {
vhost_traffic_status_display;
vhost_traffic_status_display_format prometheus;
allow 10.1.92.100; # Prometheus 节点 IP
allow 10.1.92.101; # Grafana 节点 IP
allow 127.0.0.1;
deny all;
}
# 为 Prometheus 提供一个独立端口(可选,简化网络策略)
# 在另一个 server 块中监听独立端口
}
}
# =============================================
# 独立 metrics 端口(推荐:业务与监控端口分离)
# =============================================
server {
listen 8080;
server_name localhost;
# 监控端口只暴露 metrics
location /metrics {
vhost_traffic_status_display;
vhost_traffic_status_display_format prometheus;
allow 10.1.92.100;
allow 10.1.92.101;
allow 127.0.0.1;
deny all;
}
location /vts_status {
vhost_traffic_status_display;
vhost_traffic_status_display_format html;
allow 10.1.0.0/16;
allow 127.0.0.1;
deny all;
}
}
Step 3:验证 Nginx 指标暴露
bash
# 重启 Nginx(或 reload)
nginx -s reload
# 验证模块统计启用
curl http://127.0.0.1:8080/vts_status
# 应返回 HTML 格式的流量统计页面
# 验证 Prometheus metrics 格式
curl http://127.0.0.1:8080/metrics
# 应返回类似以下内容:
Prometheus metrics 关键指标说明:
# 请求总数(按状态码和 location 分组)
nginx_vts_server_requests_total{host="*",filter="location::corpor-batch"} 15234
# 请求速率
nginx_vts_server_request_seconds_total{...} 1256.789
# upstream 连接数
nginx_vts_upstream_request_seconds_total{...}
# 各状态码请求数(2xx / 3xx / 4xx / 5xx)
nginx_vts_server_requests_total{host="*",code="2xx",filter="location::corpor-batch"} 15100
nginx_vts_server_requests_total{host="*",code="5xx",filter="location::corpor-batch"} 34
# 平均响应时间(需计算)
# rate(nginx_vts_server_request_seconds_total[1m]) / rate(nginx_vts_server_requests_total[1m])
注意点 :nginx-module-vts 暴露的是请求总耗时 和请求总数 ,需要 PromQL 做除法计算平均耗时 。无法直接暴露 P95/P99 分位值,分位值需要在 Prometheus 中通过
histogram_quantile计算。
阶段二:Prometheus 部署(mon-prom-01)
Step 1:安装 Prometheus
bash
# 1. 创建用户
useradd --no-create-home --shell /bin/false prometheus
# 2. 下载 Prometheus(选择 LTS 稳定版)
cd /usr/local/src
wget https://github.com/prometheus/prometheus/releases/download/v2.52.0/prometheus-2.52.0.linux-amd64.tar.gz
tar -zxvf prometheus-2.52.0.linux-amd64.tar.gz
# 3. 安装
mkdir -p /etc/prometheus /data/prometheus
cp prometheus-2.52.0.linux-amd64/prometheus /usr/local/bin/
cp prometheus-2.52.0.linux-amd64/promtool /usr/local/bin/
cp -r prometheus-2.52.0.linux-amd64/consoles /etc/prometheus/
cp -r prometheus-2.52.0.linux-amd64/console_libraries /etc/prometheus/
chown -R prometheus:prometheus /etc/prometheus /data/prometheus
chmod +x /usr/local/bin/prometheus /usr/local/bin/promtool
Step 2:配置 prometheus.yml
yaml
# /etc/prometheus/prometheus.yml
global:
scrape_interval: 15s # 采集间隔
scrape_timeout: 10s # 采集超时
evaluation_interval: 15s # 告警规则评估间隔
external_labels:
env: production
project: corporate-batch
# =============================================
# 告警规则文件
# =============================================
rule_files:
- /etc/prometheus/rules/nginx_batch_alerts.yml
# =============================================
# 采集目标
# =============================================
scrape_configs:
# Nginx Master 节点
- job_name: 'nginx-batch-master'
scrape_interval: 15s
metrics_path: '/metrics'
static_configs:
- targets: ['10.1.92.61:8080']
labels:
instance: 'nginx-master'
cluster: 'batch-cluster'
# Nginx Slave 节点
- job_name: 'nginx-batch-slave'
scrape_interval: 15s
metrics_path: '/metrics'
static_configs:
- targets: ['10.1.92.62:8080']
labels:
instance: 'nginx-slave'
cluster: 'batch-cluster'
# Prometheus 自身监控
- job_name: 'prometheus'
scrape_interval: 30s
static_configs:
- targets: ['localhost:9090']
Step 3:配置告警规则
yaml
# /etc/prometheus/rules/nginx_batch_alerts.yml
groups:
- name: nginx_batch_alerts
interval: 30s
rules:
# =============================================
# 1. 接口平均耗时过高告警
# =============================================
- alert: NginxUpstreamAvgLatencyHigh
expr: |
(
rate(nginx_vts_upstream_request_seconds_total{filter=~"location::.*"}[5m])
/
rate(nginx_vts_upstream_requests_total{filter=~"location::.*"}[5m])
) > 600
for: 5m
labels:
severity: warning
component: nginx-batch
annotations:
summary: "Nginx upstream 平均耗时过高"
description: "Location {{ $labels.filter }} 最近5分钟 upstream 平均耗时 {{ $value | humanizeDuration }},可能影响批量任务执行"
# =============================================
# 2. 5xx 错误率告警
# =============================================
- alert: Nginx5xxErrorRateHigh
expr: |
(
sum(rate(nginx_vts_server_requests_total{code="5xx",filter=~"location::.*"}[5m])) by (filter)
/
sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[5m])) by (filter)
) > 0.01
for: 3m
labels:
severity: critical
component: nginx-batch
annotations:
summary: "Nginx 5xx 错误率超过 1%"
description: "Location {{ $labels.filter }} 5xx 错误率 {{ $value | humanizePercentage }},可能存在后端服务故障"
# =============================================
# 3. 上游后端实例不可用告警
# =============================================
- alert: NginxUpstreamDown
expr: |
nginx_vts_upstream_server_up{backend=~".*"} == 0
for: 2m
labels:
severity: critical
component: nginx-batch
annotations:
summary: "Nginx upstream 后端实例不可用"
description: "后端 {{ $labels.backend }} 健康检查失败超过 2 分钟"
# =============================================
# 4. 请求量异常下降告警
# =============================================
- alert: NginxRequestRateDrop
expr: |
(
rate(nginx_vts_server_requests_total{filter=~"location::.*"}[10m])
/
rate(nginx_vts_server_requests_total{filter=~"location::.*"}[10m] offset 1h)
) < 0.1
for: 10m
labels:
severity: warning
component: nginx-batch
annotations:
summary: "Nginx 请求量异常下降"
description: "Location {{ $labels.filter }} 请求量较 1 小时前下降超过 90%"
# =============================================
# 5. Prometheus 采集目标不可达告警
# =============================================
- alert: PrometheusTargetDown
expr: up{job=~"nginx-batch.*"} == 0
for: 2m
labels:
severity: critical
component: prometheus
annotations:
summary: "Prometheus 采集目标不可达"
description: "采集目标 {{ $labels.instance }} 不可达超过 2 分钟"
Step 4:配置 Alertmanager
yaml
# /etc/prometheus/alertmanager.yml
global:
resolve_timeout: 5m
# 企业微信告警
route:
group_by: ['alertname', 'component']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'wechat'
routes:
- match:
severity: critical
receiver: 'wechat-critical'
repeat_interval: 30m
receivers:
- name: 'wechat'
wechat_configs:
- corp_id: 'your_corp_id'
to_party: '运维组'
agent_id: '1000002'
api_secret: 'your_secret'
send_resolved: true
- name: 'wechat-critical'
wechat_configs:
- corp_id: 'your_corp_id'
to_user: '@all'
agent_id: '1000002'
api_secret: 'your_secret'
send_resolved: true
- name: 'email'
email_configs:
- to: 'ops-team@corp.com'
from: 'alertmanager@corp.com'
smarthost: 'smtp.corp.com:25'
send_resolved: true
Step 5:启动 Prometheus
bash
# 1. 创建 systemd 服务
cat > /etc/systemd/system/prometheus.service << 'EOF'
[Unit]
Description=Prometheus Server
Documentation=https://prometheus.io/docs/
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/data/prometheus \
--storage.tsdb.retention.time=30d \
--storage.tsdb.retention.size=150GB \
--web.listen-address=0.0.0.0:9090 \
--web.enable-lifecycle \
--web.enable-admin-api \
--storage.tsdb.wal-compression
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10s
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
EOF
# 2. 启动
systemctl daemon-reload
systemctl enable prometheus
systemctl start prometheus
systemctl status prometheus
# 3. 验证
curl http://127.0.0.1:9090/api/v1/targets | python -m json.tool | grep "health"
# 预期看到所有 target 状态为 "up"
Step 6:启动 Alertmanager
bash
# 创建 systemd 服务
cat > /etc/systemd/system/alertmanager.service << 'EOF'
[Unit]
Description=Alertmanager
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/alertmanager \
--config.file=/etc/prometheus/alertmanager.yml \
--storage.path=/data/alertmanager \
--web.listen-address=0.0.0.0:9093
Restart=on-failure
RestartSec=10s
[Install]
WantedBy=multi-user.target
EOF
mkdir -p /data/alertmanager
chown -R prometheus:prometheus /data/alertmanager
systemctl daemon-reload
systemctl enable alertmanager
systemctl start alertmanager
阶段三:Grafana 部署(mon-grafana-01)
Step 1:安装 Grafana
bash
# 1. 添加 Grafana 官方 YUM 源
cat > /etc/yum.repos.d/grafana.repo << 'EOF'
[grafana]
name=grafana
baseurl=https://packages.grafana.com/oss/rpm
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://packages.grafana.com/gpg.key
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
EOF
# 2. 安装
yum install -y grafana
# 3. 启动
systemctl daemon-reload
systemctl enable grafana-server
systemctl start grafana-server
systemctl status grafana-server
# 4. 开放防火墙
firewall-cmd --permanent --add-port=3000/tcp
firewall-cmd --reload
Step 2:配置数据源
bash
# 1. 登录 Grafana(默认账户 admin/admin,首次登录会要求修改密码)
# http://10.1.92.101:3000
# 2. 添加 Prometheus 数据源
# Configuration → Data Sources → Add data source → Prometheus
# URL: http://10.1.92.100:9090
# Scrape interval: 15s
# 点击 Save & Test
也可通过 API 配置:
bash
curl -X POST http://admin:admin@10.1.92.101:3000/api/datasources \
-H "Content-Type: application/json" \
-d '{
"name": "Prometheus",
"type": "prometheus",
"url": "http://10.1.92.100:9090",
"access": "proxy",
"isDefault": true
}'
Step 3:仪表盘设计
在 Grafana 中创建以下仪表盘(或者导入预置 JSON):
仪表盘一:Nginx 批量任务总览
| 面板 | 类型 | PromQL | 描述 |
|---|---|---|---|
| 全局 QPS | Stat | sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[1m])) |
所有批量任务每秒请求数 |
| 5xx 错误率 | Stat | (sum(rate(nginx_vts_server_requests_total{code="5xx"}[5m])) / sum(rate(nginx_vts_server_requests_total[5m]))) * 100 |
5xx 错误百分比 |
| 请求量时序 | Graph | sum(rate(nginx_vts_server_requests_total{filter="$location"}[5m])) by (code) |
按状态码的请求量趋势 |
| 各中心平均耗时 | Table | 见下方说明 | 各 Location 平均 upstream 耗时 |
各中心平均耗时 PromQL:
promql
# 按 Location 分组的平均 upstream 响应时间(秒)
(
rate(nginx_vts_upstream_request_seconds_total{filter=~"location::.*"}[5m])
/
rate(nginx_vts_upstream_requests_total{filter=~"location::.*"}[5m])
)
# 转换为毫秒
(
rate(nginx_vts_upstream_request_seconds_total{filter=~"location::.*"}[5m])
/
rate(nginx_vts_upstream_requests_total{filter=~"location::.*"}[5m])
) * 1000
仪表盘二:单中心任务耗时详情
使用 Grafana 变量 $location 动态切换:
promql
# 变量定义
label_values(nginx_vts_server_requests_total{filter=~"location::.*"}, filter)
# 上游耗时速率(请求秒数/秒)
rate(nginx_vts_upstream_request_seconds_total{filter="$location"}[1m])
# 请求速率
rate(nginx_vts_upstream_requests_total{filter="$location"}[1m])
# 上游平均响应时间(毫秒)
(
rate(nginx_vts_upstream_request_seconds_total{filter="$location"}[5m])
/
rate(nginx_vts_upstream_requests_total{filter="$location"}[5m])
) * 1000
仪表盘三:Nginx 节点健康状态
| 面板 | PromQL | 描述 |
|---|---|---|
| Nginx 节点存活 | up{job=~"nginx-batch.*"} |
1=存活 0=不可达 |
| 上游后端存活 | nginx_vts_upstream_server_up |
各后端实例健康状态 |
| 上游后端权重 | nginx_vts_upstream_server_weight |
当前权重 |
| 当前连接数 | nginx_vts_main_connections{filter="active"} |
活跃连接数 |
| 连接数变化 | rate(nginx_vts_main_connections{filter="accepted"}[1m]) |
新建连接速率 |
Step 4:Grafana 告警配置
在 Grafana 中也可以设置告警(比 Prometheus Alertmanager 更适合业务人员):
yaml
# Grafana 告警渠道配置
# Alerting → Notification channels → Add channel
# 企业微信
Type: WeCom (企业微信)
Name: WeChat-Ops
Webhook URL: https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxx
# 邮件
Type: Email
Name: Email-Ops
Addresses: ops-team@corp.com
四、架构总览
#mermaid-svg-0DkGP3dT7gcaKQv7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0DkGP3dT7gcaKQv7 .error-icon{fill:#552222;}#mermaid-svg-0DkGP3dT7gcaKQv7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0DkGP3dT7gcaKQv7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .marker.cross{stroke:#333333;}#mermaid-svg-0DkGP3dT7gcaKQv7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0DkGP3dT7gcaKQv7 p{margin:0;}#mermaid-svg-0DkGP3dT7gcaKQv7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster-label text{fill:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster-label span{color:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster-label span p{background-color:transparent;}#mermaid-svg-0DkGP3dT7gcaKQv7 .label text,#mermaid-svg-0DkGP3dT7gcaKQv7 span{fill:#333;color:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .node rect,#mermaid-svg-0DkGP3dT7gcaKQv7 .node circle,#mermaid-svg-0DkGP3dT7gcaKQv7 .node ellipse,#mermaid-svg-0DkGP3dT7gcaKQv7 .node polygon,#mermaid-svg-0DkGP3dT7gcaKQv7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .rough-node .label text,#mermaid-svg-0DkGP3dT7gcaKQv7 .node .label text,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape .label,#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-0DkGP3dT7gcaKQv7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .rough-node .label,#mermaid-svg-0DkGP3dT7gcaKQv7 .node .label,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape .label,#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape .label{text-align:center;}#mermaid-svg-0DkGP3dT7gcaKQv7 .node.clickable{cursor:pointer;}#mermaid-svg-0DkGP3dT7gcaKQv7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .arrowheadPath{fill:#333333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0DkGP3dT7gcaKQv7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0DkGP3dT7gcaKQv7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0DkGP3dT7gcaKQv7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster text{fill:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 .cluster span{color:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0DkGP3dT7gcaKQv7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0DkGP3dT7gcaKQv7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape p,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0DkGP3dT7gcaKQv7 .icon-shape .label rect,#mermaid-svg-0DkGP3dT7gcaKQv7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0DkGP3dT7gcaKQv7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0DkGP3dT7gcaKQv7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0DkGP3dT7gcaKQv7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 被监控业务
通知层
可视化层
指标存储与告警层
数据采集层
负载均衡
负载均衡
负载均衡
scrape /metrics
scrape /metrics
规则评估
数据源
告警通知
告警通知
仪表盘告警
仪表盘告警
Nginx Master
10.1.92.61:9530
Nginx Slave
10.1.92.62:9530
nginx-module-vts
:8080/metrics
nginx-module-vts
:8080/metrics
Prometheus Server
10.1.92.100:9090
TSDB 存储 30d
Alertmanager
:9093
告警分组/去重/路由
Grafana
10.1.92.101:3000
仪表盘 + 告警
企业微信
邮件
批量中心
corpor-batch 64条任务
交易中心
corpor-trans 23条
其他中心
14条
五、关键 PromQL 查询清单
5.1 接口耗时类
promql
# 1. 各中心上游平均耗时(毫秒)
(
rate(nginx_vts_upstream_request_seconds_total{filter=~"location::.*"}[5m])
/
rate(nginx_vts_upstream_requests_total{filter=~"location::.*"}[5m])
) * 1000
# 2. 指定中心最近 1 小时平均耗时趋势(毫秒)
(
rate(nginx_vts_upstream_request_seconds_total{filter="location::corpor-batch"}[5m])
/
rate(nginx_vts_upstream_requests_total{filter="location::corpor-batch"}[5m])
) * 1000
# 3. Nginx 端到端耗时 vs 上游耗时对比
# 端到端: (rate(nginx_vts_server_request_seconds_total[5m]) / rate(nginx_vts_server_requests_total[5m])) * 1000
# 上游: (rate(nginx_vts_upstream_request_seconds_total[5m]) / rate(nginx_vts_upstream_requests_total[5m])) * 1000
# 代理延迟: 端到端 - 上游
5.2 请求量类
promql
# 4. 各中心 QPS
sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[1m])) by (filter)
# 5. 按状态码的 QPS
sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[1m])) by (code)
# 6. 总请求量(过去 1 小时)
sum(increase(nginx_vts_server_requests_total{filter=~"location::.*"}[1h]))
5.3 错误率类
promql
# 7. 各中心 5xx 错误率(百分比)
(
sum(rate(nginx_vts_server_requests_total{code="5xx",filter=~"location::.*"}[5m])) by (filter)
/
sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[5m])) by (filter)
) * 100
# 8. 各中心 4xx 错误率(百分比)
(
sum(rate(nginx_vts_server_requests_total{code="4xx",filter=~"location::.*"}[5m])) by (filter)
/
sum(rate(nginx_vts_server_requests_total{filter=~"location::.*"}[5m])) by (filter)
) * 100
5.4 健康检查类
promql
# 9. 采集目标存活状态
up{job=~"nginx-batch.*"}
# 10. 上游后端存活状态(按后端 server 分组)
nginx_vts_upstream_server_up
# 11. 上游后端当前权重
nginx_vts_upstream_server_weight
六、知识点与注意事项
6.1 nginx-module-vts 工作原理
| 概念 | 说明 |
|---|---|
| 统计时机 | 每次请求结束时,vts 模块通过 Nginx 的 log_subrequest 阶段钩子收集指标 |
| 内存开销 | 所有统计(计数器/累加器)存储在共享内存中,各 Worker 进程共享,内存占用约 2MB~10MB |
| 对象分级 | server(server 级别)→ filter(过滤标签级别)→ upstream(上游后端级别)→ upstream server(单后端实例级别) |
| 指标类型 | 仅暴露 Counter(计数器),无直接 Histogram/Summary,分位值需通过 PromQL 间接计算 |
| 性能影响 | < 1% CPU 开销,基本可忽略 |
6.2 vts 模块的局限性及应对
| 局限性 | 表现 | 应对方案 |
|---|---|---|
| 无原生分位值 | 只能计算平均值,无法直接获得 P95/P99 | 启用 Nginx $request_time 日志 + Loki/mtail 补充分位值计算 |
| Counter 重置 | Nginx reload 后计数器归零 | Prometheus rate() 函数自动处理 Counter 重置 |
| 长时任务统计 | 超长连接(>30min)的耗时计入该时间窗口 | 通过 Grafana 变量选择合适时间窗口 |
| 无法按接口粒度过细 | vts filter 维度过多会增加内存开销 | 按 Location 中心粒度统计即可,不做过细的 filter |
6.3 编译模块注意事项
- 版本匹配:nginx-module-vts 必须与 Nginx 版本匹配,使用前查看模块的 README 确认兼容版本
- 编译依赖 :需要
gcc、make、pcre-devel、openssl-devel、zlib-devel - 平滑升级 :使用
kill -USR2信号实现二进制热替换,避免中断服务 - 保留旧 bin :升级前务必备份
nginx二进制和配置 - 与 upsync 模块共存:nginx-module-vts 与 nginx-upsync-module 无冲突,可以同时编译
6.4 Prometheus 配置注意事项
- scrape_interval 不宜过短:15s 是推荐值,过短会增加 Nginx 和 Prometheus 压力
- 保留时间:30d 保留时间,建议存储空间 ≥ 150GB(取决于指标量)
- WAL 压缩 :
--storage.tsdb.wal-compression减少磁盘 I/O - 生命周期管理 :
--web.enable-lifecycle允许 API 热重载配置(curl -X POST http://localhost:9090/-/reload) - 远程写入:生产环境建议配置 remote_write 到长期存储(如 VictoriaMetrics、Thanos)
6.5 Grafana 配置注意事项
- 数据源缓存 :建议设置
Min time interval为采集间隔(15s),避免查询粒度小于实际数据精度 - 模板变量 :使用
$location变量实现仪表盘复用,无需为每个中心单独创建面板 - 查询超时 :复杂 PromQL 查询可能超时,建议在面板中设置合理的
Max data points - 版本管理:将仪表盘 JSON 导出到 Git 仓库,实现配置版本管理
七、可能存在的问题及解决方案
7.1 编译问题
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 模块不兼容 | nginx: [emerg] unknown directive "vhost_traffic_status_zone" |
确认模块已正确编译进 Nginx;检查 nginx -V 输出 |
| 编译错误 | make 报错缺少依赖 |
yum install -y gcc make pcre-devel openssl-devel zlib-devel |
| 多模块冲突 | 与已有第三方模块冲突 | 调整 ./configure 中模块加载顺序,先加载 vts 再加载 upsync |
| Nginx 版本不匹配 | 版本不一致导致编译失败 | nginx -v 查看当前版本,下载对应版本的 Nginx 源码 |
7.2 运行时问题
| 问题 | 现象 | 解决方案 |
|---|---|---|
| metrics 接口 403 | curl /metrics 返回 403 |
检查 allow/deny 规则,确认 Prometheus 节点 IP 在白名单 |
| vts 无数据 | /vts_status 页面显示空白或 0 |
确认 vhost_traffic_status_zone; 在 http 块中;检查是否有实际请求经过 |
| 内存泄漏 | Nginx 内存持续增长 | vts 共享内存大小默认足够,若大量 filter 标签可限制 vhost_traffic_status_filter_max_node |
| reload 丢指标 | Nginx reload 后 Counter 归零 | Prometheus rate() 函数自动处理 reset,3 个采集周期后恢复正常 |
7.3 Prometheus 问题
| 问题 | 现象 | 解决方案 |
|---|---|---|
| target down | Prometheus 显示 target 状态为 DOWN | 检查网络连通性、防火墙规则、Nginx metrics 端口是否监听 |
| 磁盘空间不足 | Prometheus OOM 或 crash | 调整 --storage.tsdb.retention.size;增加磁盘空间;清理旧 WAL |
| 大量时间序列 | 查询变慢,内存飙升 | 减少 filter 标签维度;使用 relabel_configs 丢弃不必要的标签 |
| 告警不生效 | 规则配了但不触发 | 检查 evaluation_interval;用 Prometheus UI 的 Alerts 页面验证规则表达式 |
7.4 Grafana 问题
| 问题 | 现象 | 解决方案 |
|---|---|---|
| 数据为空 | 面板显示 "No data" | 检查 Prometheus 数据源连接;验证 PromQL 语法;确认时间范围有数据 |
| 查询超时 | 面板加载很慢或超时 | 减少查询时间范围或降低 Step 精度;拆分复杂面板 |
| 分位值不准 | 计算的耗时显示为 0 或异常 | 确认 Counter 累加正常;使用 increase() 代替 rate() 对比验证 |
| Grafana 仪表盘不自动刷新 | 数据显示陈旧 | 检查面板右上角自动刷新设置;确认浏览器标签页保持活跃 |
7.5 高可用问题
| 问题 | 现象 | 解决方案 |
|---|---|---|
| Prometheus 单点故障 | Prometheus 宕机,监控中断 | 部署 2 台 Prometheus,配合 Thanos/Cortex 实现高可用 |
| Grafana 单点故障 | Grafana 宕机,仪表盘不可见 | 使用数据库(MySQL/PostgreSQL)后端 + 多实例部署 |
| Nginx 节点故障 | 某节点 metrics 不可达 | Prometheus 可以区分不同节点指标,不影响其他节点监控 |
八、验证清单
8.1 部署后验证
bash
# 1. 验证 Nginx vts 模块
curl http://10.1.92.61:8080/metrics | head -20
# 应输出 Prometheus 格式的指标,包含 nginx_vts_ 开头
# 2. 验证 Prometheus 采集
curl http://10.1.92.100:9090/api/v1/targets 2>/dev/null | python -m json.tool | grep -E '"job"|"health"'
# 所有 target 的 health 应为 "up"
# 3. 验证指标可查询
curl -s 'http://10.1.92.100:9090/api/v1/query?query=nginx_vts_server_requests_total' | python -m json.tool | head -30
# 4. 验证 Grafana 数据源
curl http://admin:admin@10.1.92.101:3000/api/datasources/1 | python -m json.tool
# 5. 模拟批量请求验证耗时指标
for i in {1..100}; do
curl -s -o /dev/null -w "%{http_code}\n" \
--connect-timeout 30 --max-time 7200 \
"http://10.1.92.60:9530/corpor-batch/xxx" &
done
wait
# 等待 30s 后查看 vts 数据
curl http://10.1.92.61:8080/metrics | grep 'location::corpor-batch'
8.2 验收标准
| 验收项 | 通过标准 |
|---|---|
| Nginx metrics 正常暴露 | curl /metrics 返回 200 + 包含 nginx_vts_* 指标 |
| Prometheus 采集正常 | Targets 页面所有节点状态为 UP |
| Grafana 数据可见 | 仪表盘可正确显示各中心耗时趋势图表 |
| 告警规则正常 | 手动触发告警条件后能在 Alertmanager/企业微信收到通知 |
| 性能影响可控 | Nginx CPU 增加 < 1%,内存增加 < 10MB |
九、总结
本方案通过 nginx-module-vts + Prometheus + Grafana 三层架构实现 Nginx 接口耗时的完整监控体系:
- nginx-module-vts :在 Nginx 侧零侵入地采集请求量、耗时、状态码等指标,通过
/metrics接口暴露 - Prometheus:定时拉取 metrics,存储时序数据,执行告警规则
- Grafana:通过 PromQL 查询构建可视化仪表盘,实现实时监控和历史趋势分析
- Alertmanager:告警分组、去重、路由到企业微信/邮件
核心数据流 :Nginx 请求 → vts 统计 → /metrics → Prometheus scrape → Grafana 可视化 + Alertmanager 告警