指标体系设计:USE、RED、Golden Signals 与业务 KPI
指标(Metrics)是可观测性的三大支柱之一。但”要监控什么指标”这个问题,从来没有一个团队能仅凭直觉给出正确答案。
2012 年 Brendan Gregg 提出 USE、2015 年 Tom Wilkie 提出 RED、2016 年 Google SRE Book 总结 Four Golden Signals——这三套方法论并不是互相取代的关系,而是各自针对不同对象:USE 看资源、RED 看请求、Golden Signals 看服务健康、业务 KPI 看价值。
本文尝试把这四个层次串起来,并给出一份可以直接抄到生产环境的指标清单。
一、为什么需要方法论,而不是”想到什么监控什么”
1.1 没有方法论的指标体系,长什么样
绝大多数团队在起步阶段的监控,都是按”出过什么事故就加什么指标”的方式建设的。这种”事故驱动”的指标体系会在半年内演化成三种典型病态:
第一种:监控盲区。 你监控了 CPU
使用率、内存使用率、HTTP 5xx,但没有监控磁盘 I/O
饱和度、没有监控 TCP
重传率、没有监控文件描述符耗尽。直到某天凌晨三点 MySQL 因为
Too many open files 挂掉,你才发现从来没有 fd
相关告警。
第二种:告警疲劳(Alert Fatigue)。 因为不知道哪些指标重要,运维同学把所有指标都设了告警阈值,一个凌晨收到 200 条告警,值班同学直接静音所有通道,真正的故障告警被淹没。Google SRE Book 第六章给出了一个经验数字:如果一个 on-call 工程师一天收到超过两条需要人工介入的告警,这个告警系统就已经失败了。
第三种:关键指标缺失。 你有 CPU、内存、网络流量、HTTP QPS,但当老板问”为什么昨晚 GMV 掉了 30%“的时候,你只能回答”技术指标一切正常”。因为你从来没有把”支付成功率”这个业务指标接入监控。
这三类问题的共同根因是:没有一个覆盖框架(coverage framework)告诉你”监控应该覆盖哪些维度”。
1.2 方法论的作用:系统性覆盖
方法论的本质是”完备性保证”。当你用 USE 方法论去审视一台服务器的时候,你会被迫为每一个资源(CPU、内存、磁盘、网络、进程、文件描述符、中断……)填满三列:Utilization、Saturation、Errors。如果某一格填不出来,那就是一个盲区,要么是数据没采集,要么是这个维度不存在。
这就像软件测试领域的”等价类划分 + 边界值分析”——方法论不能保证你一定找到 bug,但能保证你不会因为遗漏某一类维度而错过 bug。
1.3 三套方法论的历史脉络
| 年份 | 方法论 | 提出者 | 背景 |
|---|---|---|---|
| 2012 | USE | Brendan Gregg(当时在 Joyent) | 为 Solaris/Linux 系统性能分析总结 |
| 2015 | RED | Tom Wilkie(Weaveworks) | 在 GrafanaCon 2015 演讲中提出,面向微服务 |
| 2016 | Four Golden Signals | Google SRE Team | Site Reliability Engineering 一书正式化 |
时间顺序并非巧合:2012 年的业务形态仍以单机/小集群为主,关注的是资源瓶颈;2015 年容器化和微服务兴起,关注的是请求链路;2016 年 Google 把分布式系统运维的最佳实践系统化,关注的是整个服务健康。
1.4 从阿里双十一看方法论的演进
阿里巴巴在公开的技术分享中描述过双十一压测监控体系的演进(参见 InfoQ、阿里技术公众号等公开资料):早期(2009—2011)主要关注机器资源,仪表盘上全是 CPU、内存曲线;2012 年后引入类似 RED 的接口指标,开始关注服务层面的成功率和 RT;2015 年之后,“业务全景大盘”成为主角,订单创建率、支付成功率、库存扣减成功率被作为一等公民监控。这条演进路径几乎和 USE → RED → Golden Signals → KPI 的层次完全吻合——不是巧合,而是分布式系统复杂度上升的必然结果。
二、USE 方法论
2.1 定义与来源
USE 方法论由 Brendan Gregg 在 2012 年 6 月的博文 The USE Method 中正式提出,后续在 Systems Performance: Enterprise and the Cloud(第一版 2013,第二版 2020)一书中系统展开。其核心主张:
For every resource, check utilization, saturation, and errors.
三字母展开:
- U = Utilization(使用率):资源繁忙的时间百分比。例如 CPU 在过去一分钟内有 75% 的时间在执行非 idle 指令。
- S = Saturation(饱和度):资源无法处理而排队的工作量。例如 CPU run queue 长度、磁盘 I/O 队列深度、内存页面换出速率。
- E = Errors(错误):错误事件计数。例如网卡 CRC 错误、磁盘读写错误、内存 ECC 校正次数。
三者的关系:Utilization 告诉你”资源有多忙”,Saturation 告诉你”资源忙到什么程度开始积压工作”,Errors 告诉你”资源是否出现了物理或协议层面的异常”。
2.2 一个容易混淆的概念:Utilization 100% 不等于 Saturation > 0
这是 USE 方法论最容易被误解的点。考虑一个单核 CPU:当它持续 100% 运行某个单线程程序时,Utilization = 100%,但如果此时 run queue 长度为 1(只有当前一个任务),Saturation = 0。此时 CPU 并未”过载”,只是恰好跑满。
只有当 run queue 长度大于 CPU 核心数,即多个任务在排队等 CPU,才算 Saturation > 0。
这个区分很重要,因为单独看 Utilization 会做出错误的扩容决策。Brendan Gregg 原文中举过一个经典例子:一个批处理任务把 CPU 打到 100%,但系统没有任何延迟感知问题——因为没有其他任务在等;此时盲目扩容毫无意义。
2.3 USE 的适用对象
USE 方法论只适用于资源。Brendan Gregg 在原文中给出的 checklist 包括:
| # | 资源类型 | Utilization | Saturation | Errors |
|---|---|---|---|---|
| 1 | CPU | %busy | run queue length / scheduler latency | machine check exceptions |
| 2 | Memory(容量) | used / total | 页面换出速率、OOM 事件 | ECC 错误计数 |
| 3 | Memory(带宽) | 内存总线占用率 | 停顿周期 | — |
| 4 | 磁盘 I/O | %util(iostat) | avgqu-sz、await | 磁盘错误(SMART) |
| 5 | 磁盘容量 | used / total | — | 文件系统损坏事件 |
| 6 | 网络接口 | 带宽占用率 | TX/RX queue drops | CRC / frame errors |
| 7 | 文件描述符 | used / limit | EMFILE 错误频次 | — |
| 8 | Kernel: tasks | 进程数 / pid_max | fork 失败率 | — |
| 9 | Kernel: inode | used / total | — | — |
| 10 | GPU | compute utilization | memory pressure | ECC errors |
注意这张表的覆盖粒度——每个资源都要填满三列,填不出来就要问自己:是这一维度不存在(比如磁盘容量的 Saturation 没有明确定义),还是只是没采集到?
2.4 USE 的 Prometheus 实现
基于 node_exporter(Prometheus 官方的 Linux
主机 exporter),USE 矩阵的绝大部分格子都能被填满。
CPU Utilization:
# 整机 CPU 使用率(1 - idle 比例)
1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)
CPU Saturation(run queue 与核数比值):
# 负载归一化:load1 / logical_cpu_count
avg(node_load1) by (instance)
/
count(count(node_cpu_seconds_total{mode="idle"}) by (instance, cpu)) by (instance)
这个查询比直接用 node_load1 更有意义:在 8
核机器上 load1 = 4 属于健康,在 2 核机器上 load1 = 4
就是饱和。除以核数之后可以统一阈值(通常 > 1
视为饱和)。
Memory Utilization:
# 已使用内存占比(排除 buffer / cache 可回收部分)
1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes
注意:不要用 MemFree。Linux
倾向于把空闲内存用作 page cache,MemFree
常年接近 0 毫无意义。MemAvailable(内核 3.14+
提供)才是真正”可用”的内存。
Memory Saturation(swap 活动):
# 每秒换出页数;持续 > 0 即饱和
rate(node_vmstat_pswpout[5m])
在禁用 swap 的现代容器环境中,Memory Saturation 表现为 OOM Kill:
# OOM Kill 事件速率
rate(node_vmstat_oom_kill[5m])
Disk I/O Utilization:
# 磁盘繁忙百分比
rate(node_disk_io_time_seconds_total[5m])
Disk I/O Saturation:
# 平均队列深度
rate(node_disk_io_time_weighted_seconds_total[5m])
io_time_weighted
是队列深度对时间的积分,除以采样时间窗口就得到平均队列深度。持续
> 1 表示磁盘有积压。
Network Errors:
# 接收错误速率
rate(node_network_receive_errs_total[5m])
# 丢包速率
rate(node_network_receive_drop_total[5m])
File Descriptor Utilization:
# 整机 fd 使用率
node_filefd_allocated / node_filefd_maximum
进程级 fd 需要单独的 exporter(如
process-exporter)。
2.5 USE 的局限
USE 方法论的威力在于”资源视角”,但它也仅限于资源视角。以下问题 USE 回答不了:
- 服务是否在正常响应用户请求:一台服务器 CPU / 内存 / 磁盘全部低水位,但服务进程死锁,用户请求全部 timeout——USE 全绿。
- 业务是否在赚钱:支付链路上某个风控规则错误地拒绝了 100% 交易,GMV 归零——USE 全绿。
- 请求级别的 SLA:USE 没有”延迟分布”这个维度。
这正是需要 RED 与 Golden Signals 的原因。
三、RED 方法论
3.1 定义与来源
RED 方法论由 Tom Wilkie 在 GrafanaCon 2015 演讲 Monitoring Microservices, the RED Method 中提出。彼时他在 Weaveworks 工作,主持 Cortex 项目(后来演化为 Grafana Mimir)。RED 三字母:
- R = Rate:每秒处理的请求数(request/s)。
- E = Errors:失败请求数(或错误率)。
- D = Duration:请求处理时间的分布(通常以分位数表达)。
Tom Wilkie 在演讲中明确指出:RED 的提出动机是让微服务监控”足够简单以至于每个服务都能做到一致”——一家公司可能有几百个微服务,不可能每个服务都用不同的监控方案。RED 是一种跨服务的标准化接口。
3.2 RED 的对象:请求
RED 只关心”请求/响应”这一范式。它的标准适用对象:
- HTTP / gRPC 服务端
- HTTP / gRPC 客户端(反向)
- 数据库查询(把 SQL 当作”请求”)
- 消息消费者(把消息当作”请求”)
- RPC 调用的任意端
如果一个组件没有清晰的”请求”概念——比如一个常驻后台 worker 按流式处理数据——那么 RED 就不直接适用,可能需要用”处理的消息数 + 处理延迟 + 错误数”的类 RED 形式。
3.3 RED 的 Prometheus 实现
标准的 HTTP RED 依赖三个指标:
http_requests_total Counter tags: service, handler, method, status
http_request_duration_seconds Histogram tags: service, handler, method
Rate:
sum(rate(http_requests_total[5m])) by (service, handler)
Error Rate(失败率):
# 5xx 占比
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
注意分母不能加 status
标签过滤,否则就不是”失败率”而是”5xx
内部分布”了。另外在告警中通常要给分母设置”至少 N
QPS”的门槛,否则低流量服务一条错误就会触发 100% 错误率:
# 仅在 QPS > 1 时评估错误率
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
> 0.01
and
sum(rate(http_requests_total[5m])) by (service) > 1
Duration(p99 延迟):
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, handler, le)
)
这里有一个微妙之处:sum by (..., le)
是必须的——必须保留 le 标签,因为
histogram_quantile 就是靠 le
推算分位数。忘记保留 le 是 PromQL
初学者最常见的错误之一。
3.4 RED 与 SLO 的直接映射
Google SRE 把可用性 SLO 拆成两个维度:
- Availability SLO = 成功请求数 / 总请求数
- Latency SLO = 满足延迟目标的请求比例
对照 RED:
- Availability SLO ← R(分母)与 E(分子)
- Latency SLO ← D 分布中某一分位数与目标阈值的比较
典型的 SLO 定义(伪代码):
slo:
name: checkout-service-availability
objective: 99.9%
window: 30d
sli:
numerator: sum(rate(http_requests_total{service="checkout",status!~"5.."}[1m]))
denominator: sum(rate(http_requests_total{service="checkout"}[1m]))
slo:
name: checkout-service-latency
objective: 99% # 99% 请求 <= 500ms
window: 30d
sli: |
sum(rate(http_request_duration_seconds_bucket{service="checkout",le="0.5"}[1m]))
/
sum(rate(http_request_duration_seconds_count{service="checkout"}[1m]))可以看到:RED 指标是 SLI(Service Level Indicator)的直接原材料。这也是为什么每个微服务都应该有 RED 的根本原因——没有 RED 就没有 SLO。
3.5 RED 的不足
RED 看得见请求,看不见资源。考虑以下场景:
- 订单服务 p99 延迟正常,错误率 0%,但 JVM 老年代已经占到 90%,离 Full GC Stop-The-World 只差一次大请求——USE 能发现,RED 看不到。
- HTTP 200 返回了”金额为 0 的订单”——RED 看不到(状态码是 200),只有业务 KPI 才能发现。
所以 RED 必须和 USE 搭配,并且都上层再叠加业务 KPI。
四、Google Four Golden Signals
4.1 定义与来源
Google SRE Book 第六章 Monitoring Distributed Systems 给出了 Four Golden Signals:
- Latency:请求处理时间。成功请求与失败请求必须分开测量,因为一个快速返回的 500 响应可能掩盖了整个服务的慢问题。
- Traffic:高层业务强度,如 HTTP QPS、数据库 TPS、并发连接数。
- Errors:失败请求速率。明确区分显式错误(HTTP 500、RPC exception)和隐式错误(HTTP 200 + 返回内容不对)。
- Saturation:系统”多满”。强调预测性——最好能回答”按当前趋势,X 天后某资源会满”。
4.2 Golden Signals 和 RED 的关系
对比会发现:
| RED | Golden Signals | 差异 |
|---|---|---|
| Rate | Traffic | 几乎等价,Traffic 更强调业务语义 |
| Errors | Errors | Golden Signals 额外强调”隐式错误” |
| Duration | Latency | Golden Signals 要求拆分成功/失败 |
| — | Saturation | RED 没有,Golden Signals 显式要求 |
Golden Signals = RED + Saturation + 区分成功/失败的延迟。它是 RED 的超集,但视角更偏 SRE——不只要看请求成功率,还要看”未来会不会出事”。
4.3 Saturation 的 Prometheus 实现
Saturation 并没有统一的 PromQL 表达式,因为每种资源的”多满”定义不同。实践中常见做法:
CPU:
# load per core,建议阈值:> 1
avg(node_load1) by (instance)
/
count(count(node_cpu_seconds_total{mode="idle"}) by (instance, cpu)) by (instance)
应用层连接池:
# 连接池使用率,建议阈值:> 0.8
sum(db_pool_in_use_connections) by (service)
/
sum(db_pool_max_connections) by (service)
消息队列积压(典型的 Saturation):
kafka_consumergroup_lag{group="order-consumer"}
磁盘容量的预测性告警(Golden Signals 强调的”N 天后会满”):
# predict_linear 根据过去 6 小时趋势预测 4 天后的值
predict_linear(node_filesystem_avail_bytes[6h], 4 * 24 * 3600) < 0
这是 Prometheus predict_linear
函数的经典用法——给线性增长的指标做简单外推。对于容量类指标(磁盘、证书到期天数、内存缓慢泄漏)非常有效。
4.4 区分成功 / 失败的延迟
Google SRE Book 强调”快速失败的请求会拉低延迟指标”。标准做法是分开两个 Histogram:
# 成功请求 p99
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{status!~"5.."}[5m])) by (le))
# 失败请求 p99
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket{status=~"5.."}[5m])) by (le))
或者直接在埋点阶段就把 status 作为
label——但要注意基数(见第九节)。
五、三者适用对象对比
5.1 分层架构示意
从下往上:
┌─────────────────────────────────────────┐
│ Business KPI 业务价值指标 │ ← 业务埋点
│ 订单成功率、GMV、DAU、转化率 │
├─────────────────────────────────────────┤
│ Service Layer 服务层 │ ← RED / Golden Signals
│ HTTP 请求、gRPC 调用、DB 查询 │
├─────────────────────────────────────────┤
│ Infrastructure 基础设施层 │ ← USE
│ CPU、内存、磁盘、网络、FD、内核 │
└─────────────────────────────────────────┘
- USE 独占 Infrastructure 层。
- RED 独占 Service 层。
- Golden Signals 跨越 Service 层和 Infrastructure 层(Saturation 需要看资源)。
- Business KPI 独占最顶层,通常需要自定义埋点。
5.2 覆盖矩阵
| 组件 | 方法论 | 关键指标 | Prometheus 实现示例 |
|---|---|---|---|
| Linux 主机 CPU | USE | 使用率 | 1 - rate(node_cpu_seconds_total{mode="idle"}[5m]) |
| Linux 主机 CPU | USE | 饱和度 | node_load1 / cpu_count |
| Linux 主机 内存 | USE | 使用率 | 1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes |
| Linux 主机 磁盘 | USE | I/O util | rate(node_disk_io_time_seconds_total[5m]) |
| Linux 主机 网络 | USE | 错误 | rate(node_network_receive_errs_total[5m]) |
| Kubernetes Node | USE | Pod 密度 | kube_node_status_allocatable{resource="pods"} - sum(kube_pod_info) by (node) |
| HTTP 服务 | RED | Rate | sum(rate(http_requests_total[5m])) by (service) |
| HTTP 服务 | RED | Errors | ... status=~"5.." ... / ... |
| HTTP 服务 | RED | Duration | histogram_quantile(0.99, ...) |
| gRPC 服务 | RED | Rate | sum(rate(grpc_server_handled_total[5m])) |
| gRPC 服务 | RED | Errors | ... grpc_code!="OK" ... |
| MySQL | USE+RED | 连接饱和度 | mysql_global_status_threads_running / mysql_global_variables_max_connections |
| MySQL | RED | 查询 QPS | rate(mysql_global_status_queries[5m]) |
| Redis | USE | 内存使用率 | redis_memory_used_bytes / redis_memory_max_bytes |
| Redis | RED | 命令 QPS | rate(redis_commands_processed_total[5m]) |
| Kafka | Golden | Lag(饱和度) | kafka_consumergroup_lag |
| Kafka | RED | 生产 QPS | rate(kafka_server_brokertopicmetrics_messagesin_total[5m]) |
| JVM 应用 | USE | 堆使用率 | jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"} |
| JVM 应用 | USE | GC 耗时 | rate(jvm_gc_pause_seconds_sum[5m]) |
| 订单服务 | KPI | 订单成功率 | sum(rate(orders_created_total{status="success"}[5m])) / sum(rate(orders_created_total[5m])) |
| 支付服务 | KPI | 支付失败率 | sum(rate(payment_total{result="failure"}[5m])) / sum(rate(payment_total[5m])) |
5.3 怎么判断”该用哪一个”
实用决策树:
- 这是一个”资源”吗?(CPU、内存、磁盘……)→ USE
- 这是一个”请求响应”组件吗?→ RED
- 你需要预测性指标或从 SRE 视角建 SLO?→ Golden Signals
- 老板或 PM 会看这个指标吗?→ Business KPI
大多数生产服务同时需要前三者,关键业务服务还必须叠加第四类。
六、业务 KPI 与技术指标的映射
6.1 为什么纯技术指标不够
2020 年某头部电商平台曾发生过这样一起故障:购物车服务的
RED 指标完全正常(QPS 正常、5xx 为零、p99
延迟正常),但加购接口因为一次配置变更,错误地把所有 SKU
的库存判定为 0,于是每次加购都返回 HTTP 200 +
{"success":false,"msg":"库存不足"}。整整一个多小时,技术监控全绿,业务同学从
GMV 曲线上才发现异常。
这就是”系统健康 ≠ 业务健康”的典型案例。HTTP 200 可以掩盖一切业务错误;如果不把业务状态埋点到监控系统,永远发现不了。
6.2 典型映射
| 技术指标(RED / USE) | 业务 KPI | 联动关系 |
|---|---|---|
| 订单服务 HTTP 200 成功率 | 订单创建成功率 | 近似一致,但需要排除”业务失败的 200” |
| 支付网关 5xx 错误率 | 支付失败率 | 5xx 是支付失败的子集 |
| 搜索服务 p99 | 搜索结果呈现时间 | 延迟越长点击率越低 |
| Redis 缓存命中率 | 商品详情页加载耗时 | 命中率低 → 降级到 DB → 延迟上升 |
| Kafka lag | 订单履约时效 | lag 大 → 订单状态更新慢 → 用户感知变慢 |
6.3 美团公开的联动案例
美团技术团队在《美团监控平台的高可用架构实践》(2021)一文中提到,其监控平台”CAT + 天网 + Mt-Falcon”不仅采集技术指标,还把关键业务指标(订单、优惠券核销、骑手接单成功率)与技术指标关联展示。当某个 IDC 的订单创建率跌幅超过阈值时,平台会自动关联该 IDC 的基础设施指标和核心服务 RED 指标进行根因分析。这一设计本质上就是”Business KPI 作为告警入口,技术指标作为根因证据”的典型实现。
6.4 SLI 的精确定义
Google SRE Workbook 对 SLI(Service Level Indicator)的定义值得抄一遍:
SLI 是一个精心挑选的、能准确反映用户体验的技术指标。它必须满足:可量化、可比较、随服务质量单调变化。
不是所有技术指标都是 SLI。CPU 使用率是技术指标但不是 SLI——因为”CPU 使用率高”和”用户体验差”没有单调关系。HTTP 成功率是典型 SLI——成功率降低直接意味着用户体验变差。
业务 KPI 往往就是最佳的 SLI 候选:用户不关心你的 CPU,但关心能不能下单、能不能支付。
6.5 业务 KPI 的 Prometheus 实现
业务指标通常需要显式埋点。以 Go 语言订单服务为例:
package orders
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
ordersCreatedTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Namespace: "myshop",
Subsystem: "orders",
Name: "created_total",
Help: "Total number of order creation attempts.",
},
[]string{"channel", "result"}, // channel: app/web/h5; result: success/business_fail/system_fail
)
ordersAmountTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Namespace: "myshop",
Subsystem: "orders",
Name: "amount_cents_total",
Help: "GMV in cents (only counted on success).",
},
[]string{"channel"},
)
)
func CreateOrder(ctx context.Context, req *CreateOrderRequest) (*Order, error) {
order, err := doCreateOrder(ctx, req)
if err != nil {
if isBusinessError(err) {
ordersCreatedTotal.WithLabelValues(req.Channel, "business_fail").Inc()
} else {
ordersCreatedTotal.WithLabelValues(req.Channel, "system_fail").Inc()
}
return nil, err
}
ordersCreatedTotal.WithLabelValues(req.Channel, "success").Inc()
ordersAmountTotal.WithLabelValues(req.Channel).Add(float64(order.AmountCents))
return order, nil
}对应的 Prometheus 查询:
# 订单成功率(1 分钟窗口)
sum(rate(myshop_orders_created_total{result="success"}[1m]))
/
sum(rate(myshop_orders_created_total[1m]))
# 实时 GMV 速率(元/秒)
sum(rate(myshop_orders_amount_cents_total[1m])) / 100
6.6 业务指标的特殊性
相比 RED / USE,业务 KPI 有几个需要特别注意的特性:
- 时间窗口更长:技术指标常用 1m / 5m 滚动窗口,业务 KPI 常用小时级、日级——因为业务波动有自然周期(早高峰、工作日、月末)。
- 需要多源 join:一个完整的业务 KPI
可能要从交易系统、履约系统、风控系统各自埋点的指标组合得到,PromQL
的
group_left/group_right很常用。 - 同比 / 环比更重要:业务 KPI
的绝对值没有意义,要和昨日同时段、上周同时段比较。Prometheus
的
offset修饰符(例如rate(metric[5m] offset 1w))就是为此设计。
七、Prometheus 指标命名规范
7.1 标准格式
Prometheus 官方文档 Best Practices for Naming 推荐的指标名格式:
<namespace>_<subsystem>_<name>_<unit>
namespace:公司/组织/应用前缀,如myshop、linkerd。目的是避免和第三方指标命名冲突。subsystem:子系统,例如http、grpc、database、cache。name:指标本身的名字,描述被测量的对象。unit:单位后缀(详见 7.3)。
7.2 Label(标签)的命名
Label 名使用
snake_case,不放入指标名中:
- 错误:
http_requests_POST_5xx_total - 正确:
http_requests_total{method="POST",status=~"5.."}
为什么?因为 Prometheus 查询依赖 label 做聚合。如果你把 method 写进指标名,就无法用一个查询同时统计所有 method。
7.3 单位后缀规范
| 类型 | 后缀 | 示例 |
|---|---|---|
| Counter | _total |
http_requests_total |
| 秒 | _seconds |
http_request_duration_seconds |
| 字节 | _bytes |
process_resident_memory_bytes |
| 比例 | _ratio |
cpu_usage_ratio |
| 信息 metric | _info |
kubernetes_pod_info |
关键约定:时间一律用秒(_seconds),不用毫秒。字节一律用字节(_bytes),不用
KB / MB。这个约定是为了避免聚合时单位转换错误。
7.4 四种指标类型的命名
| 类型 | 命名约定 | 适用场景 |
|---|---|---|
| Counter | 必须以 _total 结尾 |
单调递增的累计值(请求数、错误数、字节数) |
| Gauge | 无特殊后缀,常用名词 | 瞬时值(队列长度、连接数、温度) |
| Histogram | 以 _seconds / _bytes
结尾,自动生成
_bucket、_sum、_count |
分布类,用于延迟、包大小 |
| Summary | 同 Histogram | 同 Histogram,但客户端计算分位数 |
7.5 好的命名 vs 坏的命名对比
| 坏的命名 | 好的命名 | 问题 |
|---|---|---|
api_latency |
http_request_duration_seconds |
没有单位;缺少子系统前缀 |
errors |
http_requests_total{status=~"5.."} |
不应该为错误单独建指标,用 label |
userCount |
users_active |
驼峰命名,应用 snake_case |
requests_per_second |
http_requests_total |
Counter 应该记录总量,rate 在查询时计算 |
cpu_usage_percentage |
cpu_usage_ratio (0-1) |
比例建议用 0-1 而非 0-100 |
memory_mb |
process_resident_memory_bytes |
统一用字节 |
http_GET_requests_total |
http_requests_total{method="GET"} |
method 应该是 label |
7.6 标准 HTTP 指标命名
这是 OpenMetrics 和 Prometheus 社区事实上的标准:
http_requests_total Counter labels: method, status, handler
http_request_duration_seconds_bucket Histogram labels: method, handler, le
http_request_duration_seconds_sum Counter labels: method, handler
http_request_duration_seconds_count Counter labels: method, handler
http_request_size_bytes_bucket Histogram labels: method, handler, le
http_response_size_bytes_bucket Histogram labels: method, handler, le
大多数框架(Spring Boot Actuator、Go 的
promhttp、Python 的
prometheus_client)都遵循这套命名,跨服务聚合才成为可能。
八、Histogram vs Summary 的选择
8.1 为什么需要分布类指标
延迟只给平均值会骗人:1000 次请求,999 次 10ms,1 次 10 秒,平均 20ms——看起来不错,但那 1 次 10 秒的用户体验是灾难。这就是为什么需要分位数。
8.2 Histogram:客户端分桶,服务端估算
Histogram 在客户端把观测值分到预定义的 bucket 中,每个
bucket 对应一个 Counter。例如定义 bucket
[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10](单位:秒),每次观测都给”小于等于某个
bucket 上界”的所有 Counter +1。
服务端的 histogram_quantile
通过插值估算分位数。
关键优势:Histogram 可以跨实例聚合。因为 bucket 都是线性可加的 Counter,所以:
# 把 10 个实例的延迟合起来计算 p99
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
这在微服务环境下至关重要——一个服务跑在 50 个 Pod 上,你想要的是”整个服务的 p99”,而不是每个 Pod 单独的 p99。
8.3 Histogram Bucket 设计原则
Bucket 设计是 Histogram 最容易做错的环节。经验法则:
- 围绕 SLO 设置:如果 SLO 是 p99 < 500ms,bucket 必须在 500ms 附近细粒度覆盖(例如 300ms、400ms、500ms、600ms、800ms)。
- 指数增长:在感兴趣的区间之外,bucket 以大约 2x 间距扩展。
- 覆盖极端值:最大 bucket
要覆盖到可能出现的极慢请求(10s、30s),否则超出最大 bucket
的请求会被计入
+Inf,分位数计算会高估。 - 数量控制:每个 Histogram 的 bucket 数量直接决定时间序列数,典型值 10—15 个 bucket 即可。
一个好的 HTTP 延迟 bucket 示例:
var DefaultHTTPLatencyBuckets = []float64{
0.005, 0.01, 0.025, 0.05, 0.1,
0.25, 0.5, 1, 2.5, 5, 10,
}8.4 Summary:客户端分位数
Summary 在客户端直接计算分位数(通常用 t-digest 或 CKMS 算法),服务端直接读取数值。
http_request_duration_seconds{quantile="0.5"} 0.032
http_request_duration_seconds{quantile="0.9"} 0.125
http_request_duration_seconds{quantile="0.99"} 0.850
关键限制:Summary
不能跨实例聚合。分位数数学上不可加,avg(p99)
不是”整体 p99”——这是第十节会专门讨论的工程坑。
8.5 选型决策
| 维度 | Histogram | Summary |
|---|---|---|
| 跨实例聚合 | ✅ 支持 | ❌ 不支持 |
| 分位数精度 | 取决于 bucket 设计,近似值 | 客户端精确计算 |
| 客户端开销 | 低(只是 counter 递增) | 较高(滑动窗口 + 估算算法) |
| 服务端开销 | 较高(每个 bucket 一条时间序列) | 低 |
| 可以改分位数 | ✅ 查询时决定 | ❌ 埋点时决定 |
| 99% 的微服务场景 | ✅ 首选 | — |
| 单实例精确分位数 | — | ✅ |
结论:绝大多数微服务场景选 Histogram,不要犹豫。Summary 只在”单进程精确分位数”这个小众场景有优势。
8.6 Native Histogram(Prometheus 2.40+)
Prometheus 2.40(2022 年 11 月)引入了 Native Histogram(也叫 Sparse Histogram)。它解决了经典 Histogram 的核心痛点:bucket 需要预先设计。
Native Histogram 使用指数 bucket 方案(schema),bucket 数量和边界自动按观测值范围调整,可以做到”精度和空间都可配置”。在 Grafana Mimir 和高基数场景下,Native Histogram 显著降低了 series 数量。
不过截至 2024 年,Native Histogram 在 Grafana / Alertmanager 生态中的工具链支持仍在完善,生产环境采用前建议先小范围验证。
8.7 三语言埋点示例
Go(官方
client_golang):
import "github.com/prometheus/client_golang/prometheus/promauto"
var httpDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "myshop",
Subsystem: "http",
Name: "request_duration_seconds",
Help: "HTTP request latencies.",
Buckets: []float64{0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10},
},
[]string{"method", "handler", "status"},
)
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
rw := &responseWriter{ResponseWriter: w, status: 200}
next.ServeHTTP(rw, r)
httpDuration.WithLabelValues(
r.Method,
normalizeHandler(r.URL.Path),
strconv.Itoa(rw.status),
).Observe(time.Since(start).Seconds())
})
}Java(Micrometer):
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
@Component
public class HttpTimingAspect {
private final MeterRegistry registry;
public HttpTimingAspect(MeterRegistry registry) {
this.registry = registry;
}
public Object around(ProceedingJoinPoint pjp, String handler) throws Throwable {
Timer.Sample sample = Timer.start(registry);
String status = "200";
try {
return pjp.proceed();
} catch (Exception e) {
status = "500";
throw e;
} finally {
sample.stop(Timer.builder("myshop.http.request.duration")
.tag("handler", handler)
.tag("status", status)
.publishPercentileHistogram() // 生成 Histogram 而不是 Summary
.serviceLevelObjectives(
Duration.ofMillis(100),
Duration.ofMillis(500),
Duration.ofSeconds(1))
.register(registry));
}
}
}Micrometer 默认使用 Summary(它叫 “client-side
percentile”),一定要显式调用
publishPercentileHistogram()
才会导出成 Prometheus Histogram,否则跨实例聚合会失效。这是
Spring Boot + Prometheus 最常见的陷阱之一。
Python(prometheus_client):
from prometheus_client import Histogram, CollectorRegistry
import time
HTTP_DURATION = Histogram(
'myshop_http_request_duration_seconds',
'HTTP request latency',
['method', 'handler', 'status'],
buckets=(0.005, 0.01, 0.025, 0.05, 0.1,
0.25, 0.5, 1, 2.5, 5, 10),
)
def middleware(app):
def wrapper(environ, start_response):
start = time.perf_counter()
status_code = {'value': '200'}
def custom_start_response(status, headers, exc_info=None):
status_code['value'] = status.split(' ')[0]
return start_response(status, headers, exc_info)
try:
return app(environ, custom_start_response)
finally:
HTTP_DURATION.labels(
method=environ['REQUEST_METHOD'],
handler=normalize_path(environ['PATH_INFO']),
status=status_code['value'],
).observe(time.perf_counter() - start)
return wrapper九、基数(Cardinality)治理
9.1 基数的定义
一个 metric 的时间序列数量 = 所有 label 值组合的笛卡尔积。
举例:
http_requests_total{method, status, handler, service}
method: 5 种(GET, POST, PUT, DELETE, PATCH)status: 20 种(200, 201, 301, 400, 401, …, 504)handler: 50 种service: 10 种
总时间序列数 = 5 × 20 × 50 × 10 = 50000
这是一个 metric,如果有 50 个 metric,就是 250 万 series。Prometheus 单机建议 series 总量控制在 1000 万以内。
9.2 高基数的典型错误
凡是把”值域无限或接近无限”的字段做 label,都是基数灾难:
| label 字段 | 值域规模 | 结果 |
|---|---|---|
user_id |
百万级 | 灾难 |
request_id / trace_id |
每次请求一个 | 秒级爆炸 |
session_id |
在线用户数 | 灾难 |
| 原始 URL path(含 ID) | 接口数 × ID 数 | 灾难 |
| 错误堆栈 hash | 理论无限 | 灾难 |
| 时间戳字符串 | 无限 | 灾难 |
正确的做法是把这些信息作为”结构化日志”或”Trace span attribute”,而不是作为 metric label。
9.3 线上事故案例
2020 年国内某大厂的一次公开事后复盘(脱敏整理):
- 业务组某个 HTTP 接口的 URL 包含商品 ID,如
/product/12345678/detail - 监控 SDK 把完整 URL 作为
pathlabel 上报 - 商品总数约 5000 万,活跃访问商品约 100 万
- 单个 endpoint 的 series 数 = 100 万(active paths) × 2(method) × 15(status) = 3000 万 series
- Prometheus 内存从 32GB 迅速涨到 128GB,OOM 重启
- 期间业务告警完全失效,监控中断约 2 小时,事后复盘将该次事故定级为 P1
这个案例在业内流传甚广,核心教训只有一句话:任何进入 label 的字段,都要先证明它的值域有限且可控。
9.4 防御策略
策略 1:label 值审查
埋点时问三个问题:
- 这个 label 的可能取值集合能列出来吗?
- 取值数量是否随业务增长?
- 如果新增一个取值是否需要修改代码?
全部”是 / 否 / 是”才能作为
label。典型正例:status(HTTP
状态码枚举)、method(HTTP
方法枚举)、region(机房枚举)。典型反例:user_id、url、err_msg。
策略 2:路径规范化(path normalization)
HTTP handler 要把动态部分替换成模板:
func normalizeHandler(path string) string {
// /product/12345/detail → /product/:id/detail
re := regexp.MustCompile(`/\d+(/|$)`)
return re.ReplaceAllString(path, "/:id$1")
}现代 Web 框架(Gin、Spring、FastAPI)都可以拿到路由模板,优先使用框架提供的模板而不是原始 URL。
策略 3:Recording rules 预聚合
对高基数的原始指标预聚合成低基数的中间指标:
# prometheus.rules.yml
groups:
- name: http_aggregations
interval: 30s
rules:
- record: service:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (service, status)
- record: service:http_request_duration_seconds:p99
expr: histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le))Dashboard 和告警应该优先消费 service:*
命名空间的聚合指标,而不是原始指标。这既能降低查询压力,也能让上层用户免于接触高基数。
策略 4:series 数量硬限制
Prometheus 启动参数:
prometheus \
--storage.tsdb.retention.time=15d \
--query.max-samples=50000000 \
--web.enable-admin-api自 Prometheus 2.24 起,可以通过 limits
配置块为单个 scrape target 设置 series 限制:
scrape_configs:
- job_name: 'app'
sample_limit: 10000 # 每次 scrape 最多 10000 个 sample
label_limit: 30 # 单个 metric 最多 30 个 label
label_name_length_limit: 200
label_value_length_limit: 200策略 5:基数巡检
定期检查各 metric 的 series 数:
# 查询所有 metric 名
curl -s http://prometheus:9090/api/v1/label/__name__/values | jq '.data | length'
# 查询单个 metric 的 series 数(需要 Prometheus 2.20+)
curl -sG http://prometheus:9090/api/v1/query \
--data-urlencode 'query=count({__name__="http_requests_total"})'
# 列出 series 数最高的 top 10 metric
curl -s http://prometheus:9090/api/v1/status/tsdb | \
jq '.data.seriesCountByMetricName[:10]'Prometheus 自带的 /api/v1/status/tsdb
接口直接返回 top 10 高基数
metric,应该把它接入日常巡检。
策略 6:Mimirtool cardinality check
Grafana Labs 的 mimirtool
提供专门的基数分析命令:
mimirtool analyze prometheus \
--address=http://prometheus:9090 \
--id=tenant1 \
--output=cardinality.json
mimirtool analyze dashboard \
--output-dir=./report \
dashboards/*.json它不仅能找出高基数 metric,还能分析 dashboard 和 rule 文件里未使用的 metric——未使用的指标是纯粹的成本浪费。
9.5 基数预算管理
成熟团队会把 series 配额作为”监控成本”分配给各业务团队:
| 团队 | series 配额 | 当前使用 | 使用率 |
|---|---|---|---|
| 订单 | 1,000,000 | 820,000 | 82% |
| 支付 | 500,000 | 510,000 | 102% ← 告警 |
| 风控 | 300,000 | 150,000 | 50% |
配合告警规则:
groups:
- name: cardinality_budget
rules:
- alert: TeamCardinalityBudgetExceeded
expr: |
sum by (team) (
count by (team, __name__) ({__name__=~".+"})
)
>
on(team) group_left team_cardinality_budget
for: 1h
annotations:
summary: "Team {{ $labels.team }} exceeded cardinality budget"十、工程坑点
真实踩过的坑(脱敏整理):
10.1 把 userID 写成 label
某电商 SaaS 的用户行为埋点:
// 错误
userActionCounter.WithLabelValues(userID, action).Inc()- 100 万 DAU × 50 个 action = 5000 万 series
- 每天凌晨 0 点执行的 TSDB block compaction 触发 Prometheus 长达 10 分钟的 GC 停顿
- 一周后 Prometheus 主进程 OOM,监控系统失联
- 修复:把
userID移出 label,改为日志 + ClickHouse 方案;label 只保留action和枚举值的user_tier(付费 / 免费)
教训:metric label 只放枚举值,高基数数据用日志或 APM。
10.2 直方图桶设错
SLO 要求:订单创建 p99 < 500ms。
错误的 bucket 定义:
Buckets: []float64{0.1, 1, 10} // 100ms, 1s, 10s当 p99 处于 100ms 和 1s
之间时,histogram_quantile 插值得到的值在 100ms
到 1000ms 之间线性分布——这意味着无论真实 p99 是 200ms 还是
800ms,计算结果的误差都可能达到数百毫秒。SLO
告警要么一直触发要么一直不触发,完全失去意义。
修复:重新设计 bucket,在 SLO 目标附近细粒度覆盖:
Buckets: []float64{0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 0.75, 1, 2, 5, 10}教训:bucket 必须覆盖 SLO 目标且前后有细粒度梯度。
10.3 Counter Reset 没处理
某 Python 服务用 gunicorn 启动多
worker,每次热重启 Counter
归零。监控系统直接对原始值做差分:
# 错误:直接用当前值 - 上次值
delta = current_value - last_value当
current_value < last_value(因为重启)时,delta
变成负数,错误率”突增”触发告警。
修复方式(二选一): - 使用 Prometheus
rate() 或
increase(),它们内部会自动处理 counter reset -
自己实现差分时,判断 current < last 则把
current 当作从 0 开始计算
if current < last:
delta = current # counter reset
else:
delta = current - last教训:永远用 rate() /
increase(),不要自己做 Counter 差分。
10.4 Label 命名不一致
同一个微服务,Java SDK 上报的 label 是
serviceName,Go SDK 上报的 label 是
service_name,Python SDK 上报的 label 是
service。PromQL 聚合:
sum(rate(http_requests_total[5m])) by (service)
只聚合了 Python 服务的数据,Java 和 Go 的请求全部被当作”空 service”——Grafana 图上有一条巨大的”未知”曲线,但没人注意到。
修复:制定公司级监控 SDK 规范,强制
label 命名约定;CI/CD
中加入静态检查(promtool check metrics +
自定义规则)。
教训:label 命名必须企业级统一,一次规范,全员遵守。
10.5 Recording rules 滞后建设
一个业务 dashboard 有 30 个 panel,每个 panel 查询的都是原始高基数指标:
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, handler, le))
每次打开 dashboard 都要扫描 5 分钟窗口 × 数十万 series × 30 个 panel。Prometheus CPU 打满,其他查询 timeout。
修复:把每个 panel 的聚合结果固化为 recording rule,interval 30s:
- record: service:http_request_duration_seconds:p99:5m
expr: histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, handler, le))Dashboard 改为消费
service:http_request_duration_seconds:p99:5m,查询瞬间由”扫描数十万
series”降为”读取数千条预聚合序列”,CPU 消耗降到原来的
1/50。
教训:任何在 dashboard / 告警中出现 2 次以上的复杂查询,都应该有 recording rule。
10.6 Summary 跨实例聚合
某 Java 服务用 Micrometer 默认配置(Summary)暴露 p99,Grafana 面板里:
avg(http_server_requests_seconds{quantile="0.99"}) by (service)
这个查询在数学上毫无意义。分位数不可平均:10 个实例各自 p99 = 500ms,不代表整体 p99 = 500ms。极端情况下 10 个实例 p99 = 500ms,但整体 p99 可能是 2000ms(因为最慢那 1% 请求集中在某个实例上)。
修复:切换到 Histogram(Micrometer 调用
publishPercentileHistogram()),然后:
histogram_quantile(0.99,
sum(rate(http_server_requests_seconds_bucket[5m])) by (service, le))
教训: - 跨实例聚合一律用 Histogram -
Micrometer 默认是 Summary,务必显式开启
Histogram - 代码评审重点检查
quantile="x" 外层是否有 avg /
sum 等聚合——这是红旗信号
十一、选型建议与可抄的指标清单
11.1 不同规模团队的指标体系建议
| 团队规模 | 阶段建议 | 重点方法论 | 工具栈建议 |
|---|---|---|---|
| < 10 人,< 5 服务 | MVP:USE + 核心 RED | USE 为主,关键服务加 RED | 单 Prometheus + Grafana,15 天保留 |
| 10—50 人,10—30 服务 | 全量 RED + 业务 KPI 埋点 | USE + RED + 核心业务 KPI | Prometheus HA + Thanos / VictoriaMetrics,30 天保留 |
| 50—200 人,30—200 服务 | 引入 Golden Signals 与 SLO | 全部四层 | Mimir / VictoriaMetrics cluster,90 天保留 |
| 200+ 人,200+ 服务 | 基数治理、多租户、成本优化 | 全部四层 + 基数预算 | Mimir + cardinality 巡检 + recording rules 自动化 |
11.2 基础设施层(USE)指标清单
# Node 层(node_exporter)
- node_cpu_usage_ratio # U: CPU 使用率
- node_cpu_saturation # S: load1 / cores
- node_memory_usage_ratio # U: 内存使用率
- node_memory_swap_rate # S: 换页速率
- node_disk_io_util_ratio # U: 磁盘繁忙率
- node_disk_io_queue_depth # S: I/O 队列
- node_disk_error_rate # E: 磁盘错误
- node_network_bandwidth_usage_ratio # U: 带宽占用
- node_network_drop_rate # S: 丢包
- node_network_error_rate # E: 网卡错误
- node_filefd_usage_ratio # U: fd 使用率
# Kubernetes 层
- kube_pod_status_phase # 状态
- kube_pod_container_status_restarts # 重启次数
- kube_node_status_allocatable_pods # 容量11.3 服务层(RED)必备指标
每个服务至少要有这 5 个指标(可以由 SDK / middleware 自动生成):
| 指标名 | 类型 | 必备 label | 告警参考 |
|---|---|---|---|
http_requests_total |
Counter | service, method, handler, status | QPS 跌幅 > 50% |
http_request_duration_seconds |
Histogram | service, method, handler | p99 > SLO 目标 |
http_request_size_bytes |
Histogram | service, handler | p99 > 10MB |
http_response_size_bytes |
Histogram | service, handler | p99 > 10MB |
http_in_flight_requests |
Gauge | service | > 实例最大并发 |
11.4 中间件指标清单
MySQL(mysqld_exporter):
# USE
- mysql_global_status_threads_running / max_connections # 连接饱和度
- rate(mysql_global_status_slow_queries[5m]) # 慢查询 E
- mysql_global_status_innodb_row_lock_time_avg # 锁等待 S
# RED
- rate(mysql_global_status_queries[5m]) # QPS
- rate(mysql_global_status_com_select[5m]) # SELECT 速率
- rate(mysql_global_status_handlers_error[5m]) # Handler 错误Redis(redis_exporter):
- redis_memory_used_bytes / redis_memory_max_bytes # U: 内存使用率
- rate(redis_commands_processed_total[5m]) # R: 命令 QPS
- rate(redis_rejected_connections_total[5m]) # E: 拒绝连接
- redis_blocked_clients # S: 阻塞客户端
- rate(redis_keyspace_hits_total[5m])
/ (rate(redis_keyspace_hits_total[5m])
+ rate(redis_keyspace_misses_total[5m])) # 命中率Kafka(kafka_exporter):
- kafka_consumergroup_lag # S: 消费滞后(核心)
- rate(kafka_topic_partition_current_offset[5m]) # R: 生产速率
- kafka_topic_partition_under_replicated_partition # E: 副本未同步
- kafka_brokers # 可用 broker 数11.5 JVM 应用指标
- jvm_memory_used_bytes / jvm_memory_max_bytes # 堆使用率
- rate(jvm_gc_pause_seconds_sum[5m]) # GC 耗时速率
- rate(jvm_gc_pause_seconds_count[5m]) # GC 次数
- jvm_threads_live # 活跃线程
- jvm_threads_deadlocked # 死锁线程
- jvm_classes_loaded # 已加载类11.6 业务 KPI 通用模板
每个业务域至少埋这几类:
# 交易
- business_orders_created_total{channel, result}
- business_orders_amount_cents_total{channel}
- business_payment_total{method, result}
- business_payment_amount_cents_total{method}
# 用户
- business_user_login_total{channel, result}
- business_user_register_total{channel, result}
# 风控
- business_risk_rules_triggered_total{rule, action}
- business_risk_reject_total{reason}对应的核心 SLO:
slo:
- name: order-creation-success
sli: |
sum(rate(business_orders_created_total{result="success"}[5m]))
/
sum(rate(business_orders_created_total[5m]))
objective: 99.95%
window: 30d
- name: payment-success
sli: |
sum(rate(business_payment_total{result="success"}[5m]))
/
sum(rate(business_payment_total[5m]))
objective: 99.99%
window: 30d11.7 推荐的 recording rules 基线
groups:
- name: red_aggregations
interval: 30s
rules:
- record: service:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (service)
- record: service:http_requests:error_rate5m
expr: |
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)
- record: service:http_request_duration_seconds:p50:5m
expr: histogram_quantile(0.5,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le))
- record: service:http_request_duration_seconds:p99:5m
expr: histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (service, le))
- name: use_aggregations
interval: 30s
rules:
- record: instance:node_cpu:usage_ratio5m
expr: 1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)
- record: instance:node_memory:usage_ratio
expr: 1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes
- record: instance:node_disk:io_util_ratio5m
expr: rate(node_disk_io_time_seconds_total[5m])11.8 最终 checklist
建设指标体系时,对着这份清单自查:
能把这份清单过全的团队,指标体系就已经超过 80% 的同行了。
十二、总结
四套方法论覆盖了从资源到业务的完整观测面:
- USE(2012,Brendan Gregg)= 资源视角,回答”硬件 / 内核 / 基础设施有没有瓶颈”。
- RED(2015,Tom Wilkie)= 请求视角,回答”服务在不在正常响应”。
- Golden Signals(2016,Google SRE)= 服务健康视角,回答”服务整体好不好、未来会不会出事”,是 RED 的超集。
- Business KPI = 价值视角,回答”业务到底在不在赚钱”。
构建指标体系不是”想到什么监控什么”,而是:沿着层次结构,逐层用方法论做完备性检查,再叠加基数治理与规范化,最后沉淀成可跨服务复用的标准 SDK + 告警规则。
下一步自然是埋点:谁来埋、埋在哪、埋多细、成本多高。这是下一篇《埋点哲学:粒度、采样、基数爆炸与成本模型》要讨论的内容。
参考资料
- Brendan Gregg, “The USE Method”, https://www.brendangregg.com/usemethod.html, 2012
- Tom Wilkie, “The RED Method: How to Instrument Your Services”, GrafanaCon 2015 演讲
- Niall Murphy, Betsy Beyer, Chris Jones, Jennifer Petoff, Site Reliability Engineering, O’Reilly / Google, 2016, Chapter 6 “Monitoring Distributed Systems”
- Prometheus 官方文档 - “Metric and Label Naming”, https://prometheus.io/docs/practices/naming/
- Prometheus 官方文档 - “Histograms and Summaries”, https://prometheus.io/docs/practices/histograms/
- Charity Majors, Liz Fong-Jones, George Miranda, Observability Engineering, O’Reilly, 2022
- 美团技术团队,《美团监控平台的高可用架构实践》,美团技术博客,2021
- Brendan Gregg, Systems Performance: Enterprise and the Cloud, 2nd ed., Addison-Wesley, 2020
- Rob Skillington, “Taming High Cardinality TSDB Behavior”, Chronosphere Engineering Blog, 2021
- Grafana Labs, “Analyzing metric cardinality with mimirtool”, Grafana Mimir 官方文档
上一篇:可观测性 vs 监控:从 Zabbix/Nagios 到 OpenTelemetry 的二十年
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
可观测性工程
从 Metrics、Logs、Traces 到 Profiling、eBPF、OpenTelemetry 与 SLO 治理,面向中国工程团队的可观测性系统化手册。
【可观测性工程】可观测性全景:Metrics、Logs、Traces、Profiles、Events 五大支柱
从控制论到云原生:拆解可观测性的五大信号支柱,对比监控与可观测性的本质区别,梳理开源/商业/SaaS 分类,以及国内互联网公司三大支柱落地现状与典型工程坑点。
【可观测性工程】Metrics:Prometheus、VictoriaMetrics、Thanos、Mimir、M3
从 Prometheus 架构与数据模型出发,系统梳理 Remote Write、PromQL 进阶、Thanos 全局聚合、Mimir 多租户、VictoriaMetrics 性能、M3DB 原理,以及五者在大规模生产场景下的对比矩阵与迁移实践。
【可观测性工程】时序数据库内核:TSM、TSI、倒排索引与 Gorilla 压缩
深入时序数据库的存储内核:Prometheus TSDB 的 WAL 与块管理、InfluxDB 的 TSM 引擎与 TSI 倒排索引、Gorilla 压缩算法的数学原理、VictoriaMetrics mergeset 架构、ClickHouse MergeTree 作为 metrics 后端,以及国内大厂在 series churn 和 compaction 风暴上踩过的坑。