系统出现慢请求时,CPU 图、应用日志和链路追踪可能分别正常。要让这些材料共同解释一次失败,需要先定义用户可感知的目标,再统一请求标识、时间和服务版本,沿调用路径查找异常。可观测性建立在指标、日志与链路追踪三大支柱上:指标说明发生了什么,日志说明为什么发生,链路追踪说明在哪里发生。本文按监控与告警、日志与链路追踪、SLO 与跨层故障定位三部分展开。
一、监控与告警
监控体系三大支柱
| 支柱 | 含义 | 代表工具 |
|---|---|---|
| 指标(Metrics) | 可聚合的数值型时间序列数据 | Prometheus、InfluxDB、VictoriaMetrics |
| 日志(Logs) | 离散的事件记录 | Loki、ELK Stack、ClickHouse |
| 链路追踪(Traces) | 请求在分布式系统中的完整路径 | Jaeger、Zipkin、Tempo |
三类信号互补:指标告诉你“发生了什么”(CPU 飙到 95%),日志告诉你“为什么发生”(OOM Killer 杀掉了进程),链路追踪告诉你“在哪里发生的”(哪个微服务调用超时)。本章覆盖指标监控(Prometheus 生态)与告警;日志采集独立维护在日志收集与分析,链路与 APM 见第二章。
Prometheus:时序数据引擎
Prometheus 采用 Pull 模型:主动从目标拉取指标,目标地址由静态配置或服务发现提供;服务发现与拉取是两项独立机制。
┌──────────────┐
│ Alertmanager │
└──────┬───────┘
│ 告警推送
┌──────────────┐ ┌─────┴──────┐ ┌──────────────┐
│ node_exporter │──▶│ Prometheus │◀──│ 应用 Exporter │
└──────────────┘ └─────┬──────┘ └──────────────┘
│
┌──────┴───────┐
│ Grafana │
└──────────────┘二进制安装(生产环境常用)
PROM_VERSION="2.52.0"
wget https://github.com/prometheus/prometheus/releases/download/v${PROM_VERSION}/prometheus-${PROM_VERSION}.linux-amd64.tar.gz
tar xzf prometheus-${PROM_VERSION}.linux-amd64.tar.gz
sudo mv prometheus-${PROM_VERSION}.linux-amd64/{prometheus,promtool} /usr/local/bin/
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo useradd --no-create-home --shell /bin/false prometheus
sudo chown -R prometheus:prometheus /var/lib/prometheus配置文件 /etc/prometheus/prometheus.yml:
global:
scrape_interval: 15s # 全局采集间隔
evaluation_interval: 15s # 规则评估间隔
scrape_timeout: 10s # 采集超时
rule_files:
- /etc/prometheus/rules/*.yml
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'node'
static_configs:
- targets:
- '192.168.1.10:9100'
- '192.168.1.11:9100'
- '192.168.1.12:9100'
labels:
env: production
region: cn-east
- job_name: 'file-sd'
file_sd_configs:
- files:
- /etc/prometheus/targets/*.json
refresh_interval: 30s创建 Systemd 服务并启动:
cat <<'EOF' | sudo tee /etc/systemd/system/prometheus.service
[Unit]
Description=Prometheus Monitoring
After=network-online.target
Wants=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \
--config.file=/etc/prometheus/prometheus.yml \
--storage.tsdb.path=/var/lib/prometheus \
--storage.tsdb.retention.time=30d \
--storage.tsdb.retention.size=50GB \
--web.enable-lifecycle \
--web.enable-admin-api
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus验证:curl -s http://localhost:9090/-/healthy,并确认 systemctl status prometheus 处于 active 状态。
Docker Compose 快速搭建
version: '3.8'
services:
prometheus:
image: prom/prometheus:v2.52.0
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- ./rules:/etc/prometheus/rules
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-lifecycle'
restart: unless-stopped
volumes:
prometheus_data:PromQL 实战
基础查询:
# CPU 使用率(所有核心)
100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
# 内存使用率
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100
# 磁盘使用率
(1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100
# 网络流入速率(MB/s)
rate(node_network_receive_bytes_total{device="eth0"}[5m]) / 1024 / 1024进阶技巧:
# 增长率:过去 1 小时磁盘写入总量
increase(node_disk_written_bytes_total[1h])
# 预测:基于过去 6 小时趋势,预测 24 小时后磁盘使用量
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[6h], 24*3600)
# 排序:CPU 使用率 Top 5 的实例
topk(5, 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100))
# 比较:当前值 vs 昨日同期
node_load1 offset 1d
# 聚合:按环境汇总 HTTP 请求总量
sum by(env) (rate(http_requests_total[5m]))常用函数:
| 函数 | 用途 | 示例 |
|---|---|---|
rate() |
计算每秒增长率 | rate(http_requests_total[5m]) |
increase() |
计算区间增量 | increase(counter[1h]) |
irate() |
瞬时增长率(更敏感) | irate(http_requests_total[5m]) |
avg_over_time() |
区间平均值 | avg_over_time(http_requests_total[5m]) |
histogram_quantile() |
百分位计算 | histogram_quantile(0.99, ...) |
predict_linear() |
线性预测 | predict_linear(disk_free[6h], 86400) |
label_replace() |
标签重写 | label_replace(up, "host", "$1", "instance", "(.*):.*") |
node_exporter:主机指标采集
NODE_VERSION="1.8.0"
wget https://github.com/prometheus/node_exporter/releases/download/v${NODE_VERSION}/node_exporter-${NODE_VERSION}.linux-amd64.tar.gz
tar xzf node_exporter-${NODE_VERSION}.linux-amd64.tar.gz
sudo mv node_exporter-${NODE_VERSION}.linux-amd64/node_exporter /usr/local/bin/
sudo useradd --no-create-home --shell /bin/false node_exporter
cat <<'EOF' | sudo tee /etc/systemd/system/node_exporter.service
[Unit]
Description=Node Exporter
After=network.target
[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter \
--collector.systemd \
--collector.processes \
--collector.tcpstat \
--web.listen-address=:9100
Restart=always
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter验证:curl -s http://localhost:9100/metrics | head 应返回指标文本。
值得关注的采集器:
| 采集器 | 采集内容 | 启用方式 |
|---|---|---|
systemd |
systemd 单元状态 | --collector.systemd |
processes |
进程状态统计 | --collector.processes |
tcpstat |
TCP 连接状态 | --collector.tcpstat |
textfile |
自定义指标文件 | --collector.textfile.directory=/var/lib/node_exporter/textfile |
textfile 采集器生成自定义指标:
cat <<'EOF' > /usr/local/bin/custom_metrics.sh
#!/bin/bash
# 待更新的包数量
UPDATES=$(apt list --upgradable 2>/dev/null | grep -c upgradable)
echo "node_pending_security_updates ${UPDATES}" > /var/lib/node_exporter/textfile/security_updates.prom
EOF
echo "0 * * * * root /usr/local/bin/custom_metrics.sh" | sudo tee /etc/cron.d/custom_metrics常用主机指标:
# CPU 相关
node_cpu_seconds_total # CPU 时间(按模式)
node_load1 / node_load5 / node_load15 # 系统负载
# 内存相关
node_memory_MemTotal_bytes # 总内存
node_memory_MemAvailable_bytes # 可用内存
node_memory_Buffers_bytes # Buffer 缓存
node_memory_Cached_bytes # Page Cache
# 磁盘相关
node_filesystem_size_bytes # 文件系统总大小
node_filesystem_avail_bytes # 可用空间
node_disk_io_time_seconds_total # 磁盘 IO 时间
node_disk_read_bytes_total # 读取字节数
# 网络相关
node_network_receive_bytes_total # 接收字节数
node_network_transmit_bytes_total # 发送字节数
node_network_receive_errs_total # 接收错误数
# 系统相关
node_time_seconds # 当前时间戳
node_boot_time_seconds # 启动时间戳
node_filefd_allocated # 已分配文件描述符Grafana:数据可视化
# Debian/Ubuntu
sudo apt-get install -y apt-transport-https software-properties-common
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt-get update && sudo apt-get install grafana
sudo systemctl enable --now grafana-server默认访问 http://localhost:3000,初始账号 admin / admin。通过 API 添加 Prometheus 数据源:
curl -s -X POST http://admin:admin@localhost:3000/api/datasources \
-H "Content-Type: application/json" \
-d '{"name": "Prometheus", "type": "prometheus", "url": "http://localhost:9090", "access": "proxy", "isDefault": true}'社区面板可以直接导入,按需挑选:
| Dashboard ID | 名称 | 用途 |
|---|---|---|
| 1860 | Node Exporter Full | 主机监控全览 |
| 9628 | Prometheus 2.0 Overview | Prometheus 自身监控 |
| 12006 | Kafka Exporter | Kafka 监控 |
| 763 | Redis Dashboard | Redis 监控 |
| 12740 | Kubernetes Pods | K8s Pod 监控 |
一个最小面板 JSON(CPU 使用率)可作为自定义面板起点:
{
"panels": [
{
"title": "CPU 使用率",
"type": "timeseries",
"targets": [
{
"expr": "100 - (avg by(instance) (rate(node_cpu_seconds_total{mode=\"idle\"}[5m])) * 100)",
"legendFormat": "{{instance}}"
}
],
"fieldConfig": {
"defaults": {
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"steps": [
{ "value": null, "color": "green" },
{ "value": 70, "color": "yellow" },
{ "value": 90, "color": "red" }
]
}
}
}
}
]
}常见问题排查:
# 采集目标的健康状态与最近错误
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.health=="down") | {instance, lastError}'
# 规则文件语法
promtool check rules /etc/prometheus/rules/*.yml
# 数据源联通性
curl -s http://localhost:9090/api/v1/query?query=up监控最佳实践
分层监控策略:
┌─────────────────────────────────────────────┐
│ L1: 基础设施层 (CPU/内存/磁盘/网络) │ ← node_exporter
├─────────────────────────────────────────────┤
│ L2: 平台服务层 (DB/缓存/消息队列/K8s) │ ← 各组件 Exporter
├─────────────────────────────────────────────┤
│ L3: 应用服务层 (QPS/延迟/错误率/业务指标) │ ← 应用埋点
├─────────────────────────────────────────────┤
│ L4: 业务指标层 (订单量/转化率/活跃用户) │ ← 自定义 Metrics
└─────────────────────────────────────────────┘容量规划与存储估算:
# 磁盘容量预测(线性回归)
predict_linear(node_filesystem_avail_bytes{mountpoint="/"}[7d], 30*24*3600)
# Prometheus 存储大小 ~ 每个样本 2 字节
prometheus_tsdb_head_series # 当前活跃时间序列数
rate(prometheus_tsdb_head_chunks_created_total[1h]) # 每小时新增 chunk 数生产环境推荐部署两套 Prometheus 做冗余,Alertmanager 用集群去重:
# alertmanager.yml 中配置集群
cluster:
listen-address: "0.0.0.0:9094"
peers:
- "alertmanager-1:9094"
- "alertmanager-2:9094"长期存储可选 Thanos(全局查询与对象存储)、VictoriaMetrics(兼容 Prometheus 协议的高性能时序库)或 Cortex(可水平扩展的 Prometheus 后端)。
监控系统自身也要被监控:
prometheus_config_last_reload_successful # 配置是否加载成功
prometheus_rule_evaluation_duration_seconds # 规则评估耗时
up # 目标是否可达
alertmanager_notifications_total # 通知发送总量
alertmanager_notifications_failed_total # 通知失败量告警体系与分级
一个完整的告警体系由五层组成:
数据采集 → 指标计算 → 规则评估 → 告警路由 → 响应处置
Prometheus PromQL AlertManager 通知渠道 Runbook/SOP
Telegraf recording 评估引擎 飞书/钉钉 自动化修复
Exporters rules Silences 邮件/SMS Postmortem设计目标:每条告警关联明确的响应动作;不同严重程度走不同通道和响应时效;压制噪声;从触发到恢复的记录可追溯。
P0-P4 五级分级:
| 级别 | 定义 | 响应时效 | 通知方式 | 示例 |
|---|---|---|---|---|
| P0 致命 | 核心业务全面不可用 | 5分钟内响应 | 电话+短信+IM群 | 数据库主库宕机、支付链路中断 |
| P1 严重 | 核心业务部分降级 | 15分钟内响应 | 短信+IM群+邮件 | API响应延迟大于5s、错误率大于10% |
| P2 一般 | 非核心功能异常 | 30分钟内响应 | IM群+邮件 | 磁盘使用率大于85%、非核心服务重启 |
| P3 提醒 | 潜在风险预警 | 4小时内响应 | 邮件+工单 | SSL证书30天内过期、版本落后 |
| P4 观察 | 趋势关注,无需立即处理 | 下一工作日 | 工单 | 资源使用率缓慢增长 |
升级机制:
┌─────────────┐ 15min未响应 ┌─────────────┐ 30min未响应 ┌─────────────┐
│ 初级值班 │ ──────────────→ │ 高级值班 │ ──────────────→ │ 值班经理 │
│ (一线) │ │ (二线) │ │ (三线) │
└─────────────┘ └─────────────┘ └─────────────┘
↑ ↑
│ P0直接通知 │ P0超30min直接
└─────────────────────────────────┘route:
receiver: 'oncall-primary'
group_wait: 30s
routes:
- match:
severity: critical
receiver: 'oncall-primary'
group_wait: 10s
routes:
- receiver: 'oncall-secondary'
matchers:
- ack = ""
repeat_interval: 15m
- receiver: 'oncall-manager'
matchers:
- ack = ""
repeat_interval: 30mAlertmanager 基础部署
AM_VERSION="0.27.0"
wget https://github.com/prometheus/alertmanager/releases/download/v${AM_VERSION}/alertmanager-${AM_VERSION}.linux-amd64.tar.gz
tar xzf alertmanager-${AM_VERSION}.linux-amd64.tar.gz
sudo mv alertmanager-${AM_VERSION}.linux-amd64/{alertmanager,amtool} /usr/local/bin/
sudo mkdir -p /etc/alertmanager /var/lib/alertmanager基础配置 /etc/alertmanager/alertmanager.yml:
global:
resolve_timeout: 5m
smtp_smarthost: 'smtp.example.com:587'
smtp_from: 'alert@example.com'
smtp_auth_username: 'alert@example.com'
smtp_auth_password: 'your-password'
smtp_require_tls: true
route:
receiver: 'default-receiver'
group_by: ['alertname', 'instance']
group_wait: 30s # 首次告警等待时间(聚合)
group_interval: 5m # 同组告警间隔
repeat_interval: 4h # 重复告警间隔
routes:
- match:
severity: critical
receiver: 'critical-receiver'
group_wait: 10s
repeat_interval: 1h
- match:
severity: warning
receiver: 'warning-receiver'
repeat_interval: 4h
inhibit_rules:
- source_match:
severity: 'critical'
target_match:
severity: 'warning'
equal: ['alertname', 'instance']
receivers:
- name: 'default-receiver'
email_configs:
- to: 'ops-team@example.com'
- name: 'critical-receiver'
email_configs:
- to: 'ops-emergency@example.com'
webhook_configs:
- url: 'http://localhost:8060/dingtalk/ops/send'
send_resolved: true
- name: 'warning-receiver'
email_configs:
- to: 'ops-team@example.com'告警规则文件与验证
创建 /etc/prometheus/rules/node_alerts.yml:
groups:
- name: node_alerts
rules:
- alert: HighCpuUsage
expr: 100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 5m
labels:
severity: warning
annotations:
summary: "CPU 使用率过高 (实例 {{ $labels.instance }})"
description: 'CPU 使用率已超过 85%,当前值 {{ $value | printf "%.1f" }}%,持续 5 分钟。'
- alert: HighMemoryUsage
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
for: 5m
labels:
severity: critical
annotations:
summary: "内存使用率过高 (实例 {{ $labels.instance }})"
description: '内存使用率已超过 90%,当前值 {{ $value | printf "%.1f" }}%。'
- alert: DiskSpaceLow
expr: (1 - node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes) * 100 > 85
for: 10m
labels:
severity: warning
annotations:
summary: "磁盘空间不足 ({{ $labels.instance }}:{{ $labels.mountpoint }})"
description: '磁盘 {{ $labels.mountpoint }} 使用率 {{ $value | printf "%.1f" }}%。'
- alert: DiskWillFillIn24h
expr: predict_linear(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}[6h], 24*3600) < 0
for: 30m
labels:
severity: critical
annotations:
summary: "磁盘预计 24 小时内写满 ({{ $labels.instance }}:{{ $labels.mountpoint }})"
- alert: InstanceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "实例宕机 ({{ $labels.instance }})"
description: "{{ $labels.job }} 的 {{ $labels.instance }} 已宕机超过 1 分钟。"
- alert: SystemReboot
expr: changes(node_boot_time_seconds[15m]) > 0
labels:
severity: warning
annotations:
summary: "系统发生重启 ({{ $labels.instance }})"
- name: application_alerts
rules:
- alert: HighHttp5xxRate
expr: sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "HTTP 5xx 错误率超过 5%"
description: '当前 5xx 错误率 {{ $value | printf "%.2f" }}%。'
- alert: HighRequestLatency
expr: histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le)) > 2
for: 5m
labels:
severity: warning
annotations:
summary: "P99 请求延迟超过 2 秒"
description: '当前 P99 延迟 {{ $value | printf "%.2f" }} 秒。'验证与热加载:
promtool check rules /etc/prometheus/rules/*.yml
curl -X POST http://localhost:9090/-/reload
curl -s http://localhost:9090/api/v1/alerts | jq .在 Prometheus Web UI 的 Status → Rules 查看规则状态、Alerting → Alerts 查看当前告警;用 amtool config routes test alertname=HighCpuUsage severity=warning 测试路由命中。
告警规则设计
阈值告警适合指标有明确安全边界的场景:
groups:
- name: host_alerts
rules:
- alert: HighCpuUsage
expr: |
100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100) > 85
for: 5m
labels:
severity: warning
team: infra
annotations:
summary: "CPU 使用率过高: {{ $labels.instance }}"
description: 'CPU 使用率 {{ $value | printf "%.1f" }}%,已持续5分钟'
runbook: "https://wiki.internal/runbook/high-cpu"
- alert: DiskSpaceRunningLow
expr: |
(node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes) * 100 < 15
for: 10m
labels:
severity: warning
annotations:
summary: "磁盘空间不足: {{ $labels.instance }}:{{ $labels.mountpoint }}"
description: '剩余空间 {{ $value | printf "%.1f" }}%'
- alert: OOMKillDetected
expr: increase(node_vmstat_oom_kill[5m]) > 0
labels:
severity: critical
annotations:
summary: "OOM Kill 发生: {{ $labels.instance }}"
description: "5分钟内发生 {{ $value }} 次 OOM Kill"趋势告警针对指标未达阈值但恶化速度异常的场面:
- alert: MemoryLeakSuspected
expr: |
deriv(node_memory_MemAvailable_bytes[30m]) < -104857600 # 30分钟下降>100MB
and
node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.3
for: 1h
labels:
severity: warning
annotations:
summary: "疑似内存泄漏: {{ $labels.instance }}"
description: "可用内存30分钟内下降超过100MB,当前剩余 {{ $value }}"
- alert: DiskWriteRateSpike
expr: |
rate(node_disk_written_bytes_total[5m]) > 3 * rate(node_disk_written_bytes_total[1h] offset 1d)
for: 10m
labels:
severity: warning
annotations:
summary: "磁盘写入速率异常飙升: {{ $labels.device }}"同比环比适合有明显周期性的业务指标:
- alert: TrafficDropHourOverHour
expr: |
sum(rate(http_requests_total[5m])) < 0.5 * sum(rate(http_requests_total[5m] offset 1h))
for: 10m
labels:
severity: critical
annotations:
summary: "流量环比骤降50%"
description: "当前QPS {{ $value }},1小时前为对照值的2倍以上"
- alert: TrafficDropWeekOverWeek
expr: |
sum(rate(http_requests_total[5m])) < 0.3 * sum(rate(http_requests_total[5m] offset 7d))
for: 15m
labels:
severity: warning
annotations:
summary: "流量同比大幅下降"基于统计学的异常检测用动态阈值替代静态阈值:
groups:
- name: anomaly_detection
rules:
- alert: ResponseTimeAnomaly
expr: |
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
>
(
avg_over_time(
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))[24h:5m]
)
+
3 * stddev_over_time(
histogram_quantile(0.99, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))[24h:5m]
)
)
for: 10m
labels:
severity: warning
annotations:
summary: "P99延迟异常偏离: {{ $labels.service }}"
description: "当前P99 {{ $value }}s,超出历史3σ范围"
- alert: ErrorRateAnomaly
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m]))
>
3 * quantile_over_time(0.99,
(sum(rate(http_requests_total{status=~"5.."}[5m])) / sum(rate(http_requests_total[5m])))[7d:5m]
)
for: 5m
labels:
severity: critical
annotations:
summary: "错误率异常飙升,超过历史P99的3倍"告警降噪
去重:相同 labelset 的告警在一个 group 中只发送一次,直到 resolve,关键是控制 group_by 粒度。
聚合:把相关的多条告警打包成一条通知。
抑制:高优先级告警触发时抑制低优先级的相关告警。
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- match:
team: platform
group_by: ['cluster', 'service']
group_wait: 60s
group_interval: 10m
receiver: 'platform-oncall'
inhibit_rules:
- source_matchers:
- severity = "critical"
- alertname = "ClusterDown"
target_matchers:
- severity =~ "warning|info"
equal: ['cluster']
- source_matchers:
- alertname = "ServiceDown"
target_matchers:
- alertname =~ "High.*Latency|HighErrorRate"
equal: ['service']静默用于维护窗口和已知问题期间的临时屏蔽:
# 2 小时维护窗口
amtool silence add \
--author="ops-team" \
--comment="数据库维护窗口 2026-06-10 02:00-04:00" \
--duration=2h \
alertname=~"Database.*" \
cluster="prod-cn-east"
amtool silence query
amtool silence expire <silence-id>通过 API 创建静默:
curl -X POST http://alertmanager:9093/api/v2/silences \
-H "Content-Type: application/json" \
-d '{
"matchers": [{"name": "service", "value": "payment", "isRegex": false}],
"startsAt": "2026-06-10T02:00:00Z",
"endsAt": "2026-06-10T04:00:00Z",
"createdBy": "ops-bot",
"comment": "支付服务计划维护"
}'标签规范化避免同类告警分裂:
alerting:
alert_relabel_configs:
- source_labels: [instance]
regex: '(.+):\d+'
target_label: instance
- action: labeldrop
regex: 'request_id|trace_id'规则本身保持克制:设置明确的 for 时长避免瞬时抖动误报,抑制与聚合规则定期 review,删除反复触发却无行动的规则。过宽的抑制可能隐藏独立事件,因此用两个同时发生的故障检查规则。
路由树与通知渠道
路由树决定“什么样的告警发给谁”:
global:
resolve_timeout: 5m
smtp_from: 'alertmanager@company.com'
smtp_smarthost: 'smtp.company.com:587'
smtp_auth_username: 'alertmanager@company.com'
smtp_auth_password: '***'
slack_api_url: 'https://hooks.slack.com/services/xxx'
route:
receiver: 'default-webhook'
group_by: ['alertname', 'cluster', 'namespace']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- matchers:
- severity = "critical"
receiver: 'p0-pager'
group_wait: 10s
group_interval: 1m
repeat_interval: 5m
routes:
- matchers:
- team = "dba"
receiver: 'dba-oncall'
- matchers:
- team = "network"
receiver: 'network-oncall'
- matchers:
- severity = "warning"
receiver: 'p1-webhook'
group_wait: 1m
repeat_interval: 1h
- matchers:
- severity =~ "info|none"
receiver: 'low-priority-email'
repeat_interval: 12h
receivers:
- name: 'default-webhook'
webhook_configs:
- url: 'http://alert-gateway:8080/webhook'
send_resolved: true
- name: 'p0-pager'
webhook_configs:
- url: 'http://alert-gateway:8080/webhook?level=p0'
slack_configs:
- channel: '#alert-critical'
title: ' P0 告警'
text: '{{ range .Alerts }}{{ .Annotations.summary }}\n{{ end }}'
email_configs:
- to: 'oncall@company.com'
headers:
Subject: '[P0] {{ .GroupLabels.alertname }}'
- name: 'p1-webhook'
webhook_configs:
- url: 'http://alert-gateway:8080/webhook?level=p1'
- name: 'low-priority-email'
email_configs:
- to: 'ops@company.com'
- name: 'dba-oncall'
webhook_configs:
- url: 'http://alert-gateway:8080/webhook?team=dba'
- name: 'network-oncall'
webhook_configs:
- url: 'http://alert-gateway:8080/webhook?team=network'按维度分组实现精细化路由:
route:
routes:
- matchers:
- category = "infrastructure"
group_by: ['cluster', 'env']
receiver: 'infra-oncall'
- matchers:
- category = "application"
group_by: ['service', 'namespace']
receiver: 'app-oncall'
- matchers:
- category = "business"
group_by: ['business_line']
receiver: 'biz-oncall'通知模板 /etc/alertmanager/templates/custom.tmpl:
{{ define "custom.title" }}
[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}]
{{ .GroupLabels.SortedPairs.Values | join " " }}
{{ end }}
{{ define "custom.text" }}
{{ range .Alerts }}
====
**告警名称**: {{ .Labels.alertname }}
**严重程度**: {{ .Labels.severity }}
**实例**: {{ .Labels.instance }}
**集群**: {{ .Labels.cluster }}
**状态**: {{ .Status }}
**开始时间**: {{ .StartsAt.Format "2006-01-02 15:04:05" }}
{{ if .EndsAt }}**恢复时间**: {{ .EndsAt.Format "2006-01-02 15:04:05" }}{{ end }}
**描述**: {{ .Annotations.description }}
{{ if .Annotations.runbook }}**Runbook**: {{ .Annotations.runbook }}{{ end }}
{{ end }}
====
**聚合标签**: {{ .GroupLabels.SortedPairs.Values | join " / " }}
{{ end }}引用模板与邮件接收器:
templates:
- '/etc/alertmanager/templates/*.tmpl'
receivers:
- name: 'mail-critical'
email_configs:
- to: 'oncall@example.com'
headers:
Subject: '[{{ .GroupLabels.alertname }}] {{ .Status | toUpper }}'邮件发送可以用 amtool 触发测试告警:
amtool alert add alertname=TestAlert severity=warning instance=localhost:9090 \
--annotation=summary="测试告警" \
--annotation=description="这是一条测试告警"飞书自定义机器人(脚本读取 Alertmanager Webhook 后转为卡片消息):
cat > feishu-webhook.py << 'EOF'
import json, requests, sys
def send_feishu_alert(webhook_url, alert_data):
for alert in alert_data.get('alerts', []):
status = alert.get('status', 'firing')
severity = alert.get('labels', {}).get('severity', 'unknown')
card = {
"msg_type": "interactive",
"card": {
"header": {
"title": {
"tag": "plain_text",
"content": f"[{severity.upper()}] {alert['labels'].get('alertname', 'Unknown')}"
},
"template": "red" if severity == "critical" else "orange" if severity == "warning" else "blue"
},
"elements": [
{
"tag": "div",
"fields": [
{"is_short": True, "text": {"tag": "lark_md", "content": f"**实例**\n{alert['labels'].get('instance', 'N/A')}"}},
{"is_short": True, "text": {"tag": "lark_md", "content": f"**集群**\n{alert['labels'].get('cluster', 'N/A')}"}},
{"is_short": True, "text": {"tag": "lark_md", "content": f"**状态**\n{status}"}},
{"is_short": True, "text": {"tag": "lark_md", "content": f"**时间**\n{alert.get('startsAt', '')[:19]}"}}
]
},
{
"tag": "div",
"text": {"tag": "lark_md", "content": f"**描述**: {alert.get('annotations', {}).get('description', 'N/A')}"}
},
{
"tag": "action",
"actions": [
{
"tag": "button",
"text": {"tag": "plain_text", "content": " Runbook"},
"url": alert.get('annotations', {}).get('runbook', '#'),
"type": "primary"
}
]
}
]
}
}
requests.post(webhook_url, json=card, timeout=5)
print(f"Sent alert: {alert['labels'].get('alertname')}")
if __name__ == '__main__':
webhook_url = sys.argv[1]
data = json.load(sys.stdin)
send_feishu_alert(webhook_url, data)
EOFAlertmanager 中接飞书:
receivers:
- name: 'feishu-alerts'
webhook_configs:
- url: 'http://localhost:9095/alertmanager/feishu'
send_resolved: true钉钉通过 prometheus-webhook-dingtalk 中间件转发:
docker run -d --name dingtalk-hook \
-p 8060:8060 \
timonwong/prometheus-webhook-dingtalk \
--ding.profile="ops=https://oapi.dingtalk.com/robot/send?access_token=YOUR_TOKEN"targets:
ops:
url: https://oapi.dingtalk.com/robot/send?access_token=xxxx
secret: SECxxxxxxxx
message:
title: '{{ template "ding.link.title" . }}'
text: '{{ template "ding.link.content" . }}'Alertmanager 中接钉钉:
receivers:
- name: 'dingtalk-ops'
webhook_configs:
- url: 'http://dingtalk-hook:8060/dingtalk/ops/send'企业微信:
cat > wecom-webhook.py << 'EOF'
import json, requests, sys
WEBHOOK_BASE = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={key}"
def send_wecom(key, alert_data):
webhook_url = WEBHOOK_BASE.format(key=key)
for alert in alert_data.get('alerts', []):
status = alert['status']
labels = alert.get('labels', {})
annotations = alert.get('annotations', {})
content = f"""## {' 告警触发' if status == 'firing' else ' 告警恢复'}
> **告警名称**: {labels.get('alertname', 'N/A')}
> **严重程度**: {labels.get('severity', 'N/A')}
> **实例**: {labels.get('instance', 'N/A')}
> **集群**: {labels.get('cluster', 'N/A')}
> **时间**: {alert.get('startsAt', '')[:19]}
> **描述**: {annotations.get('description', 'N/A')}
---
[ 查看Runbook]({annotations.get('runbook', '#')})"""
payload = {"msgtype": "markdown", "markdown": {"content": content}}
resp = requests.post(webhook_url, json=payload, timeout=5)
print(f"WeCom response: {resp.json()}")
if __name__ == '__main__':
send_wecom(sys.argv[1], json.load(sys.stdin))
EOFSlack:
receivers:
- name: 'slack-ops'
slack_configs:
- api_url: 'https://hooks.slack.com/services/T00/B00/xxx'
channel: '#ops-alerts'
title: '{{ template "slack.title" . }}'
text: '{{ template "slack.text" . }}'
color: '{{ if eq .Status "firing" }}danger{{ else }}good{{ end }}'
send_resolved: true
actions:
- type: button
text: 'Silence'
url: '{{ template "slack.silenceURL" . }}'
- type: button
text: 'Runbook'
url: '{{ (index .Alerts 0).Annotations.runbook }}'通用 Webhook 可对接任意自定义系统:
receivers:
- name: 'universal-webhook'
webhook_configs:
- url: 'http://alert-gateway:8080/api/v1/alerts'
send_resolved: true
http_config:
authorization:
type: Bearer
credentials: 'your-api-token'
follow_redirects: true
max_alerts: 50接收端示例(Go):
cat > webhook-gateway.go << 'GOEOF'
package main
import (
"encoding/json"
"log"
"net/http"
"time"
)
type AlertGroup struct {
Status string `json:"status"`
Alerts []Alert `json:"alerts"`
GroupLabels map[string]string `json:"groupLabels"`
CommonLabels map[string]string `json:"commonLabels"`
}
type Alert struct {
Status string `json:"status"`
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
StartsAt time.Time `json:"startsAt"`
EndsAt time.Time `json:"endsAt"`
}
func alertHandler(w http.ResponseWriter, r *http.Request) {
var group AlertGroup
if err := json.NewDecoder(r.Body).Decode(&group); err != nil {
http.Error(w, err.Error(), 400)
return
}
for _, alert := range group.Alerts {
log.Printf("[%s] %s - %s", alert.Status,
alert.Labels["alertname"],
alert.Annotations["summary"])
}
w.WriteHeader(200)
}
func main() {
http.HandleFunc("/api/v1/alerts", alertHandler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
GOEOF通知渠道验收:机器人创建成功只代表配置存在。用受控测试告警检查触发、分组、发送、接收与恢复通知;内部进程和资源指标解释失败原因,外部探测反映访问路径是否可用,也要验证监控系统自身断采时能被发现。不要依靠缺失数据自动解释为零错误。
On-Call 与告警治理
轻量轮值排班可用脚本按天或按周轮转,复杂场景建议 PagerDuty / OpsGenie 或自建排班系统。
#!/bin/bash
# oncall-scheduler.sh - 每日值班通知脚本
# 放入 crontab: 0 9 * * * /opt/scripts/oncall-scheduler.sh
DATE=$(date +%Y-%m-%d)
PRIMARY_ONCALL=("张三" "李四" "王五" "赵六")
SECONDARY_ONCALL=("高级A" "高级B" "高级C")
# 按周轮转一线,按天轮转二线
PRIMARY_IDX=$(( ($(date +%s) / 86400 / 7) % ${#PRIMARY_ONCALL[@]} ))
SECONDARY_IDX=$(( ($(date +%s) / 86400) % ${#SECONDARY_ONCALL[@]} ))
PRIMARY=${PRIMARY_ONCALL[$PRIMARY_IDX]}
SECONDARY=${SECONDARY_ONCALL[$SECONDARY_IDX]}
curl -s -X POST "$FEISHU_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{
\"msg_type\": \"interactive\",
\"card\": {
\"header\": {\"title\": {\"tag\": \"plain_text\", \"content\": \" 今日值班通知 - $DATE\"}, \"template\": \"green\"},
\"elements\": [{\"tag\": \"div\", \"text\": {\"tag\": \"lark_md\", \"content\": \"**一线值班**: $PRIMARY\n**二线值班**: $SECONDARY\"}}]
}
}"
echo "[$(date)] On-call notification sent: Primary=$PRIMARY, Secondary=$SECONDARY"值班原则:每班次至少两人(主和备),交接时同步未关闭告警和进行中的事件,记录所有告警处理过程,轮值周期不超过一周;避免同一个人连续值班超过七天、P0 只通知一个人、没有备份值班人员或在值班期间做高风险变更。
告警质量审计:
#!/bin/bash
# alert-audit.sh - 告警质量审计脚本
PROMETHEUS_URL="http://prometheus:9090"
echo "=== 告警质量月度审计 ==="
echo ""
echo "--- 告警规则统计 ---"
total_rules=$(curl -s "$PROMETHEUS_URL/api/v1/rules" | jq '.data.groups[].rules | length' | awk '{sum+=$1} END{print sum}')
echo "总规则数: $total_rules"
echo ""
echo "--- 高频告警 Top 20(过去30天)---"
curl -s "$PROMETHEUS_URL/api/v1/query" \
--data-urlencode 'query=topk(20, changes(ALERTS_FOR_STATE[30d]))' \
| jq -r '.data.result[] | "\(.metric.alertname) | \(.value[1]) 次 | severity=\(.metric.severity // "N/A")"'
echo ""
echo "--- 冷却告警(30天未触发,考虑清理)---"
curl -s "$PROMETHEUS_URL/api/v1/rules" \
| jq -r '.data.groups[].rules[] | select(.type=="alerting") | .name' \
| sort > /tmp/all_alerts.txt
echo ""
echo "--- 告警响应率(近30天)---"
echo "请从告警管理平台导出数据计算"告警生命周期:创建 → 审核(同行评审、Runbook 必备、分级标注)→ 上线 → 运营(季度审计,30 天未触发考虑清理,误报率超 50% 优化规则)→ 退役归档。
季度审计清单:每条告警有明确的 severity 标签与 annotations;路由树覆盖所有 team 标签、默认 receiver 已配置;通知模板与值班名单更新;Runbook 经过验证;上月复盘后的改进措施已落地。
推荐按文件组织规则:
/etc/prometheus/rules/
├── 00-global.rules.yml # 全局基础告警
├── 10-node.rules.yml # 节点级告警
├── 20-container.rules.yml # 容器/K8s 告警
├── 30-middleware.rules.yml # 中间件告警(DB/Redis/MQ)
├── 40-application.rules.yml # 应用级告警
├── 50-business.rules.yml # 业务指标告警
├── 60-slo.rules.yml # SLA/SLO 相关告警
├── 90-recording.rules.yml # Recording rules
└── 99-test.rules.yml # 测试规则(不带入生产)联动部署:
# docker-compose.alerting.yml
services:
prometheus:
image: prom/prometheus:v2.52.0
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- ./prometheus/rules:/etc/prometheus/rules
- prometheus_data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.retention.time=30d'
- '--web.enable-lifecycle'
restart: unless-stopped
alertmanager:
image: prom/alertmanager:v0.27.0
ports:
- "9093:9093"
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml
- ./alertmanager/templates:/etc/alertmanager/templates
- alertmanager_data:/alertmanager
command:
- '--config.file=/etc/alertmanager/alertmanager.yml'
- '--storage.path=/alertmanager'
- '--cluster.listen-address=0.0.0.0:9094'
restart: unless-stopped
dingtalk-adapter:
image: timonwong/prometheus-webhook-dingtalk:latest
ports:
- "8060:8060"
volumes:
- ./dingtalk/config.yml:/etc/prometheus-webhook-dingtalk/config.yml
restart: unless-stopped
volumes:
prometheus_data:
alertmanager_data:告警响应 Runbook:CPU 持续高占用
先记录告警实例、时间窗与业务影响;CPU 阈值只是调查入口。以下命令在已确认的目标主机执行,PID 需要从进程列表中选择。
uptime
top -bn1 | head -20
mpstat -P ALL 1 5
ps aux --sort=-%cpu | head -10进一步检查目标进程的线程与调用栈。采样工具、权限和开销需要事先确认;发现高 CPU 时保留证据,避免直接重启丢失现场。
: "${PID:?设置已确认的目标进程 PID}"
top -H -p "$PID"对照请求量、慢查询、GC 和进程来源判断可能原因。临时限流或限制 CPU 前核对资源单位、目标服务和恢复动作,并观察是否把 CPU 压力转成排队超时。处置后验证请求延迟、错误率及任务完成情况。升级给其他负责人时附已收集证据、已排除方向和当前影响;具体响应时限由值班策略确定。
二、日志、链路追踪与 APM
日志采集与存储统一维护在日志收集与分析的 Alloy 章节,Promtail 已于 2026-03-02 结束支持。指标中保留有限标签,单次请求通过 trace ID 或请求 ID 关联日志;先核对时间同步和实例标识,再解释趋势。本章集中介绍链路追踪与应用性能管理。
链路追踪基础
分布式追踪的核心是:在请求经过的每个服务中埋点,记录调用关系和耗时,把这些片段拼接成一条完整的调用链。
工作流程:
- 请求到达入口服务时,生成全局唯一的 Trace ID
- 每个服务处理请求时创建一个 Span(跨度),记录操作名、开始/结束时间、状态
- Span 之间通过 Parent-Span ID 形成树状结构
- 所有 Span 上报到后端存储,拼接成完整的 Trace
Trace 是一个完整请求的全链路记录,由多个 Span 组成;Span 是一个逻辑操作单元,包含 traceId、spanId、parentSpanId、operationName、startTime / endTime、status(OK / ERROR / UNSET)、attributes、events 和 links。
一个典型 Trace 结构:
Trace ID: abc123
├── Span A: API Gateway (0-200ms)
│ ├── Span B: User Service (10-150ms)
│ │ └── Span C: MySQL Query (20-80ms)
│ └── Span D: Order Service (30-180ms)
│ ├── Span E: Redis Cache (40-50ms)
│ └── Span F: RabbitMQ Publish (60-70ms)OpenTracing 与 OpenTelemetry 对比:
| 特性 | OpenTracing | OpenTelemetry |
|---|---|---|
| 定义 | 追踪 API 规范 | 统一的可观测性框架 |
| 范围 | 仅追踪 | 追踪 + 指标 + 日志 |
| 状态 | 已归档,合并入 OTel | CNCF 活跃项目,事实标准 |
| 厂商锁定 | 无(仅 API) | 无(API + SDK + Collector) |
OpenTelemetry(OTel)由 OpenTracing 和 OpenCensus 合并而来,被所有主流云厂商和 APM 工具支持。
OpenTelemetry 安装
架构分三层:应用层(Trace/Metrics/Logs 的 API、SDK 与 Exporter)、Collector(Receiver → Processor → Exporter)、后端存储(Jaeger / Tempo / Prometheus / Loki)。Collector 有 Core 与 Contrib 两种发行版,推荐 Contrib。
docker run -d --name otel-collector \
-p 4317:4317 \
-p 4318:4318 \
-p 8888:8888 \
-p 8889:8889 \
-v $(pwd)/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml \
otel/opentelemetry-collector-contrib:latest配置文件 otel-collector-config.yaml:
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
jaeger:
protocols:
grpc:
endpoint: 0.0.0.0:14250
thrift_http:
endpoint: 0.0.0.0:14268
zipkin:
endpoint: 0.0.0.0:9411
processors:
batch:
timeout: 5s
send_batch_size: 1024
memory_limiter:
check_interval: 1s
limit_mib: 512
spike_limit_mib: 128
resource:
attributes:
- key: environment
value: production
action: upsert
key: team
value: platform
action: upsert
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
prometheus:
endpoint: 0.0.0.0:8889
namespace: otel
debug:
verbosity: basic
extensions:
health_check:
endpoint: 0.0.0.0:13133
zpages:
endpoint: 0.0.0.0:55679
service:
extensions: [health_check, zpages]
pipelines:
traces:
receivers: [otlp, jaeger, zipkin]
processors: [memory_limiter, resource, batch]
exporters: [otlp/jaeger, debug]
metrics:
receivers: [otlp]
processors: [memory_limiter, resource, batch]
exporters: [prometheus]验证:curl http://localhost:13133 返回 OK,且应用上报后能在 Jaeger 查到对应按服务、时间过滤的 Trace。
Jaeger 部署与使用
本地验证用 All-in-One 镜像,存储换成 Elasticsearch:
version: "3.9"
services:
jaeger:
image: jaegertracing/all-in-one:1.58
environment:
COLLECTOR_OTLP_ENABLED: "true"
SPAN_STORAGE_TYPE: "elasticsearch"
ES_SERVER_URLS: "http://elasticsearch:9200"
ES_INDEX_PREFIX: "jaeger"
ES_TAGS_AS_FIELDS_ALL: "true"
ports:
- "16686:16686" # UI
- "4317:4317" # OTLP gRPC
- "4318:4318" # OTLP HTTP
- "14250:14250" # gRPC
- "14268:14268" # HTTP Thrift
depends_on:
- elasticsearch
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.14.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
volumes:
- es-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
volumes:
es-data:生产环境拆成独立组件:
# 1. Elasticsearch 集群(推荐 3 节点以上)
# 2. 分别部署 collect/query/ingester
jaeger-collector \
--es.server-urls=http://es1:9200,es2:9200,es3:9200 \
--es.index-jaeger-spans-days=7 \
--es.index-jaeger-services-days=7 \
--es.index-jaeger-dependencies-days=7 \
--es.max-span-age=168h0m0s \
--collector.otlp.enabled=true
jaeger-query \
--es.server-urls=http://es1:9200,es2:9200,es3:9200
jaeger-ingester \
--kafka.consumer.brokers=kafka:9092 \
--kafka.consumer.topic=jaeger-spans \
--es.server-urls=http://es1:9200,es2:9200,es3:9200组件职责:
| 组件 | 作用 |
|---|---|
| Agent | 部署在每台主机上,接收应用的 Span,转发给 Collector |
| Collector | 接收、验证、处理、存储 Span 到后端存储 |
| Ingester | 从 Kafka 消费 Span,写入存储(可选) |
| Query | 提供查询 API 和 UI |
| Storage | 后端存储:Elasticsearch / Cassandra / Badger |
采样策略配置文件 sampling-strategies.json:
{
"default_strategy": {
"type": "probabilistic",
"param": 0.1
},
"service_strategies": [
{
"service": "payment-service",
"type": "rateLimiting",
"param": 100
},
{
"service": "gateway",
"type": "probabilistic",
"param": 0.5,
"operation_strategies": [
{
"operation": "/health",
"type": "probabilistic",
"param": 0.01
}
]
}
]
}采样类型:probabilistic 按比例随机采样(0.1 表示 10%);rateLimiting 限制每秒采样数;remote 由后端远程控制采样率。
UI 核心能力:按服务名、操作名、时间范围、标签、Duration 筛选;火焰图/甘特图查看调用链;选择两个 Trace 做 Diff 定位性能退化;依赖图自动绘制服务间调用拓扑;Monitor 基于 Trace 数据做 RED 指标监控。
Jaeger 与 Zipkin:新项目优先 Jaeger(原生 OTLP、Go 实现、支持远程动态采样),已有 Zipkin 基础设施的可继续使用。
SkyWalking 部署与接入
Java 体系零侵入接入时常用 SkyWalking。OAP 加 UI 加 Elasticsearch 的 Compose 部署:
version: "3.9"
services:
oap:
image: apache/skywalking-oap-server:9.7.0
environment:
SW_STORAGE: elasticsearch
SW_STORAGE_ES_CLUSTER_NODES: elasticsearch:9200
SW_HEALTH_CHECKER: default
SW_TELEMETRY: prometheus
ports:
- "11800:11800" # gRPC
- "12800:12800" # REST
- "1234:1234" # Agent gRPC
depends_on:
- elasticsearch
ui:
image: apache/skywalking-ui:9.7.0
environment:
SW_OAP_ADDRESS: http://oap:12800
ports:
- "8080:8080"
depends_on:
- oap
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.14.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms1g -Xmx1g"
volumes:
- sw-es-data:/usr/share/elasticsearch/data
ports:
- "9200:9200"
volumes:
sw-es-data:Java Agent 配置(命令行方式):
wget https://dlcdn.apache.org/skywalking/java-agent/9.1.0/apache-skywalking-java-agent-9.1.0.tgz
tar -xzf apache-skywalking-java-agent-9.1.0.tgz
java -javaagent:/path/to/skywalking-agent/skywalking-agent.jar \
-Dskywalking.agent.service_name=my-service \
-Dskywalking.collector.backend_service=oap-host:11800 \
-Dskywalking.agent.sample_n_per_3_secs=10 \
-Dskywalking.agent.ignore_suffix=".jpg,.css,.js,.png" \
-Dskywalking.agent.trace.ignore_path="/health,/metrics,/actuator/**" \
-jar my-app.jar配置文件方式 agent/config/agent.config(推荐):
agent.service_name=${SW_AGENT_NAME:my-service}
collector.backend_service=${SW_AGENT_COLLECTOR_BACKEND_SERVICES:oap-host:11800}
agent.sample_n_per_3_secs=${SW_AGENT_SAMPLE_N_PER_3_SECS:10}
agent.ignore_suffix=${SW_IGNORE_SUFFIX:.jpg,.css,.js,.png,.ico}
agent.snapshot.depth=${SW_AGENT_SNAPSHOT_DEPTH:5}
agent.cross_thread_propagation=${SW_AGENT_CROSS_THREAD:true}OAP 内置告警规则 config/alarm-settings.yml:
rules:
service_resp_time_rule:
metrics-name: service_resp_time
op: ">"
threshold: 2000
period: 10
count: 3
silence-period: 5
message: "服务 {name} 响应时间超过 2 秒(最近 10 分钟内触发 3 次)"
service_sla_rule:
metrics-name: service_sla
op: "<"
threshold: 8000
period: 10
count: 2
silence-period: 5
message: "服务 {name} 可用性低于 80%"
endpoint_resp_time_rule:
metrics-name: endpoint_avg
op: ">"
threshold: 3000
period: 10
count: 3
silence-period: 5
message: "端点 {name} 平均响应时间超过 3 秒"
database_access_resp_time_rule:
metrics-name: database_access_resp_time
op: ">"
threshold: 1000
period: 10
count: 3
silence-period: 5
message: "数据库 {name} 响应时间超过 1 秒"
webhooks:
- url: http://alertmanager:9095/api/v1/alerts
receivers:
- platform-team@company.com自定义埋点:
import org.apache.skywalking.apm.toolkit.trace.Tag;
import org.apache.skywalking.apm.toolkit.trace.Trace;
import org.apache.skywalking.apm.toolkit.trace.ActiveSpan;
public class OrderService {
@Trace(operationName = "createOrder")
@Tag(key = "orderId", value = "arg[0]")
public Order createOrder(String orderId, OrderRequest request) {
ActiveSpan.tag("userId", request.getUserId());
ActiveSpan.tag("amount", String.valueOf(request.getAmount()));
ActiveSpan.info("开始创建订单");
try {
Order order = processOrder(orderId, request);
ActiveSpan.tag("orderStatus", order.getStatus().name());
return order;
} catch (Exception e) {
ActiveSpan.error(e);
ActiveSpan.tag("error", e.getMessage());
throw e;
}
}
public void asyncProcess(Order order) {
Runnable task = RunnableWrapper.of(() -> {
ActiveSpan.tag("async", "true");
doAsyncWork(order);
});
executor.submit(task);
}
}应用性能指标:RED、USE 与四个黄金信号
RED 方法用于面向请求的服务监控:
| 指标 | 含义 | 计算方式 |
|---|---|---|
| Rate | 请求速率 | 每秒请求数(QPS/RPS) |
| Errors | 错误率 | 错误请求数 / 总请求数 × 100% |
| Duration | 延迟 | 请求耗时(通常关注 P50/P95/P99) |
# 请求速率
rate(http_server_requests_seconds_count{service="order-service"}[5m])
# 错误率
sum(rate(http_server_requests_seconds_count{status=~"5..", service="order-service"}[5m]))
/
sum(rate(http_server_requests_seconds_count{service="order-service"}[5m]))
# P99 延迟
histogram_quantile(0.99, rate(http_server_requests_seconds_bucket{service="order-service"}[5m]))USE 方法用于基础设施资源监控:
| 指标 | 含义 | 示例 |
|---|---|---|
| Utilization | 利用率 | CPU 使用率、内存使用率 |
| Saturation | 饱和度 | 运行队列长度、磁盘 I/O 队列 |
| Errors | 错误数 | 磁盘错误、网络丢包 |
mpstat -P ALL 1 # CPU 利用率
vmstat 1 # 内存饱和度(swap 使用)
iostat -xz 1 # 磁盘利用率
netstat -i # 网络错误四个黄金信号(Google SRE):延迟(Latency)对应 RED 的 Duration,流量(Traffic)对应 RED 的 Rate,错误(Errors)对应 RED 的 Errors,饱和度(Saturation)对应 USE 的 Saturation。服务层面用 RED,基础设施层面用 USE,整体架构层面关注四个黄金信号。
Java 应用集成
Spring Boot + OpenTelemetry
方式一:Java Agent,零代码修改。
# docker-compose.yml
services:
app:
image: my-spring-boot-app:latest
environment:
JAVA_TOOL_OPTIONS: "-javaagent:/app/otel-agent.jar"
OTEL_SERVICE_NAME: order-service
OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4317
OTEL_TRACES_SAMPLER: parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG: "0.1"
OTEL_LOGS_EXPORTER: none
OTEL_METRICS_EXPORTER: prometheus
volumes:
- ./opentelemetry-javaagent.jar:/app/otel-agent.jar方式二:Spring Boot Starter,细粒度控制。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.40.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
<version>2.6.0-alpha</version>
</dependency>
</dependencies># application.yml
otel:
service:
name: order-service
exporter:
otlp:
endpoint: http://otel-collector:4317
traces:
sampler: parentbased_traceidratio
sampler-arg: 0.1
instrumentations:
spring-webmvc:
enabled: true
jdbc:
enabled: true
logback-appender:
enabled: trueSkyWalking Agent + Spring Boot
java -javaagent:/opt/skywalking/agent/skywalking-agent.jar \
-Dskywalking.agent.service_name=order-service \
-Dskywalking.collector.backend_service=skywalking-oap:11800 \
-jar order-service.jarDocker/K8s 用环境变量配置,并用 Init Container 注入 Agent:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
template:
spec:
initContainers:
- name: skywalking-agent
image: apache/skywalking-java-agent:9.1.0
command: ['sh', '-c', 'cp -r /agent /skywalking-agent']
volumeMounts:
- name: sw-agent
mountPath: /skywalking-agent
containers:
- name: app
image: order-service:latest
env:
- name: JAVA_TOOL_OPTIONS
value: "-javaagent:/skywalking-agent/skywalking-agent.jar"
- name: SW_AGENT_NAME
value: "order-service"
- name: SW_AGENT_COLLECTOR_BACKEND_SERVICES
value: "skywalking-oap:11800"
volumeMounts:
- name: sw-agent
mountPath: /skywalking-agent
volumes:
- name: sw-agent
emptyDir: {}Node.js 应用集成
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpc \
@opentelemetry/exporter-metrics-otlp-grpc \
@opentelemetry/resources \
@opentelemetry/semantic-conventions创建 tracing.ts,必须在应用代码之前加载:
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-grpc';
import { OTLPMetricExporter } from '@opentelemetry/exporter-metrics-otlp-grpc';
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { Resource } from '@opentelemetry/resources';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
const sdk = new NodeSDK({
resource: new Resource({
[ATTR_SERVICE_NAME]: process.env.OTEL_SERVICE_NAME || 'node-service',
[ATTR_SERVICE_VERSION]: '1.0.0',
'deployment.environment': process.env.NODE_ENV || 'development',
}),
traceExporter: new OTLPTraceExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317',
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: process.env.OTEL_EXPORTER_OTLP_ENDPOINT || 'http://localhost:4317',
}),
exportIntervalMillis: 15000,
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': {
ignoreIncomingPaths: ['/health', '/metrics', '/favicon.ico'],
},
'@opentelemetry/instrumentation-express': {
enabled: true,
},
'@opentelemetry/instrumentation-redis': {
enabled: true,
},
'@opentelemetry/instrumentation-mysql2': {
enabled: true,
},
}),
],
});
sdk.start();
process.on('SIGTERM', () => sdk.shutdown());启动方式:
# 使用 --require 预加载
node --require ./tracing.js app.js
# 或者在入口文件最顶部
import './tracing';
import express from 'express';手动创建 Span:
import { trace, SpanStatusCode } from '@opentelemetry/api';
const tracer = trace.getTracer('order-service', '1.0.0');
async function createOrder(orderData: OrderRequest): Promise<Order> {
return tracer.startActiveSpan('createOrder', async (span) => {
try {
span.setAttribute('order.userId', orderData.userId);
span.setAttribute('order.amount', orderData.amount);
span.addEvent('validate_order', { 'order.items': orderData.items.length });
const order = await processOrder(orderData);
span.setAttribute('order.id', order.id);
span.setStatus({ code: SpanStatusCode.OK });
return order;
} catch (error) {
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
span.recordException(error);
throw error;
} finally {
span.end();
}
});
}装饰器方式需要配合提供 @Trace 装饰器的第三方库,按所选库的实际导出导入:
class OrderService {
@Trace('processPayment')
async processPayment(orderId: string, amount: number): Promise<PaymentResult> {
const result = await this.paymentGateway.charge(orderId, amount);
return result;
}
}Node.js 自动埋点支持:http / https(状态码、URL、耗时),express / koa / fastify / hapi(路由、中间件耗时),pg / mysql2 / mongodb / redis(SQL/命令、耗时),grpc-js(方法名、状态码),kafkajs / amqplib(Topic、消息数),undici(HTTP 客户端)。
追踪数据分析
延迟分析关注:最长 Span(耗时最大的操作)、串行调用(可并行的调用被串行执行)、重复调用(同服务被多次调用,N+1 问题)、Span 之间的等待时间(可能来自排队或 GC)。
# jaeger-query API 查询慢请求
curl -s "http://jaeger:16686/api/traces?service=order-service&minDuration=2s&limit=20" \
| jq '.data[] | {traceID, spans: [.spans[] | {operationName, duration}]}'
# 查询包含错误的 Trace
curl -s "http://jaeger:16686/api/traces?service=order-service&tags=%7B%22error%22%3A%22true%22%7D&limit=20"P99 延迟按服务维度分析:
histogram_quantile(0.99,
sum by (le, service) (
rate(traces_spanmetrics_latency_bucket[5m])
)
)
# 延迟 Top 10 操作
topk(10,
histogram_quantile(0.99,
sum by (le, operation) (
rate(traces_spanmetrics_latency_bucket[5m])
)
)
)错误分析检查清单:查看 Span 的 status 确认 ERROR;检查 events 中的异常堆栈;关注 attributes 中的 HTTP 状态码(4xx/5xx);对比正常与异常 Trace 的差异。
依赖拓扑:
# Jaeger 依赖图 API
curl -s "http://jaeger:16686/api/dependencies?endTs=$(date +%s)000&lookback=86400000"
# SkyWalking 拓扑图 API
curl -s "http://skywalking:12800/api/topology/service?duration.start=2024-01-01&duration.end=2024-01-02&duration.step=MINUTE"分析要点:扇出比(一个服务依赖多少下游)、调用频率(哪些依赖调用量最大)、错误传播(错误从哪个节点开始扩散)。
性能瓶颈识别流程:
1. 找到慢 Trace → 查看火焰图
2. 识别最慢的 Span → 确定是 CPU/IO/网络/锁
3. 查看该 Span 的 attributes:
- db.statement → SQL 慢查询
- http.url → 外部 API 慢
- net.peer.name → 网络延迟
4. 关联基础设施指标:
- CPU 高 → 计算密集
- IO 高 → 数据库/磁盘瓶颈
- 网络延迟 → 跨区/跨机房调用
5. 对比历史数据 → 确认是新问题还是老问题Trace 指标与告警集成
OpenTelemetry Collector 的 spanmetrics processor 可以把 Trace 数据转成 Prometheus 指标:
processors:
spanmetrics:
metrics_exporter: prometheus
latency_histogram_buckets: [5ms, 10ms, 25ms, 50ms, 100ms, 250ms, 500ms, 1s, 2.5s, 5s, 10s]
dimensions:
- name: http.method
- name: http.status_code
- name: db.system
exporters:
prometheus:
endpoint: 0.0.0.0:8889
namespace: otel
const_labels:
environment: production
service:
pipelines:
traces:
receivers: [otlp]
processors: [spanmetrics, batch]
exporters: [jaeger]
metrics:
receivers: [spanmetrics]
exporters: [prometheus]生成的关键指标:otel_traces_spanmetrics_latency_bucket(请求总耗时直方图)、otel_traces_spanmetrics_calls_total(请求总数)、otel_traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}(错误数)。
一个按操作名分组的 Grafana 面板可以使用:
# Request Rate (QPS)
sum(rate(otel_traces_spanmetrics_calls_total{service_name="$service"}[5m])) by (operation)
# Error Rate
sum(rate(otel_traces_spanmetrics_calls_total{service_name="$service", status_code="STATUS_CODE_ERROR"}[5m]))
/ sum(rate(otel_traces_spanmetrics_calls_total{service_name="$service"}[5m]))
# P99 Latency
histogram_quantile(0.99, sum(rate(otel_traces_spanmetrics_latency_bucket{service_name="$service"}[5m])) by (le, operation))告警规则:
groups:
- name: tracing-alerts
rules:
- alert: HighP99Latency
expr: |
histogram_quantile(0.99,
sum(rate(otel_traces_spanmetrics_latency_bucket[5m])) by (le, service_name)
) > 3
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.service_name }} P99 延迟超过 3 秒"
description: "当前 P99 延迟: {{ $value }}s"
- alert: HighErrorRate
expr: |
sum(rate(otel_traces_spanmetrics_calls_total{status_code="STATUS_CODE_ERROR"}[5m])) by (service_name)
/
sum(rate(otel_traces_spanmetrics_calls_total[5m])) by (service_name)
> 0.05
for: 3m
labels:
severity: critical
annotations:
summary: "{{ $labels.service_name }} 错误率超过 5%"
description: "当前错误率: {{ $value | humanizePercentage }}"
- alert: TrafficSpike
expr: |
sum(rate(otel_traces_spanmetrics_calls_total[5m])) by (service_name)
> 3 * sum(rate(otel_traces_spanmetrics_calls_total[1h] offset 1d)) by (service_name)
for: 5m
labels:
severity: warning
annotations:
summary: "{{ $labels.service_name }} 请求量异常增长"采样、上下文传播与性能开销
全量采集适用于开发/测试环境(100%);概率采样适合流量稳定的生产环境(1%-10%);速率限制适合高流量服务(100-1000 条/秒);尾部采样保留完整错误链(错误 100%,正常 1%);基于规则按服务/操作差异化。
Collector 端尾部采样:
processors:
tail_sampling:
decision_wait: 10s
num_traces: 100000
expected_new_traces_per_sec: 1000
policies:
- name: errors
type: status_code
status_code:
status_codes: [ERROR]
- name: slow-requests
type: latency
latency:
threshold_ms: 3000
- name: payment-service
type: string_attribute
string_attribute:
key: service.name
values: [payment-service]
- name: probabilistic
type: probabilistic
probabilistic:
sampling_percentage: 5上下文传播使用 W3C Trace Context 标准:
# HTTP Header
traceparent: 00-<trace-id>-<span-id>-<trace-flags>
tracestate: vendor1=value1,vendor2=value2
# gRPC Metadata
grpc-trace-bin: <二进制格式>
# Kafka: Header "traceparent"
# RabbitMQ: Message properties传播要求:所有服务使用相同的传播格式;异步操作(消息队列、定时任务)需要手动传播上下文;跨语言调用统一使用 OTLP 协议。
Java Agent 性能参数:
otel.javaagent.exclude-classes=com.example.NoisyClass
otel.instrumentation.http.client.emit-spans=false
otel.instrumentation.common.db-statement-sanitizer.enabled=true
otel.bsp.schedule.delay=5000
otel.bsp.max.queue.size=2048
otel.bsp.max.export.batch.size=512参考开销:Java Agent 增加约 1-3% CPU、10-30MB 内存;Node.js SDK 增加约 2-5% CPU;Collector 单实例可处理 10,000+ spans/s。
生产部署要点:在应用和存储之间用 Kafka 做缓冲;ES 用 ILM 自动清理过期数据;追踪系统与业务系统分开部署;用独立的 Prometheus 监控 Collector 和存储健康;Span 中不记录密码、Token、身份证号等敏感信息;Collector 启用 TLS。容量参考:每 1000 QPS 约产生 5,000-20,000 spans/s(取决于服务深度),每个 Span 约 1-2 KB,存储 7 天约需 100-500 GB。
ES 索引生命周期管理:
curl -X PUT "http://elasticsearch:9200/_ilm/policy/jaeger-retention" -H 'Content-Type: application/json' -d '{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {"max_size": "50gb", "max_age": "1d"}
}
},
"warm": {
"min_age": "3d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 }
}
},
"delete": {
"min_age": "7d",
"actions": { "delete": {} }
}
}
}
}'快速选型:
| 场景 | 推荐方案 |
|---|---|
| 新项目,全栈可观测性 | OpenTelemetry + Jaeger + Prometheus + Grafana |
| 已有 Java 微服务,快速接入 | SkyWalking(零侵入) |
| 混合语言(Java + Go + Node.js) | OpenTelemetry + Jaeger |
| 需要商业支持 | Datadog / New Relic / 阿里云 ARMS |
| 低成本起步 | OpenTelemetry + Grafana Tempo |
Trace 数据受采样、上下文传播与采集背压影响。找不到 span 时,先检查采样规则和传播头,再判断调用是否发生。异步消息需要任务或消息标识辅助关联,不能假设所有工作都保持同一同步调用栈。
先在测试环境用同一个已知请求验证 Trace 入口、上下文传播、导出和查询,再把链路扩展到全站。正文保留的 Jaeger 1.x、旧 SDK 与安装片段属于版本示例。
三、SLO 与跨层故障定位
先定义成功请求
SLI 是观测指标,SLO 是目标。对查询接口,可以定义“观察窗内返回有效结果且延迟不超过阈值的请求占比”。记录哪些请求进入分母、客户端取消如何处理、维护窗口是否排除,并确保定义与真实用户体验一致。
合格比例 = 满足全部约束的有效请求数 / 纳入统计的请求数
错误预算 = (1 - SLO 目标比例) × 纳入统计的请求数
预算消耗速率 = 当前坏请求比例 / (1 - SLO 目标比例)零请求窗口没有可计算的比例;SLO 为 100% 时最后一个公式分母为零,需改用绝对错误或其他处理规则。按请求计算的错误预算与按时间计算的不可用预算应分开维护。SRE Workbook 的 SLO 告警章节介绍了消耗速率与多窗口设计。
错误预算与 SLO 告警
SLO 指标作为 recording rule 预先计算,再围绕消耗速率告警。SLA 的计算可以这样落到 Prometheus:
# Prometheus recording rules - SLO 计算
groups:
- name: sla_rules
interval: 1m
rules:
# 服务可用性(按月滚动)
- record: service:sla:availability_30d
expr: |
1 - (
sum_over_time(http_requests_total{status=~"5.."}[30d])
/
sum_over_time(http_requests_total[30d])
)
# 错误预算消耗速率
- record: service:error_budget_burn_rate
expr: |
(
1 - (
sum(rate(http_requests_total{status!~"5.."}[1h]))
/
sum(rate(http_requests_total[1h]))
)
) / (1 - 0.999) # 假设 SLO 目标 99.9%错误预算告警需要固定 SLO 分母与低流量处理,避免把无请求窗口或采集缺失解释为服务健康。
三类信号提供不同证据
指标便于观察分布和趋势;日志保留事件与错误细节;Trace 描述一次执行的调用关系。Trace 通常受采样影响,日志也可能因限流或采集故障缺失,缺少记录时应说明证据范围。
关联至少需要时间窗、服务名、部署版本和实例标识。一次请求跨进程时传播 trace context,并在日志中附 trace ID。对于异步队列或批处理任务,还需保留消息或任务 ID,明确它与请求 Trace 的关系。
用同一请求逐层排查
假设入口 P95 延迟上升,按以下顺序缩小范围:
- 确认请求量、错误率和延迟统计口径,排除监控断采或路由标签变化。
- 在相同时间窗比较入口、应用、数据库与队列指标,判断时间主要花在排队、执行还是网络等待。
- 选择慢请求 Trace,检查关键路径、重试和缺失 span。并行子调用的耗时不能简单相加。
- 关联对应实例日志、连接池、线程和资源压力,形成可以验证的假设。
- 对一个变量进行受控调整,复跑同类请求,检查用户指标与资源指标是否一致改善。
数据库连接等待增长时,增加应用副本可能继续增加争用。需要检查连接池总量、事务时长与数据库容量,再决定限流、优化查询或扩容。
直方图与采样会改变结论
多个实例的 P95 无法直接取平均得到集群 P95。使用兼容桶边界的直方图聚合计数,再计算分位数;同时标注统计窗口和样本量。平均值适合描述总量关系,但可能掩盖长尾。
高基数标签会增加时序数量。用户 ID、请求 ID 和完整 URL 通常保留在日志或 Trace 中,指标使用路由模板、状态类别等有限维度。采样比例变化时,前后 Trace 数量不可直接作为流量增长证据。Prometheus 直方图说明提供了聚合边界。
告警围绕可行动的影响
错误预算快速消耗可触发值班通知,短暂资源尖峰可先记录或工单化。每条告警注明影响、责任人、排查入口与恢复标准;抑制和静默需有范围与过期时间。突发故障可能在发生后才被检测,告警质量不能以提前预测所有故障衡量。
用受控故障验证完整路径:产生用户可见失败,确认指标采集、规则评估、通知送达、值班响应和恢复通知。仅看到规则配置存在,无法证明通知链可用。将检测延迟、通知送达、响应时间和恢复时间分开记录。
演练与事故复盘
演练先限定目标实例、时间、最大影响和停止条件。注入进程退出、网络延迟或依赖失败后,保存时间线与原始记录。真实事故材料与演练结果分别标注,不能把示意日志写成事故证据。
复盘记录用户影响、检测时间、处置时间、支持根因的证据、仍未解释的现象和后续验证。一次修复后的状态恢复需要再经历相同负载或触发条件,才能判断问题是否被解决。复盘模板字段由真实记录填写;没有账单或业务核算依据时,损失金额保持待核算。
| 字段 | 填写要求 |
|---|---|
| 标题与等级 | 描述实际影响及分级依据 |
| 时间线 | 发生、发现、响应、缓解、恢复分别记录 |
| 用户影响 | 请求、任务、时间窗和分母 |
| 直接损失 | 数据来源、计算口径与未知项 |
| 根因证据 | 原始日志、指标、变更和反例 |
| 已采取动作 | 动作、执行者、结果与副作用 |
| 改进措施 | 负责人、期限与完成验收 |
正文覆盖监控、告警、链路与 APM 部署,提供方法与验证设计。
← 返回文章