Prometheus 与 Grafana 监控
Prometheus 的数据模型由四个部分组成: 完整表示形式: Prometheus 采用主动拉取(pull)模型,区别于大多数监控系统的推送模型: 1. 被监控目标暴露 HTTP /metrics 端点,返回 Prometheus 文本格式数据 2. Prometheus Server 按照配置的 scrape_interval 定期访问该端点 3. 抓取到的数据存入本地 TSDB(时序数据库) 4. 通过 evaluation_interval 周期性执行告警规则 优势:Prometheus 侧控制采集频率,目标无需知道 Prometheus 存在
官方文档:https://prometheus.io/docs/ | https://grafana.com/docs/grafana/latest/
适用版本:Prometheus 2.x / Grafana 10.x(2026-05-07 核实)
核心概念
数据模型
Prometheus 的数据模型由四个部分组成:
- Metric name:指标名称,描述被测量的事物(如
http_requests_total) - Labels:键值对标签,用于区分同一指标的不同维度(如
{method="GET", status="200"}) - Timestamp:时间戳,毫秒精度的 Unix 时间戳
- Value:浮点数值
完整表示形式:
http_requests_total{method="GET", handler="/api/users", status="200"} 1234 1712800000000
Metric 类型
| 类型 | 描述 | 适用场景 | 特点 |
|---|---|---|---|
| Counter | 单调递增计数器,只增不减 | 请求总数、错误总数、已处理字节数 | 重启后归零,配合 rate() 使用 |
| Gauge | 可任意增减的当前值 | 内存用量、连接数、队列长度、温度 | 反映当前状态 |
| Histogram | 将观测值分桶统计,带累积计数 | 请求延迟分布、响应体大小分布 | 服务端计算分位数,精度固定 |
| Summary | 客户端计算分位数 | 需要精确分位数但样本量小的场景 | 客户端计算,无法跨实例聚合 |
Scrape 原理(Pull 模型)
Prometheus 采用主动拉取(pull)模型,区别于大多数监控系统的推送模型:
- 被监控目标暴露 HTTP
/metrics端点,返回 Prometheus 文本格式数据 - Prometheus Server 按照配置的
scrape_interval定期访问该端点 - 抓取到的数据存入本地 TSDB(时序数据库)
- 通过
evaluation_interval周期性执行告警规则
优势:Prometheus 侧控制采集频率,目标无需知道 Prometheus 存在,便于服务发现和状态追踪。
prometheus.yml 配置
global 配置
global:
# 默认抓取间隔,可被 scrape_configs 中的配置覆盖
scrape_interval: 15s
# 告警规则评估间隔
evaluation_interval: 15s
# 抓取超时时间,必须小于 scrape_interval
scrape_timeout: 10s
# 附加到所有时序数据的外部标签,用于联邦或远程写入时区分来源
external_labels:
env: production
region: cn-beijing
| 配置项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
scrape_interval |
duration | 1m | 全局抓取间隔 |
evaluation_interval |
duration | 1m | 规则评估间隔 |
scrape_timeout |
duration | 10s | 单次抓取超时,不能超过 scrape_interval |
external_labels |
map | - | 附加到所有数据的外部标签 |
scrape_configs 结构
scrape_configs:
- job_name: "fastapi-app"
# 覆盖全局 scrape_interval
scrape_interval: 10s
metrics_path: /metrics
scheme: http
static_configs:
- targets:
- "192.168.1.10:8000"
- "192.168.1.11:8000"
labels:
app: fastapi
env: production
# 重打标签:在抓取前修改目标的标签
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: "blackbox-exporter:9115"
- job_name: "node-exporter"
static_configs:
- targets:
- "192.168.1.10:9100"
- "192.168.1.11:9100"
scrape_configs 核心字段:
| 字段 | 说明 |
|---|---|
job_name |
任务名称,自动添加为 job 标签 |
static_configs |
静态目标列表 |
file_sd_configs |
基于文件的服务发现 |
kubernetes_sd_configs |
Kubernetes 服务发现 |
relabel_configs |
抓取前对目标标签进行转换,可过滤/修改/删除标签 |
metrics_path |
指标路径,默认 /metrics |
scrape_interval |
覆盖全局间隔 |
alerting 配置
alerting:
alertmanagers:
- static_configs:
- targets:
- "alertmanager:9093"
# 超时设置
timeout: 10s
# API 版本
api_version: v2
rule_files 告警规则
rule_files:
- "rules/alert_rules.yml"
- "rules/recording_rules.yml"
# 支持通配符
- "rules/*.yml"
PromQL 查询语言
即时查询 vs 范围查询
| 类型 | 语法 | 返回值 | 用途 |
|---|---|---|---|
| 即时查询 | metric_name{label="value"} |
即时向量 | 当前值、仪表盘数值 |
| 范围查询 | metric_name{label="value"}[5m] |
范围向量 | 配合函数计算速率 |
范围向量不能直接在 Grafana 中绘图,必须用函数(如 rate())将其转换为即时向量。
选择器语法
# 精确匹配
http_requests_total{method="GET", status="200"}
# 正则匹配
http_requests_total{method=~"GET|POST"}
# 排除匹配
http_requests_total{status!="500"}
# 排除正则
http_requests_total{method!~"OPTIONS|HEAD"}
# 范围向量(过去 5 分钟的样本)
http_requests_total{method="GET"}[5m]
# 偏移量(1 小时前的值)
http_requests_total offset 1h
常用函数
rate — 平均速率
# 过去 5 分钟 HTTP 请求的平均每秒速率(适合 Counter)
rate(http_requests_total[5m])
irate — 瞬时速率
# 基于最后两个样本计算瞬时速率,对突刺更敏感
irate(http_requests_total[5m])
increase — 增量
# 过去 1 小时内请求总增量
increase(http_requests_total[1h])
sum by — 聚合
# 按 job 标签聚合请求总速率
sum by(job) (rate(http_requests_total[5m]))
# 去掉某个标签,保留其余标签
sum without(instance) (rate(http_requests_total[5m]))
histogram_quantile — 分位数
# 计算过去 5 分钟请求延迟的 P99
histogram_quantile(0.99,
sum by(le) (rate(http_request_duration_seconds_bucket[5m]))
)
# P50 / P95
histogram_quantile(0.50, sum by(le) (rate(http_request_duration_seconds_bucket[5m])))
histogram_quantile(0.95, sum by(le) (rate(http_request_duration_seconds_bucket[5m])))
topk / bottomk
# 请求速率最高的 5 个实例
topk(5, rate(http_requests_total[5m]))
# 内存占用最低的 3 个节点
bottomk(3, node_memory_MemAvailable_bytes)
其他常用函数
# 过去 5 分钟的最大值
max_over_time(process_resident_memory_bytes[5m])
# 预测 4 小时后磁盘是否耗尽(线性预测)
predict_linear(node_filesystem_avail_bytes[1h], 4 * 3600)
# 时序存在时间(秒)
time() - process_start_time_seconds
# 绝对值、向上取整、向下取整
abs(delta(cpu_temp_celsius[10m]))
ceil(rate(http_requests_total[5m]))
floor(rate(http_requests_total[5m]))
运算符
算术运算符
# 将字节转换为 GB
node_memory_MemTotal_bytes / 1024 / 1024 / 1024
# 计算内存使用率(百分比)
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
/ node_memory_MemTotal_bytes * 100
比较运算符
# 过滤出错误率超过 1% 的服务(返回匹配的时序)
rate(http_requests_total{status=~"5.."}[5m])
/ rate(http_requests_total[5m]) > 0.01
# bool 修饰符:返回 0/1 而非过滤
rate(http_requests_total[5m]) > bool 100
| 运算符 | 说明 |
|---|---|
== |
等于 |
!= |
不等于 |
> |
大于 |
< |
小于 |
>= |
大于等于 |
<= |
小于等于 |
逻辑运算符
# and:两侧都有匹配才保留
vector_a and vector_b
# or:两侧任意一个有值即保留
vector_a or vector_b
# unless:保留左侧在右侧中没有匹配的部分
vector_a unless vector_b
向量匹配
# 一对一匹配(标签完全相同)
method_code:http_errors:rate5m / ignoring(code) method:http_requests:rate5m
# 多对一匹配
sum by(app, env) (cpu_usage) / on(app) group_left(env) app_info
Python 应用接入
安装
pip install prometheus-client
Counter
from prometheus_client import Counter
# 创建 Counter
REQUEST_COUNT = Counter(
name="http_requests_total", # 指标名
documentation="Total HTTP requests", # 描述
labelnames=["method", "endpoint", "status_code"], # 标签维度
)
# 使用
REQUEST_COUNT.labels(method="GET", endpoint="/api/users", status_code="200").inc()
REQUEST_COUNT.labels(method="POST", endpoint="/api/login", status_code="401").inc(1)
Counter 参数:
| 参数 | 类型 | 说明 |
|---|---|---|
name |
str | 指标名称,建议以 _total 结尾 |
documentation |
str | 指标描述,出现在 /metrics 页面 |
labelnames |
list[str] | 标签名列表 |
namespace |
str | 前缀,最终名称为 namespace_name |
subsystem |
str | 子系统前缀 |
registry |
Registry | 注册表,默认 REGISTRY |
Gauge
from prometheus_client import Gauge
ACTIVE_CONNECTIONS = Gauge(
"active_connections",
"Current active database connections",
["db_host"],
)
# 设置绝对值
ACTIVE_CONNECTIONS.labels(db_host="db-01").set(42)
# 增减
ACTIVE_CONNECTIONS.labels(db_host="db-01").inc()
ACTIVE_CONNECTIONS.labels(db_host="db-01").dec(5)
# 追踪进行中的任务数
IN_PROGRESS = Gauge("in_progress_requests", "In-progress requests")
with IN_PROGRESS.track_inprogress():
# 进入时 +1,退出时 -1
process_request()
Histogram
from prometheus_client import Histogram
REQUEST_LATENCY = Histogram(
"http_request_duration_seconds",
"HTTP request duration in seconds",
["method", "endpoint"],
# 自定义 bucket 边界(秒)
buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0],
)
# 手动记录
REQUEST_LATENCY.labels(method="GET", endpoint="/api").observe(0.032)
# 使用计时器上下文管理器
with REQUEST_LATENCY.labels(method="GET", endpoint="/api").time():
result = expensive_operation()
Histogram 参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
buckets |
list[float] | DEFAULT_BUCKETS(0.005 到 10) |
分桶边界,Prometheus 自动添加 +Inf |
Summary
from prometheus_client import Summary
PROCESSING_TIME = Summary(
"request_processing_seconds",
"Time spent processing request",
)
@PROCESSING_TIME.time()
def process_request():
pass
Summary 与 Histogram 对比:
| 对比项 | Histogram | Summary |
|---|---|---|
| 分位数计算位置 | 查询时服务端计算 | 采集时客户端计算 |
| 跨实例聚合 | 支持 | 不支持(分位数无法聚合) |
| 分位数配置 | 灵活,查询时指定 | 需提前配置 |
| 内存开销 | 固定(bucket 数量决定) | 较高(维护滑动窗口) |
| 推荐场景 | 分布式、需跨实例聚合 | 单实例、需高精度固定分位数 |
FastAPI 集成
详细的 FastAPI 路由和中间件用法见 FastAPI完全指南。
import time
from fastapi import FastAPI, Request, Response
from prometheus_client import (
Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
)
app = FastAPI()
# 定义指标
REQUEST_COUNT = Counter(
"http_requests_total",
"Total HTTP requests",
["method", "endpoint", "status_code"],
)
REQUEST_LATENCY = Histogram(
"http_request_duration_seconds",
"HTTP request latency",
["method", "endpoint"],
buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5],
)
@app.middleware("http")
async def prometheus_middleware(request: Request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
endpoint = request.url.path
method = request.method
status = str(response.status_code)
REQUEST_COUNT.labels(method=method, endpoint=endpoint, status_code=status).inc()
REQUEST_LATENCY.labels(method=method, endpoint=endpoint).observe(duration)
return response
@app.get("/metrics")
def metrics():
# 返回 Prometheus 文本格式
return Response(
content=generate_latest(),
media_type=CONTENT_TYPE_LATEST,
)
推送模式(PushGateway)
适用于批处理任务、短生命周期进程等无法被 pull 的场景:
from prometheus_client import CollectorRegistry, Counter, push_to_gateway
# 使用独立 registry 避免污染全局
registry = CollectorRegistry()
job_duration = Counter(
"batch_job_duration_seconds_total",
"Total duration of batch jobs",
registry=registry,
)
job_duration.inc(42.5)
# 推送到 PushGateway
push_to_gateway(
gateway="pushgateway:9091",
job="batch_import", # job 标签值
registry=registry,
grouping_key={"instance": "worker-01"}, # 额外分组键
)
push_to_gateway 参数:
| 参数 | 说明 |
|---|---|
gateway |
PushGateway 地址 |
job |
任务名称,作为标签附加 |
registry |
使用的 Registry |
grouping_key |
额外的分组键,用于区分同一 job 的不同实例 |
Grafana
数据源配置
- 进入 Configuration -> Data Sources -> Add data source
- 选择 Prometheus
- 填写 URL(如
http://prometheus:9090) - 根据需要配置认证信息
核心配置项:
| 配置项 | 说明 |
|---|---|
| URL | Prometheus 服务地址 |
| Scrape interval | 告知 Grafana 数据点的最小间隔,影响 $__rate_interval |
| Query timeout | 查询超时 |
| HTTP Method | GET 或 POST,大查询建议用 POST |
Dashboard 基础操作
- 创建 Dashboard:点击左侧 + 图标 -> New Dashboard
- 添加 Panel:Add panel -> Add new panel
- 编辑查询:在 Panel 编辑页输入 PromQL,选择可视化类型
- 保存:Ctrl+S 或点击 Save dashboard
- 导入已有 Dashboard:Dashboards -> Import,粘贴 JSON 或输入 Grafana.com 的 Dashboard ID
常用面板类型
| 面板类型 | 适用场景 | 特点 |
|---|---|---|
| Time Series | 随时间变化的趋势(请求速率、延迟) | 最常用,支持多条折线 |
| Gauge | 展示当前值占最大值的比例(CPU、内存) | 直观的仪表盘样式 |
| Stat | 单一数值展示,支持颜色阈值 | 适合展示总数、当前状态 |
| Table | 多维度对比数据 | 支持列排序、着色 |
| Bar Chart | 分类对比 | 适合 topk 类查询 |
| Heatmap | 分布可视化(Histogram bucket 数据) | 直观展示延迟热力图 |
变量(Variables)
变量用于在 Dashboard 顶部创建下拉筛选,动态过滤数据。
配置路径:Dashboard Settings -> Variables -> New variable
| 变量类型 | 说明 | 示例 |
|---|---|---|
| Query | 从 Prometheus 查询标签值 | label_values(up, job) |
| Custom | 手动指定选项列表 | production, staging, development |
| Interval | 时间间隔选项 | 1m, 5m, 10m, 30m, 1h |
| Datasource | 动态切换数据源 | - |
在查询中使用变量:
# $job 和 $instance 为变量名
rate(http_requests_total{job="$job", instance="$instance"}[5m])
# 多值变量(开启 Multi-value 后)
http_requests_total{job=~"$job"}
告警规则配置
- 进入 Alerting -> Alert rules -> New alert rule
- 填写规则名称和 PromQL 表达式
- 设置触发条件(Threshold:
IS ABOVE 0.05) - 设置 Pending period(持续多久触发,防抖)
- 配置 Labels 和 Annotations(通知内容模板)
- 关联 Notification policy 和 Contact point
告警状态说明:
| 状态 | 说明 |
|---|---|
| Normal | 条件未满足 |
| Pending | 条件满足但未超过 Pending period |
| Firing | 条件持续满足,告警已触发 |
| NoData | 查询无返回数据 |
| Error | 查询执行出错 |
实用 Dashboard 推荐
| Dashboard 名称 | Grafana ID | 说明 |
|---|---|---|
| Node Exporter Full | 1860 | 节点 CPU/内存/磁盘/网络全面监控 |
| Prometheus 2.0 Overview | 3662 | Prometheus 自身性能监控 |
| FastAPI Observability | 17175 | FastAPI 应用请求、延迟、错误率 |
| MySQL Overview | 7362 | MySQL 性能监控 |
| Redis Dashboard | 11835 | Redis 内存、命令、连接数监控 |
AlertManager 告警
告警规则 YAML 语法
# rules/alert_rules.yml
groups:
- name: application_alerts
# 规则评估间隔(覆盖全局)
interval: 30s
rules:
- alert: HighErrorRate
# PromQL 表达式,结果非空时触发告警
expr: |
sum by(job) (rate(http_requests_total{status=~"5.."}[5m]))
/ sum by(job) (rate(http_requests_total[5m])) > 0.05
# 持续时间:条件满足持续 5m 后才真正触发
for: 5m
labels:
severity: critical
team: backend
annotations:
summary: "服务 {{ $labels.job }} 错误率过高"
description: "错误率为 {{ $value | humanizePercentage }},超过 5% 阈值"
runbook_url: "https://wiki.example.com/runbooks/high-error-rate"
- alert: HighMemoryUsage
expr: |
(node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)
/ node_memory_MemTotal_bytes > 0.9
for: 10m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.instance }} 内存使用率超过 90%"
- alert: DiskWillFillIn4Hours
expr: predict_linear(node_filesystem_avail_bytes[1h], 4 * 3600) < 0
for: 5m
labels:
severity: critical
annotations:
summary: "节点 {{ $labels.instance }} 磁盘预计 4 小时内耗尽"
告警规则字段说明:
| 字段 | 说明 |
|---|---|
alert |
告警规则名称 |
expr |
PromQL 表达式,非空时进入 Pending 或 Firing 状态 |
for |
持续时间,防止瞬时抖动触发误告警 |
labels |
附加标签,用于路由和分组 |
annotations |
告警描述,支持 Go template 语法({{ $labels.xxx }}) |
AlertManager 配置
# alertmanager.yml
global:
# 默认重复发送间隔
repeat_interval: 4h
# SMTP 全局配置
smtp_smarthost: "smtp.example.com:587"
smtp_from: "[email protected]"
smtp_auth_username: "[email protected]"
smtp_auth_password: "password"
# 通知路由树
route:
# 默认路由分组(所有未匹配的告警走这里)
receiver: "default-receiver"
# 分组等待时间(积攒同组告警一起发送)
group_wait: 30s
# 同组后续通知间隔
group_interval: 5m
# 重复通知间隔
repeat_interval: 4h
# 按哪些标签分组
group_by: [alertname, job]
# 子路由
routes:
- match:
severity: critical
receiver: "pagerduty-critical"
continue: false
- match_re:
team: "backend|frontend"
receiver: "dingtalk-webhook"
group_by: [alertname, team]
# 接收器定义
receivers:
- name: "default-receiver"
email_configs:
- to: "[email protected]"
send_resolved: true
headers:
Subject: "[{{ .Status | toUpper }}] {{ .GroupLabels.alertname }}"
- name: "dingtalk-webhook"
webhook_configs:
- url: "http://dingtalk-webhook:8060/dingtalk/webhook1/send"
send_resolved: true
http_config:
bearer_token: "your-token"
- name: "pagerduty-critical"
pagerduty_configs:
- routing_key: "your-pagerduty-routing-key"
severity: critical
# 静默规则(运维窗口期使用)
inhibit_rules:
- source_match:
severity: critical
target_match:
severity: warning
# 同一 job 和 instance 的 warning 会被 critical 抑制
equal: [alertname, job, instance]
route 字段说明:
| 字段 | 说明 |
|---|---|
receiver |
默认接收器名称 |
group_by |
分组标签,相同标签组合的告警聚合为一条通知 |
group_wait |
初次发送前等待时间,收集同组告警 |
group_interval |
同一组有新告警时等待多久再发送 |
repeat_interval |
告警持续未解决时重复通知间隔 |
continue |
匹配此路由后是否继续匹配后续路由 |
最佳实践
指标命名规范
| 规则 | 正确示例 | 错误示例 |
|---|---|---|
| 使用基础单位(秒、字节) | http_request_duration_seconds |
http_request_duration_ms |
Counter 以 _total 结尾 |
http_requests_total |
http_requests_count |
Gauge 不加 _total |
active_connections |
active_connections_total |
| 应用名作前缀 | myapp_cache_hits_total |
cache_hits_total |
| 使用小写和下划线 | node_memory_bytes |
nodeMemoryBytes |
| 比例用 ratio(0–1) | error_ratio |
error_percent |
命名格式:[namespace_][subsystem_]name[_unit][_total]
标签(Label)设计原则
- 低基数原则:单个 Label 的唯一值不超过 100,全部标签组合不超过 10,000 个时序
- 不在 Label 中放:用户 ID、订单 ID、请求 ID、IP 地址、邮箱等高基数值
- 可以放的 Label:
method、status、endpoint(固定路由,非含参 URL)、region、env - URL 参数路由(如
/users/123)需在接入层归一化为/users/{id}再作 Label
Recording Rules:预计算昂贵查询
频繁使用的复杂 PromQL 应转换为 Recording Rule,避免每次 Dashboard 刷新都重新计算:
# rules/recording_rules.yml
groups:
- name: http_metrics
interval: 1m
rules:
# 预计算:按 job 聚合的 5 分钟请求速率
- record: job:http_requests:rate5m
expr: sum by(job) (rate(http_requests_total[5m]))
# 预计算:P99 延迟
- record: job:http_request_duration_p99:rate5m
expr: |
histogram_quantile(0.99,
sum by(job, le) (rate(http_request_duration_seconds_bucket[5m]))
)
在 Dashboard 中直接查询 job:http_requests:rate5m,响应速度提升数倍。
黄金信号:监控什么
参考 Google SRE 四个黄金信号:
| 信号 | 指标示例 | PromQL 示例 |
|---|---|---|
| 延迟(Latency) | P99 请求时间 | histogram_quantile(0.99, sum by(le) (rate(http_request_duration_seconds_bucket[5m]))) |
| 流量(Traffic) | 每秒请求数 | sum(rate(http_requests_total[5m])) |
| 错误(Errors) | 5xx 错误率 | sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) |
| 饱和度(Saturation) | CPU / 内存使用率 | 1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) |
告警规则设计
- 告警应对症状,而非原因:告警"错误率 > 5%"(用户能感知),而非"数据库连接池耗尽"(内部状态)
- 避免无法执行的告警:每条告警都应有对应的 Runbook,注明处置步骤
- 善用
for防抖:基础设施告警用 5–10 分钟,避免闪烁 - 分级路由:critical → 立即呼叫值班,warning → 工作时间处理,info → 仅记录
Grafana Dashboard 组织
- 每个服务建一个 Dashboard,通过顶部变量(
$job、$instance)切换实例 - Row 按层次组织:Overview → 请求层 → 业务层 → 基础设施层
- 优先使用 Grafana.com 社区 Dashboard(见上方推荐 ID),在其基础上定制
- 使用
$__rate_interval替代硬编码[5m],自动适配 scrape interval 变化 - Dashboard 用 JSON 文件纳入版本控制(
grafana-dashboard-exporter或 Terraform)
踩坑与注意事项
高基数 Label 导致性能问题
问题:将用户 ID、URL 参数、请求 ID 等高基数(大量唯一值)字段作为 Label,导致时序数量爆炸式增长,Prometheus 内存和存储急剧膨胀。
# 错误示例:user_id 可能有数百万个唯一值
REQUEST_COUNT = Counter("requests", "...", ["endpoint", "user_id"])
REQUEST_COUNT.labels(endpoint="/api", user_id=user.id).inc()
# 正确示例:只用低基数标签
REQUEST_COUNT = Counter("requests", "...", ["endpoint", "status"])
REQUEST_COUNT.labels(endpoint="/api", status="200").inc()
原则:Label 的唯一值组合(cardinality)不应超过 10,000,单个 Label 的唯一值不应超过 100。
Histogram bucket 设置不合理
问题:使用默认 bucket(最大 10 秒)监控数据库查询(通常 < 100ms),导致大量数据集中在第一个 bucket,分位数计算失真。
# 错误:使用默认 bucket,最大值为 10s,不适合毫秒级延迟
DB_LATENCY = Histogram("db_query_seconds", "Database query duration")
# 正确:根据实际延迟范围设置 bucket
DB_LATENCY = Histogram(
"db_query_seconds",
"Database query duration",
buckets=[0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0],
)
原则:bucket 应覆盖正常延迟的 90% 以上,在关注的延迟区间内密集分布。
rate() 函数使用注意
rate()内的时间窗口至少要包含 4 个采样点,否则结果不稳定。公式:窗口 >= 4 × scrape_intervalrate()会自动处理 Counter 重置,不要对 Gauge 使用rate()- 长时间窗口(如
[1h])会平滑短期波动,短时间窗口响应更敏感
告警规则 for 字段
for 设置过短会导致告警抖动频繁发送通知,设置过长会延迟发现问题。建议:
- 基础设施告警(CPU、内存):5-10 分钟
- 业务指标告警(错误率):2-5 分钟
- 依赖不可用(端点 down):1-2 分钟
常见陷阱
陷阱:rate() 应用于 Gauge 指标得到无意义结果
现象: 对 CPU 使用率(Gauge)用 rate(cpu_usage[5m]) 计算,结果忽大忽小毫无规律。
原因: rate() 是为单调递增的 Counter 设计的(自动处理重置),Gauge 可以任意上下波动,rate() 计算的是区间内的增长速率,对 Gauge 没有意义。
解决: Gauge 指标直接查询或用 avg_over_time(metric[5m]) 计算时间窗口内的平均值;只对 Counter(名称通常以 _total 结尾)使用 rate()。
陷阱:Alert 频繁抖动(Flapping)
现象: 同一告警在短时间内反复触发和恢复,发送大量通知骚扰值班人员。
原因: 指标在阈值附近波动,for 持续时间设置过短,每次短暂超阈值就触发告警。
解决: 适当延长 for 字段(CPU 告警 ≥ 5 分钟),在 AlertManager 配置 repeat_interval 控制重复通知间隔,并启用 resolve_timeout 防止过早恢复。
陷阱:Grafana Dashboard 时区与数据时区不一致
现象: Grafana 图表显示的时间与实际事件时间相差 8 小时(或其他偏移)。
原因: Prometheus 存储时间戳为 UTC,Grafana Dashboard 的时区设置为本地时区,若服务器时区与 Grafana 配置不一致则产生偏移。
解决: 在 Dashboard 设置中将时区改为 Browser(跟随用户浏览器)或明确设置 Asia/Shanghai;确保 Prometheus 服务器和 Grafana 时区配置一致。
参见
- FastAPI完全指南 — FastAPI 应用接入 Prometheus 的完整示例
- Docker Compose完全指南 — 用 Docker Compose 部署 Prometheus + Grafana + AlertManager 全栈
- Kubernetes基础 — Kubernetes 服务发现(
kubernetes_sd_configs)配置 - Kubernetes进阶 — kube-prometheus-stack Helm Chart 部署监控全栈