服务稳定性之 LMAT 和 USED

Posted on Fri 06 March 2026 in Tech • 7 min read

服务稳定性之 LMAT 和 USED

隐患险于明火,防范胜于救灾,责任重于泰山。

这句话用在消防安全上恰如其分,用在服务稳定性上同样贴切。

线上故障就像火灾——等你看到火光(用户投诉)时,往往已经烧了一阵子了。真正的功夫不在救火,而在防火:你能不能在火苗刚起时就发现它?你有没有一套系统化的巡检机制?你的告警是不是真的能叫醒该叫醒的人?

本文介绍两个我在实践中反复使用的方法论框架:

  • LMAT:Log, Metrics, Alert, Trace —— 可观测性的四根支柱
  • USED:Usage, Saturation, Error, Delay —— 资源健康度的四个维度

它们一个回答"用什么手段观测",一个回答"观测什么内容",组合起来就是一套完整的服务稳定性保障体系。

                    ┌─────────────────────────────────┐
                    │        服务稳定性保障体系          │
                    ├────────────────┬────────────────┤
                    │   LMAT         │   USED         │
                    │   (怎么观测)    │   (观测什么)    │
                    ├────────────────┼────────────────┤
                    │ Log   日志     │ Usage   用量   │
                    │ Metrics 度量   │ Saturation 饱和│
                    │ Alert  告警    │ Error   错误   │
                    │ Trace  追踪    │ Delay   延迟   │
                    └────────────────┴────────────────┘

一、LMAT:可观测性的四根支柱

可观测性(Observability)这个词这几年被说烂了,但很多团队的实践仍然停留在"有日志就行"的阶段。LMAT 框架把可观测性拆成四个层次,每一层解决不同的问题。

1.1 Log(日志)—— 事后取证的第一现场

日志是最古老、最直觉的观测手段。出了问题,第一反应就是"看日志"。

日志的核心价值:事后分析和取证。

[2026-03-06 10:23:45.123] ERROR [order-service] [trace_id=abc123]
  Failed to process order #98765: PaymentGatewayTimeout
  user_id=u_42 amount=299.00 retry=2/3
  at com.example.OrderService.processPayment(OrderService.java:142)

一条好的日志应该包含:

要素 说明 示例
时间戳 精确到毫秒 2026-03-06T10:23:45.123Z
级别 ERROR/WARN/INFO/DEBUG ERROR
服务名 哪个服务产生的 order-service
Trace ID 关联到分布式追踪 trace_id=abc123
上下文 业务关键字段 user_id, order_id, amount
错误详情 具体什么错 PaymentGatewayTimeout

日志的常见问题:

  • 日志太多:每秒几万条,存储成本爆炸,搜索慢如蜗牛
  • 日志太少:出了问题找不到关键信息,只能加日志重新部署
  • 日志没结构:自由文本格式,无法聚合分析
  • 日志没关联:缺少 Trace ID,无法串联一次请求的完整链路

最佳实践:

# ❌ 不好的日志
logger.error("something went wrong")

# ✅ 好的日志:结构化 + 上下文
logger.error(
    "Payment gateway timeout",
    extra={
        "trace_id": ctx.trace_id,
        "user_id": user.id,
        "order_id": order.id,
        "amount": order.amount,
        "gateway": "stripe",
        "retry_count": retry_count,
        "latency_ms": elapsed_ms,
    }
)

日志分级策略:

生产环境日志级别建议:

INFO  ── 关键业务事件(订单创建、支付成功、用户登录)
WARN  ── 可恢复的异常(重试成功、降级触发、缓存未命中)
ERROR ── 不可恢复的错误(支付失败、数据库连接断开)
DEBUG ── 仅在排查问题时动态开启(通过配置中心热更新)

1.2 Metrics(度量)—— 实时体检的仪表盘

如果说日志是"病历本",那度量就是"体检报告"。

度量的核心价值:实时感知系统的健康状态。

度量是数值型的时间序列数据,天然适合聚合、对比、告警。

# Prometheus 格式的度量
http_requests_total{service="order", method="POST", status="200"} 12345
http_request_duration_seconds{service="order", quantile="0.99"} 0.85
order_payment_errors_total{gateway="stripe", reason="timeout"} 42

四种度量类型:

类型 说明 典型用途
Counter 只增不减的计数器 请求总数、错误总数、Token 消耗
Gauge 可增可减的瞬时值 CPU 使用率、队列长度、连接数
Histogram 值的分布(分桶统计) 延迟分布、请求大小分布
Summary 值的分位数(客户端计算) P50/P99 延迟

度量的黄金法则——RED 和 USE:

面向请求的服务(API、Web)用 RED:
  Rate    ── 每秒请求数
  Error   ── 错误率
  Duration ── 延迟

面向资源的组件(DB、Cache、Queue)用 USE:
  Utilization ── 利用率
  Saturation  ── 饱和度
  Errors      ── 错误数

度量命名规范(Prometheus 风格):

# 格式:<namespace>_<subsystem>_<name>_<unit>
# 示例:
http_server_requests_total          # Counter
http_server_request_duration_seconds # Histogram
db_pool_connections_active           # Gauge
mq_consumer_lag_messages             # Gauge

1.3 Alert(告警)—— 把被动救火变成主动预警

度量告诉你"现在怎么样",告警告诉你"什么时候该行动"。

告警的核心价值:在用户感知之前发现问题。

# Prometheus 告警规则示例
groups:
  - name: order-service-alerts
    rules:
      # 错误率告警
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{service="order",status=~"5.."}[5m]))
          / sum(rate(http_requests_total{service="order"}[5m])) > 0.01
        for: 3m
        labels:
          severity: critical
        annotations:
          summary: "订单服务错误率 > 1%: {{ $value | humanizePercentage }}"
          runbook: "https://wiki.example.com/runbook/order-high-error-rate"

      # 延迟告警
      - alert: HighLatency
        expr: |
          histogram_quantile(0.99,
            rate(http_request_duration_seconds_bucket{service="order"}[5m])
          ) > 2.0
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "订单服务 P99 延迟 > 2s: {{ $value }}s"

告警的三大陷阱:

陷阱 症状 解决方案
告警风暴 一个故障触发几十条告警,淹没真正重要的信息 告警聚合 + 抑制规则 + 分级
告警疲劳 太多误报,on-call 开始忽略告警 定期清理无效告警,每条告警必须有 runbook
告警缺失 故障发生了但没有告警 定期做故障演练,验证告警覆盖率

告警分级:

P0 (Critical) ── 用户受影响,立即响应
  例:支付成功率 < 99%、核心 API 不可用
  通知:电话 + 短信 + IM
  响应:5 分钟内

P1 (High) ── 即将影响用户,尽快处理
  例:错误率上升、延迟恶化、资源接近饱和
  通知:IM + 邮件
  响应:30 分钟内

P2 (Medium) ── 需要关注,工作时间处理
  例:非核心服务异常、日志错误增多
  通知:IM
  响应:4 小时内

P3 (Low) ── 信息性告警,下个迭代处理
  例:证书即将过期、磁盘使用率 > 70%
  通知:邮件 / 工单
  响应:1 周内

好的告警 = 可执行的告警。 每条告警都应该附带 runbook(操作手册),告诉 on-call 工程师:这个告警意味着什么?应该怎么排查?常见的根因有哪些?

1.4 Trace(追踪)—— 分布式系统的 X 光片

在微服务架构下,一个用户请求可能经过 5-10 个服务。出了问题,你怎么知道是哪个环节慢了、哪个环节报错了?

追踪的核心价值:还原一次请求的完整链路。

Trace: abc-123-def
│
├── [order-service] POST /api/orders  (120ms)
│   ├── [order-service] validate_request  (2ms)
│   ├── [order-service] check_inventory   (15ms)
│   │   └── [inventory-service] GET /api/stock  (12ms)
│   ├── [order-service] process_payment   (95ms)  ⚠️ 慢!
│   │   └── [payment-service] POST /api/charge  (90ms)
│   │       └── [stripe-gateway] external_call  (85ms)  🔥 根因
│   └── [order-service] send_notification  (5ms)
│       └── [notification-service] POST /api/notify  (3ms)

一眼就能看出:支付环节耗时 95ms,其中 85ms 花在了 Stripe 外部调用上。

追踪的三个核心概念:

概念 说明
Trace 一次完整请求的全链路,由唯一的 Trace ID 标识
Span 链路中的一个操作单元(一次函数调用、一次 RPC、一次 DB 查询)
Context Propagation 在服务间传递 Trace ID,串联所有 Span

OpenTelemetry 实现:

from opentelemetry import trace
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
from opentelemetry.instrumentation.httpx import HTTPXClientInstrumentor
from opentelemetry.instrumentation.sqlalchemy import SQLAlchemyInstrumentor

# 自动注入:FastAPI 入口 + HTTP 客户端 + 数据库
FastAPIInstrumentor.instrument_app(app)
HTTPXClientInstrumentor().instrument()
SQLAlchemyInstrumentor().instrument(engine=engine)

# 手动注入:业务关键路径
tracer = trace.get_tracer("order-service")

async def process_order(order):
    with tracer.start_as_current_span("process_order") as span:
        span.set_attribute("order.id", order.id)
        span.set_attribute("order.amount", order.amount)

        with tracer.start_as_current_span("check_inventory"):
            await inventory_client.check(order.items)

        with tracer.start_as_current_span("process_payment"):
            result = await payment_client.charge(order)
            span.set_attribute("payment.gateway", result.gateway)

1.5 LMAT 的协同效应

LMAT 四者不是孤立的,它们的真正威力在于关联

告警触发 (Alert)
    │
    ▼ "订单服务错误率 > 1%"
查看度量 (Metrics)
    │
    ▼ 发现 payment 相关的 5xx 激增,从 10:15 开始
查看追踪 (Trace)
    │
    ▼ 抽样几条失败请求,发现都卡在 stripe-gateway,超时 30s
查看日志 (Log)
    │
    ▼ 搜索 trace_id,找到具体错误:
      "Stripe API returned 503: Service Temporarily Unavailable"
    │
    ▼ 根因确认:Stripe 侧故障,触发降级到备用支付通道

关联的关键是 Trace ID——它是串联 Log、Metrics、Trace 的纽带。


二、USED:资源健康度的四个维度

LMAT 回答了"用什么手段观测",USED 回答了"观测什么内容"。

USED 是我在 Brendan Gregg 的 USE Method(Utilization, Saturation, Errors)基础上扩展的版本,增加了 Delay(延迟) 维度,更适合面向用户的服务场景。

2.1 Usage(用量)—— 你用了多少?

用量是最基础的度量维度:你的资源用了多少,还剩多少。

┌─────────────────────────────────────────────┐
│              Usage 度量矩阵                   │
├──────────────┬──────────────────────────────┤
│ 资源          │ 度量指标                      │
├──────────────┼──────────────────────────────┤
│ CPU          │ 使用率 (%)、核数              │
│ 内存          │ 已用/总量、RSS、堆内存        │
│ 磁盘          │ 已用/总量、IOPS              │
│ 网络          │ 带宽使用率、连接数            │
│ 数据库连接池   │ 活跃连接/最大连接             │
│ 线程池        │ 活跃线程/最大线程             │
│ 消息队列      │ 队列深度、消费速率            │
│ LLM API      │ Token 使用量、API 调用次数    │
│ 存储          │ 对象数量、存储空间            │
└──────────────┴──────────────────────────────┘

用量度量的关键不是"现在用了多少",而是"趋势"和"容量":

# 容量预测:按当前增长速率,多久会用完?
predict_linear(disk_used_bytes[7d], 30 * 24 * 3600) > disk_total_bytes
# 含义:按过去 7 天的趋势,30 天后磁盘会不会满?

2.2 Saturation(饱和度)—— 你还能撑多久?

饱和度比用量更进一步:不只是"用了多少",而是"还能不能接受更多工作"。

用量 vs 饱和度的区别:

用量 (Usage) 饱和度 (Saturation)
CPU 使用率 80% 运行队列长度 > CPU 核数
内存 已用 12GB/16GB 开始 swap,OOM kill
连接池 80/100 连接在用 有请求在等待连接(排队)
消息队列 队列中有 1000 条消息 消费速率 < 生产速率(积压)
线程池 8/10 线程在忙 任务队列开始堆积

饱和度是"即将出问题"的信号。 用量高不一定有问题(可能就是正常高峰),但饱和了一定有问题。

# 饱和度告警示例
- alert: DBConnectionPoolSaturated
  expr: |
    db_pool_pending_requests > 0
  for: 1m
  annotations:
    summary: "数据库连接池饱和:有请求在排队等待连接"

- alert: MessageQueueBacklog
  expr: |
    rate(mq_produced_total[5m]) > rate(mq_consumed_total[5m]) * 1.1
  for: 10m
  annotations:
    summary: "消息队列积压:生产速率持续超过消费速率"

2.3 Error(错误)—— 什么东西坏了?

错误是最直接的健康信号。但不是所有错误都一样重要。

错误分类:

错误严重程度金字塔:

    ▲  数据损坏 / 数据丢失
    │  ─────────────────────  致命
    │  服务不可用 (503)
    │  ─────────────────────  严重
    │  业务逻辑错误 (500)
    │  ─────────────────────  高
    │  上游依赖超时 (504)
    │  ─────────────────────  中
    │  客户端错误 (4xx)
    │  ─────────────────────  低
    ▼  可重试的瞬时错误

错误率 vs 错误数:

# 错误数:绝对值,适合低流量场景
http_errors_total{status="500"} > 10  # 过去 5 分钟超过 10 个 500

# 错误率:相对值,适合高流量场景
rate(http_errors_total{status=~"5.."}[5m])
/ rate(http_requests_total[5m]) > 0.01  # 错误率 > 1%

错误预算(Error Budget):

SRE 的核心理念之一——如果你的 SLO 是 99.9%,那你每月有 43 分钟的错误预算:

可用性 SLO    月度错误预算
─────────    ──────────
99%          7.3 小时
99.9%        43.2 分钟
99.95%       21.6 分钟
99.99%       4.3 分钟
# 错误预算消耗速率告警
- alert: ErrorBudgetBurnRate
  expr: |
    (
      1 - (
        sum(rate(http_requests_total{status!~"5.."}[1h]))
        / sum(rate(http_requests_total[1h]))
      )
    ) * 720 > 1
  annotations:
    summary: "错误预算消耗过快:按当前速率,本月预算将提前耗尽"

2.4 Delay(延迟)—— 用户等了多久?

延迟是用户体验最直接的度量。一个请求即使成功了,如果等了 10 秒,用户体验也是灾难性的。

延迟度量的关键原则:看分位数,不看平均值。

为什么平均值会骗人?

假设 100 个请求:
  99 个耗时 10ms
  1 个耗时 10,000ms (10秒)

平均值 = (99×10 + 1×10000) / 100 = 109ms  ← 看起来还行?
P99    = 10,000ms                          ← 真相:1% 的用户等了 10 秒!

延迟分层度量:

用户感知延迟(端到端)
    │
    ├── 网络延迟(DNS + TCP + TLS)
    │
    ├── 网关延迟(负载均衡、WAF)
    │
    ├── 服务处理延迟
    │   ├── 业务逻辑
    │   ├── 数据库查询
    │   ├── 缓存访问
    │   ├── 外部 API 调用
    │   └── 序列化/反序列化
    │
    └── 响应传输延迟
# 延迟 Histogram(Prometheus)
http_request_duration_seconds = Histogram(
    "http_request_duration_seconds",
    "Request duration",
    ["service", "method", "endpoint"],
    buckets=[0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
)

# 关键 PromQL
# P50 延迟
histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))
# P99 延迟
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
# Apdex 分数(满意 < 0.5s,容忍 < 2s)
(
  sum(rate(http_request_duration_seconds_bucket{le="0.5"}[5m]))
  + sum(rate(http_request_duration_seconds_bucket{le="2.0"}[5m]))
) / 2 / sum(rate(http_request_duration_seconds_count[5m]))

三、LMAT × USED:构建完整的度量矩阵

把 LMAT 和 USED 交叉组合,就得到一个完整的服务稳定性度量矩阵:

              │  Usage      │ Saturation  │  Error      │  Delay
──────────────┼─────────────┼─────────────┼─────────────┼────────────
 Log (日志)    │ 资源使用     │ 排队/等待   │ 错误堆栈    │ 慢查询日志
              │ 变更记录     │ 限流日志    │ 异常详情    │ 超时日志
──────────────┼─────────────┼─────────────┼─────────────┼────────────
 Metrics(度量) │ CPU/Mem/    │ 队列深度    │ 错误率      │ P50/P99
              │ Disk/连接数  │ 线程等待数  │ 错误数      │ TTFB/TTLB
──────────────┼─────────────┼─────────────┼─────────────┼────────────
 Alert (告警)  │ 容量预警     │ 饱和告警    │ 错误率告警  │ 延迟告警
              │ 趋势预测     │ 积压告警    │ 预算消耗    │ SLO 违规
──────────────┼─────────────┼─────────────┼─────────────┼────────────
 Trace (追踪)  │ 调用次数     │ 排队耗时    │ 错误链路    │ 慢链路
              │ 依赖拓扑     │ 重试次数    │ 异常传播    │ 瓶颈定位

这个矩阵的使用方式:

  1. 日常巡检:横向看 Metrics 行,快速扫描 USED 四个维度
  2. 故障排查:纵向看 Error 列,从 Alert 触发 → Metrics 定位 → Trace 追踪 → Log 取证
  3. 容量规划:横向看 Usage + Saturation,结合趋势预测
  4. SLO 管理:聚焦 Error + Delay 两列

四、实战:一个服务的 LMAT × USED 落地

以一个订单服务为例,展示如何落地这套体系。

4.1 度量定义

from prometheus_client import Counter, Histogram, Gauge

# === Usage ===
db_pool_active = Gauge(
    "db_pool_active_connections",
    "Active database connections"
)
cache_memory_bytes = Gauge(
    "cache_memory_used_bytes",
    "Cache memory usage"
)

# === Saturation ===
db_pool_pending = Gauge(
    "db_pool_pending_requests",
    "Requests waiting for a DB connection"
)
thread_pool_queue_size = Gauge(
    "thread_pool_queue_size",
    "Tasks waiting in thread pool queue"
)

# === Error ===
http_requests_total = Counter(
    "http_requests_total",
    "Total HTTP requests",
    ["method", "endpoint", "status"]
)
business_errors_total = Counter(
    "business_errors_total",
    "Business logic errors",
    ["error_type"]  # payment_failed, inventory_insufficient, ...
)

# === Delay ===
http_duration = Histogram(
    "http_request_duration_seconds",
    "HTTP request duration",
    ["method", "endpoint"],
    buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0]
)
db_query_duration = Histogram(
    "db_query_duration_seconds",
    "Database query duration",
    ["query_type"],  # select, insert, update
    buckets=[0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1.0]
)
upstream_call_duration = Histogram(
    "upstream_call_duration_seconds",
    "Upstream service call duration",
    ["service", "method"],
    buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 5.0, 10.0]
)

4.2 告警规则

groups:
  - name: order-service-used
    rules:
      # Usage: 连接池使用率 > 80%
      - alert: DBPoolHighUsage
        expr: db_pool_active_connections / db_pool_max_connections > 0.8
        for: 5m
        labels: { severity: warning }
        annotations:
          summary: "DB 连接池使用率 > 80%"
          runbook: "https://wiki/runbook/db-pool-high-usage"

      # Saturation: 有请求在排队
      - alert: DBPoolSaturated
        expr: db_pool_pending_requests > 0
        for: 1m
        labels: { severity: critical }
        annotations:
          summary: "DB 连接池饱和,请求排队中"
          runbook: "https://wiki/runbook/db-pool-saturated"

      # Error: 5xx 错误率 > 0.1%
      - alert: HighErrorRate
        expr: |
          sum(rate(http_requests_total{status=~"5.."}[5m]))
          / sum(rate(http_requests_total[5m])) > 0.001
        for: 3m
        labels: { severity: critical }
        annotations:
          summary: "错误率 > 0.1%: {{ $value | humanizePercentage }}"
          runbook: "https://wiki/runbook/high-error-rate"

      # Delay: P99 > 2s
      - alert: HighP99Latency
        expr: |
          histogram_quantile(0.99,
            rate(http_request_duration_seconds_bucket[5m])) > 2.0
        for: 5m
        labels: { severity: warning }
        annotations:
          summary: "P99 延迟 > 2s: {{ $value }}s"
          runbook: "https://wiki/runbook/high-latency"

4.3 Grafana 面板布局

┌─────────────────────────────────────────────────────────┐
│                 订单服务监控面板                            │
├──────────┬──────────┬──────────┬────────────────────────┤
│ QPS      │ 成功率    │ P99 延迟  │ 错误预算剩余           │
│ 1,234/s  │ 99.95%   │ 850ms    │ 72% (31min)           │
├──────────┴──────────┴──────────┴────────────────────────┤
│                                                          │
│  ┌─── Usage ──────────────┐  ┌─── Saturation ────────┐  │
│  │ CPU / Mem / 连接池使用率 │  │ 队列深度 / 等待线程数  │  │
│  └────────────────────────┘  └───────────────────────┘  │
│                                                          │
│  ┌─── Error ──────────────┐  ┌─── Delay ─────────────┐  │
│  │ 错误率趋势 + 错误类型   │  │ P50/P95/P99 延迟趋势  │  │
│  │ 分布 (饼图)             │  │ + 延迟分布热力图       │  │
│  └────────────────────────┘  └───────────────────────┘  │
│                                                          │
│  ┌─── 依赖健康度 ─────────────────────────────────────┐  │
│  │ payment-service: ✅ 99.9% / 120ms                  │  │
│  │ inventory-service: ✅ 99.95% / 45ms                │  │
│  │ notification-service: ⚠️ 99.5% / 350ms             │  │
│  └────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

五、防范胜于救灾:从被动到主动

有了 LMAT 和 USED,我们就有了"看"的能力。但光能看还不够,还要能"防"。

5.1 三道防线

第一道防线:预防(防患于未然)
├── 容量规划:基于 Usage 趋势预测,提前扩容
├── 混沌工程:主动注入故障,验证系统韧性
├── 变更管控:灰度发布 + 自动回滚
└── 代码质量:静态分析 + 单元测试 + 集成测试

第二道防线:发现(早发现早处理)
├── 实时告警:基于 USED 四维度的告警规则
├── 异常检测:基于历史基线的自动异常识别
├── 巡检机制:定期自动化健康检查
└── 值班制度:7×24 on-call 轮值

第三道防线:恢复(快速止血)
├── 自动恢复:熔断、降级、限流、重试
├── 快速回滚:一键回滚到上一个稳定版本
├── 预案执行:预定义的故障处理 SOP
└── 事后复盘:每次故障都要写 Post-Mortem

5.2 故障处理的 OODA 循环

Observe (观察)
    │  告警触发,查看 Metrics 面板
    ▼
Orient (判断)
    │  结合 Trace + Log,定位根因
    ▼
Decide (决策)
    │  选择处置方案:回滚?扩容?降级?
    ▼
Act (行动)
    │  执行操作,验证恢复
    ▼
  回到 Observe,确认问题解决

5.3 Post-Mortem 模板

每次故障都是学习的机会。一份好的 Post-Mortem 应该包含:

## 故障报告:[标题]

### 基本信息
- 时间:2026-03-06 10:15 ~ 10:42 (27 分钟)
- 影响:订单服务错误率升至 5%,约 1200 个订单受影响
- 严重程度:P0

### 时间线
- 10:15 Stripe API 开始返回 503
- 10:18 告警触发:HighErrorRate
- 10:20 On-call 工程师确认问题
- 10:25 决定启用备用支付通道
- 10:30 备用通道上线,错误率开始下降
- 10:42 错误率恢复正常

### 根因
Stripe 侧基础设施故障,导致 API 间歇性返回 503。

### 改进措施
| 措施 | 负责人 | 截止日期 |
|------|--------|---------|
| 支付通道自动故障转移 | @alice | 2026-03-20 |
| 增加 Stripe 健康检查探针 | @bob | 2026-03-13 |
| 补充支付降级的告警规则 | @carol | 2026-03-10 |

### 经验教训
- 我们的告警在 3 分钟内触发,响应及时 ✅
- 但备用支付通道的切换是手动的,耗时 10 分钟 ❌
- 需要实现自动故障转移

六、总结

LMAT —— 怎么观测?
  Log     日志    事后取证的第一现场
  Metrics 度量    实时体检的仪表盘
  Alert   告警    把被动救火变成主动预警
  Trace   追踪    分布式系统的 X 光片

USED —— 观测什么?
  Usage      用量    你用了多少?趋势如何?
  Saturation 饱和度  你还能撑多久?
  Error      错误    什么东西坏了?多严重?
  Delay      延迟    用户等了多久?

LMAT × USED = 完整的服务稳定性度量矩阵

隐患险于明火——线上的隐患(内存泄漏、连接池耗尽、磁盘缓慢增长)比突发故障更危险,因为它们不会触发告警,却会在某个深夜突然爆发。USED 中的 Usage 和 Saturation 就是用来发现这些隐患的。

防范胜于救灾——与其在故障发生后手忙脚乱地排查,不如提前建好 LMAT 体系,让问题在萌芽阶段就被发现。一条好的告警规则,胜过十个 on-call 工程师的通宵排查。

责任重于泰山——服务稳定性不是 SRE 团队的事,是每个工程师的事。写代码时埋好度量点,上线前配好告警规则,出了问题写好 Post-Mortem——这就是对用户负责,对团队负责。

最好的监控系统,不是在故障发生时告诉你"出事了", 而是在故障发生前告诉你"要出事了"。


参考资料:

  • Brendan Gregg, "The USE Method" (https://www.brendangregg.com/usemethod.html)
  • Google SRE Book, Chapter 6: Monitoring Distributed Systems
  • OpenTelemetry Documentation (https://opentelemetry.io/docs/)
  • Tom Wilkie, "The RED Method" (https://grafana.com/blog/2018/08/02/the-red-method-how-to-instrument-your-services/)