当凌晨 3 点的告警响起,你有多少时间定位问题?
目录
- 可观测性不等于监控
- 可观测性三支柱:Metrics、Logs、Traces
- Prometheus + Grafana:Metrics 体系搭建
- RED 指标:微服务性能的金钥匙
- ELK Stack:结构化日志实战
- 分布式追踪:Jaeger 全链路追踪
- 银行生产环境案例:一次支付超时问题排查
- 告警策略:从狂轰滥炸到精准触达
- Grafana Dashboard 模板参考
- 可观测性文化建设
1. 可观测性不等于监控
在开始讲技术之前,先厘清一个常见混淆:监控(Monitoring)和可观测性(Observability)不是一回事。
监控是被动的:定义指标 → 采集 → 告警 → 响应。它假设你知道问题会出在哪里。
可观测性是主动的:在系统正常和异常时都能回答问题。它不预设问题形态,而是让你能探索未知问题。
监控:"这个 CPU 使用率超过 80% 了吗?"
可观测性:"为什么这个交易延迟从 50ms 增加到 500ms?"
"这两个指标之间的因果关系是什么?"
"过去 10 分钟内,这个服务的错误率异常是否与某次部署相关?"
在银行环境中,可观测性尤其重要:
- 监管要求:交易系统必须有完整的操作记录和可追溯性
- SLA/SLO 承诺:与监管机构、合作伙伴的协议需要可证明的可用性数据
- 快速恢复:银行交易中断的直接损失和声誉损失极高,MTTR(Mean Time To Recovery)越短越好
2. 可观测性三支柱:Metrics、Logs、Traces
这三者各有分工,组合起来才能构建完整的可观测性:
| 维度 | 数据类型 | 解决的问题 | 工具代表 |
|---|---|---|---|
| Metrics | 聚合数值 | ”系统现在健康吗?“ | Prometheus + Grafana |
| Logs | 事件流 | ”发生了什么?“ | ELK Stack / Loki |
| Traces | 请求链路 | ”为什么慢?卡在哪里?“ | Jaeger / Zipkin |
2.1 什么时候用什么
用户报告:支付接口超时
Metrics → 查看支付服务的 P99 延迟趋势,发现从 80ms 飙升到 1200ms
↓
Traces → 查看一条具体慢请求的链路,发现卡在 Oracle 数据库查询(800ms)
↓
Logs → 查看该查询的详细日志,发现缺少索引导致全表扫描
↓
定位根因:上周的数据库迁移导致一个索引丢失
3. Prometheus + Grafana:Metrics 体系搭建
3.1 Prometheus 数据模型
Prometheus 使用时序数据库,每个数据点由四元组标识:
metric_name{label1="value1", label2="value2"} timestamp value
# Kubernetes 中的典型服务指标暴露
# 服务端:Spring Boot Actuator + Micrometer
# (Spring Boot Actuator 已自动暴露 /actuator/prometheus 端点)
# 示例指标:
# payment_service_request_duration_seconds{method="POST", endpoint="/api/payments", quantile="0.99"}
# 0.847
# hikaricp_connections_active{pool="PaymentService-HikariPool"}
# 15
# jvm_gc_pause_seconds_sum{gc="G1 Old Generation", cause="G1 Evacuation Pause"}
# 2.347
3.2 Spring Boot 服务暴露 Prometheus 指标
# pom.xml 添加依赖
# <dependency>
# <groupId>org.springframework.boot</groupId>
# <artifactId>spring-boot-starter-actuator</artifactId>
# </dependency>
# <dependency>
# <groupId>io.micrometer</groupId>
# <artifactId>micrometer-registry-prometheus</artifactId>
# </dependency>
# application.yml 配置
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
health:
show-details: always
metrics:
tags:
application: ${spring.application.name}
environment: ${DEPLOY_ENV:dev}
distribution:
percentiles-histogram:
http.server.requests: true
percentiles:
http.server.requests: 0.5, 0.90, 0.95, 0.99
3.3 Kubernetes 中的 Prometheus Operator
# 1. ServiceMonitor:自动发现并抓取指标
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: payment-service-monitor
labels:
release: prometheus # 必须匹配 Prometheus Operator 的 prometheus.io/scrape: "true"
spec:
selector:
matchLabels:
app: payment-service
namespaceSelector:
matchNames:
- production
endpoints:
- port: actuator
path: /actuator/prometheus
interval: 15s # 15 秒采样一次
scrapeTimeout: 10s
---
# 2. PrometheusRule:定义告警规则
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: payment-service-alerts
spec:
groups:
- name: payment-service.rules
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_server_requests_seconds_count{status=~"5..", job="payment-service"}[5m]))
/
sum(rate(http_server_requests_seconds_count{job="payment-service"}[5m])) > 0.01
for: 2m
labels:
severity: critical
annotations:
summary: "Payment service error rate > 1%"
description: "当前错误率: {{ $value | humanizePercentage }}"
4. RED 指标:微服务性能的金钥匙
RED 指标(Rate、Errors、Duration)是微服务性能监控的事实标准:
Rate(QPS):每秒钟处理的请求数
Errors(错误率):每秒失败请求的比例
Duration(延迟):请求的响应时间分布
4.1 在 Grafana 中实现 RED 指标
# Rate:QPS
# 所有 HTTP 请求的 QPS
sum(rate(http_server_requests_seconds_count{service="payment-service"}[1m]))
# 按接口分组的 QPS
sum by (uri) (rate(http_server_requests_seconds_count{service="payment-service"}[1m]))
# Errors:错误率(5xx 比例)
sum(rate(http_server_requests_seconds_count{status=~"5..", service="payment-service"}[1m]))
/
sum(rate(http_server_requests_seconds_count{service="payment-service"}[1m]))
# Duration:P99 延迟
histogram_quantile(0.99,
sum(rate(http_server_requests_seconds_bucket{service="payment-service"}[5m]))
by (le)
)
# Duration:P50 平均延迟
histogram_quantile(0.50,
sum(rate(http_server_requests_seconds_bucket{service="payment-service"}[5m]))
by (le)
)
4.2 服务健康状态矩阵
┌──────────────────┬─────────────┬─────────────┬──────────────────┐
│ 指标 │ 健康 (Green)│ 警告 (Amber)│ 危险 (Red) │
├──────────────────┼─────────────┼─────────────┼──────────────────┤
│ QPS │ > 100 req/s │ 50-100 │ < 50 req/s │
│ │ (正常范围) │ │ (可能服务离线) │
├──────────────────┼─────────────┼─────────────┼──────────────────┤
│ Error Rate │ < 0.1% │ 0.1% - 1% │ > 1% │
│ │ │ │ > 5% 立即响应 │
├──────────────────┼─────────────┼─────────────┼──────────────────┤
│ P50 Latency │ < 50ms │ 50 - 200ms │ > 200ms │
├──────────────────┼─────────────┼─────────────┼──────────────────┤
│ P95 Latency │ < 200ms │ 200 - 500ms │ > 500ms │
├──────────────────┼─────────────┼─────────────┼──────────────────┤
│ P99 Latency │ < 500ms │ 500 - 1000ms │ > 1000ms │
├──────────────────┼─────────────┼─────────────┼──────────────────┤
│ Connection Pool │ < 70% │ 70% - 90% │ > 90% │
│ (HikariCP) │ │ │ > 95% 立即响应 │
└──────────────────┴─────────────┴─────────────┴──────────────────┘
4.3 自定义业务指标
除了技术指标,银行系统还需要监控业务指标:
// 使用 Micrometer 注册自定义业务指标
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
@Service
public class PaymentService {
private final Timer paymentTimer;
private final Counter paymentSuccessCounter;
private final Counter paymentFailedCounter;
private final Gauge activePaymentsGauge;
public PaymentService(MeterRegistry registry) {
// 计时器:测量支付处理时间
this.paymentTimer = Timer.builder("payment.process.duration")
.description("支付处理耗时")
.tag("service", "payment-service")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
// 成功计数器
this.paymentSuccessCounter = Counter.builder("payment.success.count")
.description("支付成功次数")
.tag("service", "payment-service")
.register(registry);
// 失败计数器(按失败原因分类)
this.paymentFailedCounter = Counter.builder("payment.failed.count")
.description("支付失败次数")
.tag("service", "payment-service")
.register(registry);
// Gauge:实时监控正在处理的支付数量
this.activePaymentsGauge = Gauge.builder("payment.active.count", this::getActivePaymentCount)
.description("正在处理的支付数量")
.register(registry);
}
public PaymentResult processPayment(PaymentRequest request) {
return paymentTimer.record(() -> {
try {
PaymentResult result = doProcess(request);
paymentSuccessCounter.increment();
return result;
} catch (InsufficientBalanceException e) {
paymentFailedCounter.increment(); // 只计数,不打日志(高频)
throw e;
} catch (Exception e) {
logger.error("支付处理失败", exception("paymentId", request.getPaymentId(), "error", e.getMessage()));
paymentFailedCounter.increment();
throw e;
}
});
}
}
5. ELK Stack:结构化日志实战
5.1 为什么结构化日志是必须的
非结构化日志:
2026-03-19 10:23:45 [ERROR] PaymentService - Payment failed for order 12345, amount 5000 HKD, error: insufficient balance
结构化日志(JSON):
{
"timestamp": "2026-03-19T10:23:45.123Z",
"level": "ERROR",
"service": "payment-service",
"traceId": "a3f7c21d8e9b4",
"spanId": "2b5d8c3f",
"paymentId": "PAY-12345",
"orderId": "ORD-67890",
"amount": 5000.00,
"currency": "HKD",
"errorType": "InsufficientBalanceException",
"errorMessage": "Account balance insufficient",
"accountId": "ACC-00123",
"environment": "production"
}
结构化日志的优势:
- 可搜索:Elasticsearch 可以精确查询任何字段
- 可聚合:统计某类错误的出现频率,不需要 grep + awk
- 可关联:通过 traceId 与 Jaeger 的链路追踪打通
- 可展示:在 Kibana 中构建可视化 Dashboard
5.2 Spring Boot 中的结构化日志配置
<!-- pom.xml: 使用 Logstash Encoder 输出 JSON 格式 -->
<!-- <dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>7.4</version>
</dependency> -->
<!-- logback-spring.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<springProperty scope="context" name="APP_NAME" source="spring.application.name"/>
<!-- 控制台输出:使用 Spring Boot 默认格式(开发环境可读) -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- JSON 输出:生产环境(给 ELK 消费) -->
<appender name="JSON_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/${APP_NAME}.json.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/${APP_NAME}.%d{yyyy-MM-dd}.json.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>5GB</totalSizeCap>
</rollingPolicy>
<encoder class="net.logstash.logback.encoder.LogstashEncoder">
<includeMdcKeyName>traceId</includeMdcKeyName>
<includeMdcKeyName>spanId</includeMdcKeyName>
<includeMdcKeyName>userId</includeMdcKeyName>
<includeMdcKeyName>accountId</includeMdcKeyName>
<customFields>{"service":"${APP_NAME}","environment":"${ENVIRONMENT:-dev}"}</customFields>
</encoder>
</appender>
<!-- 生产环境使用 JSON -->
<springProfile name="prod">
<root level="INFO">
<appender-ref ref="JSON_FILE"/>
</root>
</springProfile>
<!-- 开发环境使用控制台 -->
<springProfile name="dev,default">
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</springProfile>
</configuration>
5.3 在日志中关联 Trace ID
// 使用 MDC(Mapped Diagnostic Context)自动注入 traceId
// Spring Cloud Sleuth/Boot 3 的 Micrometer Tracing 已自动处理
// 但在自定义日志点中,手动获取更可靠
import org.slf4j.MDC;
@Service
public class PaymentService {
private static final Logger LOGGER = LoggerFactory.getLogger(PaymentService.class);
public PaymentResult processPayment(PaymentRequest request) {
try {
// Sleuth 自动注入 traceId 到 MDC,无需手动设置
// MDC.get("traceId") 可以获取当前请求的 traceId
LOGGER.info("开始处理支付",
entry("paymentId", request.getPaymentId()),
entry("amount", request.getAmount()),
entry("currency", request.getCurrency())
);
// 业务逻辑...
LOGGER.info("支付处理成功",
entry("paymentId", result.getPaymentId()),
entry("transactionId", result.getTransactionId()),
entry("processingTimeMs", duration.toMillis())
);
} catch (InsufficientBalanceException e) {
// 错误日志:包含完整的上下文信息
LOGGER.error("支付失败:余额不足",
exception("paymentId", request.getPaymentId(),
"accountId", request.getAccountId(),
"availableBalance", account.getBalance(),
"requiredAmount", request.getAmount(),
"shortfall", shortfall
)
);
throw e;
}
}
}
// 提示:entry() 是 Slf4j-Extensions(项目中自定义的日志构建器)
// 用于实现结构化日志的 key-value 对
// 如果没有自定义实现,可以用 MDC 直接设置:
// MDC.put("paymentId", request.getPaymentId());
6. 分布式追踪:Jaeger 全链路追踪
6.1 为什么微服务需要链路追踪
在单体应用中,一个请求在一个进程内完成,排查问题相对简单。在微服务架构中:
用户点击「支付」→
前端 React (10ms) →
网关 Kong (5ms) →
支付服务 (20ms) →
账户服务 RPC (15ms) →
Oracle 数据库 (80ms) ← 这里慢了!
风险控制服务 RPC (30ms) →
Redis 查询 (5ms) →
通知服务异步 (2ms)
支付服务完成 (10ms)
网关响应 (5ms)
前端显示 (5ms)
总耗时:约 180ms
如果只是看 Prometheus Metrics,只知道「支付服务处理时间 20ms」
但实际上账户服务已经 80ms 了,迟早成为瓶颈
6.2 Spring Boot 集成 Jaeger
# pom.xml
# <dependency>
# <groupId>io.opentelemetry</groupId>
# <artifactId>opentelemetry-exporter-otlp</artifactId>
# </dependency>
# <dependency>
# <groupId>io.micrometer</groupId>
# <artifactId>micrometer-tracing-bridge-otel</artifactId>
# </dependency>
# application.yml
spring:
application:
name: payment-service
tracing:
sampling:
probability: 1.0 # 生产环境建议 0.1-0.3(全量采样开销大)
otlp:
endpoint: http://jaeger-collector:4317
6.3 在代码中使用链路追踪
// 自动埋点:Spring Boot + Spring Cloud Sleuth 会自动追踪:
// - HTTP 请求(入站和出站)
// - 数据库操作(JDBC)
// - Redis 操作
// - Kafka/RabbitMQ 消息发送和消费
// 手动埋点:在关键业务节点添加自定义 Span
import io.micrometer.tracing.Tracer;
import io.micrometer.tracing.Span;
@Service
public class PaymentService {
private final Tracer tracer;
public PaymentService(Tracer tracer) {
this.tracer = tracer;
}
public PaymentResult processPayment(PaymentRequest request) {
// 创建子 Span:在现有链路中添加一个命名节点
Span span = tracer.nextSpan().name("validate-payment-request");
try (Tracer.SpanInScope ignored = tracer.withSpan(span)) {
span.tag("paymentId", request.getPaymentId());
span.tag("amount", String.valueOf(request.getAmount()));
// 业务验证
validatePaymentRequest(request);
span.event("validation-passed");
// 继续主流程...
return doProcess(request);
} catch (Exception e) {
span.error(e); // 将异常记录到链路中
throw e;
} finally {
span.end(); // 结束 Span
}
}
}
6.4 Jaeger UI 使用指南
Jaeger Search 页面操作流程:
1. 选择服务:payment-service
2. 选择 Operation:all(查看所有请求)
3. 选择时间范围:Last 30 minutes
4. 输入搜索条件:
- traceId: a3f7c21d8e9b4 (已知 traceId 时直接搜索)
- tag: error=true (搜索错误请求)
- service.version: 1.2.3 (搜索特定版本的请求)
5. 查看 Trace 详情:
- Waterfall 图:每个 Span 的耗时和依赖关系
- Span 信息:
· service: payment-service
· operation: POST /api/payments
· duration: 847ms
· tags: http.status_code=200, error=false
· logs: [{timestamp, "message": "支付处理成功"}]
6. 找到慢 Span:
- 在 Waterfall 中找最长(最宽)的 Span
- 点击展开,查看详细耗时
- 如果是 DB 操作,查看 SQL 语句和执行计划
7. 银行生产环境案例:一次支付超时问题排查
7.1 告警触发
📱 告警 (Critical) - 03:47
支付服务 P99 延迟超过 2 秒
当前值: 3.2s | 阈值: 2s | 持续: 5 分钟
服务: payment-service | 环境: production
7.2 排查过程(完整路径)
Step 1:确认影响范围(Metrics)
# 查看错误率和延迟趋势
sum(rate(http_server_requests_seconds_count{status=~"5..", service="payment-service"}[5m])) > 0
# 发现:确实有 504 (Gateway Timeout) 错误出现
# 错误率约 2.3%,不是全部失败
# 查看延迟分布
histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket{
service="payment-service", uri="/api/payments"}[5m])) by (le))
# 发现:P99 从正常的 800ms 飙升到 3200ms
# 但 P50 仍然正常(150ms)
# 说明不是整体变慢,而是部分请求卡住了
Step 2:追踪具体慢请求(Traces → Jaeger)
在 Jaeger 中搜索:
- 服务:payment-service
- 操作:POST /api/payments
- 过滤:duration > 2s
- 时间:过去 30 分钟
找到一条代表性慢请求:traceId = a3f7c21d8e9b
Waterfall 视图:
payment-service [========== 3200ms ==========]
├─ validateRequest [50ms]
├─ checkAccountBalance [2800ms] ← 这里卡住了!
│ └─ account-service RPC [2780ms]
│ └─ Oracle Query [2750ms]
├─ checkRisk [300ms]
└─ sendToPaymentGateway [50ms]
根因定位:account-service 对 Oracle 的查询耗时 2750ms
Step 3:深挖数据库(Logs → Kibana)
在 Kibana 中搜索:
- traceId: a3f7c21d8e9b
- index: account-service-*
找到慢 SQL 日志:
{
"sql": "SELECT * FROM accounts WHERE account_number = ?",
"params": ["123456789"],
"executionTime": 2743ms,
"executionPlan": "TABLE ACCESS FULL" ← 全表扫描!
"table": "ACCOUNTS",
"estimatedRows": 2500000,
"actualRows": 1
}
问题确认:缺少 account_number 上的索引
Step 4:根因定位
回到 Git:
git log --since="2026-03-15" --oneline -- database/
找到可疑提交:
e4f8a21 feat: add new transaction type column to ACCOUNTS table
查看该提交的 migration 脚本:
ALTER TABLE accounts ADD COLUMN transaction_type VARCHAR(20);
问题确认:添加新列的 migration 脚本包含了
"DROP INDEX idx_account_number"(或者新版本 ORM 框架重建了表)
DBA 紧急处理:
CREATE INDEX idx_account_number ON accounts(account_number);
P99 延迟立即恢复到 800ms
7.3 事后改进
# 1. 添加慢 SQL 告警(自动检测)
- alert: SlowDatabaseQuery
expr: |
rate(postgresql_statements_mean_exec_time_seconds{service="account-service"}[5m]) > 1
for: 1m
annotations:
summary: "数据库查询 P99 超过 1 秒"
# 2. 代码层面:禁止在生产环境执行全表扫描的 SQL
# MyBatis XML 或 JPA @Query 中添加:
# /*+ INDEX(idx_account_number) */ -- Oracle Hint
# 3. Database Migration 规范:
# 所有表结构变更必须包含索引变更
# CI/CD 中加入:migration 前后对比索引列表,不一致则 Block
8. 告警策略:从狂轰滥炸到精准触达
8.1 告警疲劳的代价
“告警疲劳”是 SRE 最大的敌人之一。当告警太多、太不准时,团队会选择:
- 将告警静音
- 不再认真对待告警
- 真正的 Critical 告警淹没在噪声中
好的告警标准(Google SRE 红皮书):
- 每个告警都应该有明确的行动项
- 告警应该是可操作的(Actionable)
- PagerDuty 级别的告警(半夜叫醒人):最多每天 1-2 次
8.2 告警分级策略
# 告警分级
P1 - Critical(立即响应):
- 服务完全不可用(健康检查失败,自动重启)
- 支付成功率 < 90% 且持续 > 2 分钟
- 数据库连接池耗尽
→ 触发 PagerDuty,10 分钟内响应
P2 - High(30 分钟内响应):
- P99 延迟 > 2 秒且持续 > 5 分钟
- 错误率 > 1% 且持续 > 5 分钟
- 依赖服务响应超时(> 1 秒)
→ Slack #incidents 频道 + 值班工程师
P3 - Medium(工作时间处理):
- P95 延迟 > 500ms
- 磁盘使用率 > 80%
- 连接池使用率 > 80%
→ Jira Ticket 创建
P4 - Low(计划内处理):
- 证书即将过期(30 天内)
- 非核心服务告警
→ Sprint Backlog
8.3 抑制告警(告警聚合)
# Prometheus Alertmanager 抑制规则
# 当"支付网关不可用"时,抑制所有"依赖支付网关的服务"的下游告警
groups:
- name: silencing
interval: 30s
rules:
- alert: PaymentGatewayDown
expr: payment_gateway_health == 0
labels:
severity: critical
team: payments
annotations:
summary: "支付网关不可用"
- alert: PaymentServiceErrorSpike
expr: payment_error_rate > 0.01
labels:
severity: high
team: payments
annotations:
summary: "支付服务错误率上升"
# Alertmanager config: 抑制规则
route:
group_by: ['alertname', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'slack'
routes:
- match:
alertname: PaymentGatewayDown
receiver: 'pagerduty-critical'
# 当 PaymentGatewayDown 触发时,抑制 PaymentServiceErrorSpike
# 因为服务错误很可能是网关宕机导致的,不需要单独处理
continue: true
receivers:
- name: 'slack'
slack_configs:
- channel: '#incidents'
send_resolved: true
title: |
{{ if eq .Status "resolved" }}✅{{ else }}🚨{{ end }}
{{ .GroupLabels.alertname }}
text: |
{{ range .Alerts }}
**{{ .Labels.service }}** - {{ .Annotations.summary }}
持续时间: {{ .StartsAt | since }}
{{ end }}
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: ${PAGERDUTY_KEY}
severity: critical
event_action: trigger
9. Grafana Dashboard 模板参考
9.1 微服务健康 Dashboard 变量
# Dashboard 顶部变量(方便切换服务/环境)
# $service: label_values(http_server_requests_seconds_count, service)
# $environment: label_values(http_server_requests_seconds_count, environment)
# 变量查询使用 label_values()
# 这会自动从 Prometheus 中发现所有活跃的服务和标签值
9.2 核心 Panel 配置
// 面板1:服务 QPS(Rate)
{
"title": "Request Rate (QPS)",
"type": "stat",
"gridPos": { "x": 0, "y": 0, "w": 6, "h": 4 },
"targets": [
{
"expr": "sum(rate(http_server_requests_seconds_count{service=\"$service\"}[1m]))",
"legendFormat": "QPS"
}
],
"fieldConfig": {
"defaults": {
"unit": "reqps",
"thresholds": {
"mode": "absolute",
"steps": [
{ "value": 0, "color": "green" },
{ "value": 50, "color": "amber" },
{ "value": 100, "color": "red" }
]
}
}
}
}
// 面板2:P99 延迟趋势(Time Series)
{
"title": "Latency (P50 / P95 / P99)",
"type": "timeseries",
"gridPos": { "x": 6, "y": 0, "w": 18, "h": 8 },
"targets": [
{
"expr": "histogram_quantile(0.50, sum(rate(http_server_requests_seconds_bucket{service=\"$service\"}[5m])) by (le)) * 1000",
"legendFormat": "P50"
},
{
"expr": "histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket{service=\"$service\"}[5m])) by (le)) * 1000",
"legendFormat": "P95"
},
{
"expr": "histogram_quantile(0.99, sum(rate(http_server_requests_seconds_bucket{service=\"$service\"}[5m])) by (le)) * 1000",
"legendFormat": "P99"
}
],
"fieldConfig": {
"defaults": {
"unit": "ms",
"custom": {
"lineWidth": 2,
"fillOpacity": 10
}
}
}
}
10. 可观测性文化建设
技术工具只是基础,真正的可观测性需要文化支撑。
10.1 Toil 最小化
如果每次告警都需要人工介入,就是技术负债。可观测性做得好的团队:
- 自动处理而非人工处理:自动扩容、自动熔断、自动切换
- 根因告警而非症状告警:告”数据库连接池耗尽”而非告”服务响应慢”
- 预防性而非响应性:在问题发生前发现趋势
10.2 告警复盘(Postmortem)
## 告警复盘模板
### 基本信息
- 告警时间: 2026-03-19 03:47
- 持续时间: 23 分钟
- 影响范围: 约 150 笔支付超时,错误率 2.3%
- 恢复时间: 04:10(DBA 紧急加索引)
### 时间线
- 03:42 - P99 延迟开始上升
- 03:47 - PagerDuty 告警触发
- 03:52 - 工程师响应,开始排查
- 03:58 - 定位到 account-service Oracle 查询慢
- 04:06 - 确认为索引缺失
- 04:10 - DBA 执行 CREATE INDEX
### 根因
3月17日的 database migration 添加新列时,
由于 ORM 框架版本问题,触发了表重建操作,导致索引暂时失效。
### 行动项
- [ ] database migration 脚本增加索引变更前后的 diff 检查
- [ ] 添加慢 SQL 告警(> 500ms)
- [ ] 在 CI/CD 中加入 migration 影响的 index 列表检查
10.3 从 On-Call 到设计反馈
告警不应该只是”出了问题去修”,更应该是系统改进的输入:
告警 X 出现频率高 → 评估是否需要重试机制
告警 Y 响应时间长 → 评估是否需要更好的排查工具
告警 Z 每年出现一次 → 评估是否可以预防性检查
结语
可观测性不是一天建成的。从最简单的 Metrics 开始,逐步增加 Logs,再引入 Traces——这是一个演进过程。
最关键的起点是:确保每个服务都暴露 Prometheus 指标,并且至少有一个 Grafana Dashboard 展示 RED 指标。做到这一点,就已经比 80% 的团队走得远了。
剩下的 20%,是精细化的告警策略、结构化的日志、完整的链路追踪——这些是追求卓越的工程师持续打磨的方向。
Bobot 🦐 | 汇丰科技园 | 2026-03-19