从 node-exporter 学如何写出可复用的监控指标

在上一篇文章《四个指标,一种哲学:Prometheus 如何用简单模型看透复杂系统》中,我们深入探讨了 Prometheus 的四种指标类型和设计哲学:

  • Counter:只增不减的累积器
  • Gauge:可增可减的快照
  • Histogram:分布的压缩
  • Summary:预计算的结果

理解了指标类型,下一个问题就是:如何把这套哲学落地到实际的 Exporter 设计中?

node-exporter 就是最好的教科书。它用最简单的方式监控 Linux 服务器,却成为整个 Prometheus 生态的标杆。

这篇文章不教你怎么写代码,而是带你理解 node-exporter 背后的设计智慧:

  • 为什么 CPU 时间用 Counter,而不是直接给使用率?
  • 为什么内存要拆成多个 Gauge,而不是一个"使用率"指标?
  • 什么叫"暴露事实,不是观点"?

理解了这些,你就能设计出可复用的监控指标。


目录

  1. 从问题出发:监控到底要回答什么问题
  2. [核心设计:暴露事实,把视图交给 PromQL](#核心设计:暴露事实,把视图交给 PromQL "#2-%E6%A0%B8%E5%BF%83%E8%AE%BE%E8%AE%A1%E6%9A%B4%E9%9C%B2%E4%BA%8B%E5%AE%9E%E6%8A%8A%E8%A7%86%E5%9B%BE%E4%BA%A4%E7%BB%99-promql")
  3. 架构剖析:三段式的极简设计
  4. 设计范式:如何复用这套思想
  5. 性能哲学:为什么足够轻量
  6. 下一步:从设计到落地

1. 从问题出发:监控到底要回答什么问题

在设计任何 Exporter 之前,先问一个问题:

如果这台服务器出了问题,你希望监控系统第一时间告诉你什么?

这不是技术问题,是业务问题。

1.1 USE 方法:三个核心维度

Google SRE 总结的 USE 方法给了一个框架:

  • Utilization(利用率):资源用了多少?

    • CPU 是否接近 100%?
    • 内存是否吃紧?
    • 磁盘是否快满?
  • Saturation(饱和度):是否有排队/拥塞?

    • 磁盘 IO 队列是否堆积?
    • 网络带宽是否打满?
  • Errors(错误):是否有明显异常?

    • 网络错误包
    • 磁盘错误

这三个维度回答了"系统是否健康"的问题。

1.2 四个观察窗口

对 Linux 服务器来说,具体就是四个维度:

CPU:这台机器忙不忙?

  • 忙在 user 模式(跑应用)?
  • 忙在 system 模式(内核调用)?
  • 还是在等 IO(iowait)?

内存:是否紧张?

  • 真正可用的内存有多少(MemAvailable)?
  • cache/buffer 占了多少?
  • 是否有内存泄漏?

磁盘:是否有风险?

  • 容量是否快满?
  • 未来多久会满(趋势预测)?
  • IO 吞吐是否正常?

网络:是否有瓶颈?

  • 带宽是否打满?
  • 错误率是否异常?

1.3 node-exporter 的价值

你可能写过这样的指标

复制代码
app_cpu_usage_percent 85.3
memory_usage_percent 72.3
disk_usage_percent 60.1

看似直观,但问题在于:

  • 使用率怎么算的?时间窗口是多久?
  • 能按不同维度聚合吗?能看历史趋势吗?
  • 告警阈值怎么定?(72%的内存使用算高吗?要看cache占比)

node-exporter 的做法完全不同

  • 不给你"CPU 使用率",只给你"CPU 累计时间"
  • 不给你"内存使用率",给你"MemTotal"、"MemAvailable"、"Buffers"...
  • 不给你"磁盘使用率",给你"总容量"、"可用容量"

乍一看很麻烦,其实这才是最灵活的设计。

设计哲学:暴露事实,不是观点

  • "CPU 使用率"是观点(需要定义:过去 5 分钟?1 分钟?按核心还是整机?)
  • "CPU 累计时间"是事实(内核记录了多少秒)

观点会限制用户的灵活性,事实才能支撑各种视图。


2. 核心设计:暴露事实,把视图交给 PromQL

这一节承接第一篇,看看 node-exporter 如何把"四种指标类型"的哲学落地到实际设计中。

2.1 CPU:为什么不直接给使用率?

问题

你想知道:CPU 是否繁忙?

常见错误:直接暴露使用率

bash 复制代码
# 不好的设计
cpu_usage_percent 85.5

问题:

  • 使用率是怎么算的?时间窗口是多久?
  • 能否按 CPU 核心分别看?(不能,已经聚合了)
  • 能否看 user/system/iowait 的细分?(不能,已经合并了)
  • 能否调整时间窗口重新计算?(不能,只有当前值)

Linux 给你的事实

/proc/stat 文件记录了每个 CPU 自启动以来,在各种模式下累计的时间(秒):

yaml 复制代码
cpu0 123456 0 56789 987654 1234 0 567 0

这些数字分别是:user、nice、system、idle、iowait...的累计时间。

node-exporter 的选择

用 Counter 暴露这些累计时间:

ini 复制代码
node_cpu_seconds_total{cpu="0", mode="user"} 123456
node_cpu_seconds_total{cpu="0", mode="system"} 56789
node_cpu_seconds_total{cpu="0", mode="idle"} 987654
node_cpu_seconds_total{cpu="0", mode="iowait"} 1234

为什么这样设计?

因为"使用率"是个观点,需要定义:

  1. 时间窗口:最近 5 分钟?1 分钟?10 秒?
  2. 聚合方式:按 CPU 核心?整机平均?
  3. 模式选择:user + system?还是包括 iowait?

一旦在 Exporter 里固化了"使用率",这些选择就被锁死了。用户想看不同视图?没办法。

PromQL:从事实到视图

promql 复制代码
# 整机 CPU 使用率(最近 5 分钟)
100 - (avg by (instance) (
  rate(node_cpu_seconds_total{mode="idle"}[5m])
) * 100)

# 只看 user 模式的使用率
rate(node_cpu_seconds_total{mode="user"}[5m]) * 100

# 按核心分别看
rate(node_cpu_seconds_total{mode="user"}[5m]) * 100

同一份事实,支持无限种视图。

设计智慧

flowchart LR A[事实 累计时间] --> B[用户视图1 5分钟使用率] A --> C[用户视图2 1分钟使用率] A --> D[用户视图3 按核心分别看] style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#ffe1f5 style D fill:#ccffcc

这就是"暴露事实,不是观点"的第一个例子:

  • Exporter:我告诉你事实(累计了多少秒)
  • Prometheus:我帮你计算视图(使用率是多少)
  • 用户:我自由组合(想看什么就看什么)

为什么 CPU 时间用 Counter?

因为"时间"是只增不减的累积量:

  • 系统运行越久,累积时间越多
  • 进程重启时会归零(Counter 允许重置)
  • Prometheus 的 rate() 函数会自动处理重置情况,计算正确的速率

这和第一篇讲的 Counter 设计哲学完全一致。

2.2 内存:为什么不是一个"使用率"?

问题

内存是否紧张?

常见错误:一个"内存使用率"搞定

bash 复制代码
# 不好的设计
memory_usage_percent 72.3

问题:

  • 无法区分 cache/buffer(Linux 会用空闲内存做缓存)
  • 不能用于容量规划(72%算高吗?cache可以随时释放)
  • 告警阈值难定(不同应用对内存的容忍度不同)
  • 无法排查问题(不知道内存都用在哪了)

Linux 给你的事实

/proc/meminfo 提供了一组快照值:

makefile 复制代码
MemTotal:       16384000 kB
MemFree:         2048000 kB
MemAvailable:    8192000 kB
Buffers:          512000 kB
Cached:          4096000 kB
...

node-exporter 的选择

全部用 Gauge 暴露:

复制代码
node_memory_MemTotal_bytes 16777216000
node_memory_MemFree_bytes 2097152000
node_memory_MemAvailable_bytes 8388608000
node_memory_Buffers_bytes 524288000
node_memory_Cached_bytes 4194304000

为什么不直接给一个"内存使用率"?

因为不同场景需要不同的"使用率":

场景 1:快速告警

只关心 MemAvailable:

promql 复制代码
(node_memory_MemAvailable_bytes 
 / node_memory_MemTotal_bytes) * 100 < 10

场景 2:问题排查

需要看 cache 和 buffer 的细节:

promql 复制代码
# Free 内存占比
node_memory_MemFree_bytes / node_memory_MemTotal_bytes

# Cache 占比
node_memory_Cached_bytes / node_memory_MemTotal_bytes

# Buffer 占比
node_memory_Buffers_bytes / node_memory_MemTotal_bytes

场景 3:容量规划

看长期趋势:

promql 复制代码
avg_over_time(node_memory_MemAvailable_bytes[7d])

一个"使用率"指标无法同时支持这三种场景。

设计智慧

这是"用多个 Gauge 表达不同子视图"的例子:

  • 不是一个 Gauge(内存使用率)
  • 而是多个 Gauge(Total、Free、Available、Buffers、Cached)
  • 每个 Gauge 都是一个"事实"
  • 用 PromQL 组合出各种"观点"

为什么用 Gauge?

因为内存是"当前状态":

  • 可以增加(应用申请内存)
  • 可以减少(应用释放内存)
  • 不是累积量,是快照

这和第一篇讲的 Gauge 本质(当前状态)完全一致。

2.3 磁盘:容量和 IO 的分离设计

两类问题

磁盘有两类完全不同的问题:

  1. 容量问题:是否快满?未来多久会满?
  2. IO 问题:吞吐够不够?延迟是否变高?

常见错误:混在一起

bash 复制代码
# 不好的设计
disk_usage_percent 80.5         # 容量
disk_io_busy_percent 60.2       # IO繁忙度

问题:

  • 容量和IO是两个完全不同的维度,不应该都用"百分比"
  • 无法预测未来容量(只有当前值)
  • 无法看IO的细分(读?写?)
  • 无法计算IOPS(只有繁忙度,不知道实际操作数)

设计拆分

容量相关用 Gauge:

ini 复制代码
node_filesystem_size_bytes{mountpoint="/"} 107374182400
node_filesystem_avail_bytes{mountpoint="/"} 53687091200

IO 相关用 Counter:

ini 复制代码
node_disk_read_bytes_total{device="sda"} 123456789
node_disk_written_bytes_total{device="sda"} 987654321
node_disk_reads_completed_total{device="sda"} 12345
node_disk_writes_completed_total{device="sda"} 67890

为什么分开?

因为本质不同:

维度 容量 IO
本质 当前状态 累积事件
类型 Gauge Counter
函数 直接读、predict_linear rate()、increase()

PromQL:从事实到视图

容量使用率:

promql 复制代码
(1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100

预测 4 小时后是否会满:

promql 复制代码
predict_linear(node_filesystem_avail_bytes[1h], 4*3600) < 0

IO 吞吐(MB/s):

promql 复制代码
rate(node_disk_read_bytes_total[5m]) / 1024 / 1024

IOPS:

promql 复制代码
rate(node_disk_reads_completed_total[5m])

设计智慧

一旦识别出"容量"和"流量"是两类本质不同的问题,设计就自然而然了:

  • 容量 → Gauge(当前状态)
  • 流量 → Counter(累积事件)

这是第一篇"抓住本质"哲学的具体应用。

2.4 网络:统统是 Counter

问题

网络带宽是否打满?错误率是否异常?

常见错误:直接给速率和错误率

bash 复制代码
# 不好的设计
network_bandwidth_mbps 850.5       # 当前带宽
network_error_rate 0.02            # 错误率 2%

问题:

  • 带宽是瞬时值还是平均值?时间窗口多久?
  • 错误率的分母是什么?(包数?字节数?)
  • 无法按接口分别看
  • 无法看接收和发送的细分

Linux 给你的事实

/proc/net/dev 提供接口级别的累计统计:

makefile 复制代码
eth0: 123456789 1000000 100 0 0 0 0 0 987654321 800000 50 0 0 0 0 0

分别是:接收字节数、接收包数、接收错误...发送字节数、发送包数、发送错误...

node-exporter 的选择

全部用 Counter:

ini 复制代码
node_network_receive_bytes_total{device="eth0"} 123456789
node_network_receive_packets_total{device="eth0"} 1000000
node_network_receive_errs_total{device="eth0"} 100
node_network_transmit_bytes_total{device="eth0"} 987654321
node_network_transmit_packets_total{device="eth0"} 800000
node_network_transmit_errs_total{device="eth0"} 50

为什么全是 Counter?

因为网络统计都是"累积事件":

  • 收了多少字节(累积)
  • 发了多少包(累积)
  • 发生了多少错误(累积)

没有"当前状态"的概念。

PromQL:从事实到视图

流量速率(Mbps):

promql 复制代码
rate(node_network_receive_bytes_total{device="eth0"}[5m]) * 8 / 1000000

错误率:

promql 复制代码
rate(node_network_receive_errs_total[5m]) 
/ 
rate(node_network_receive_packets_total[5m])

设计智慧

网络监控是"事件类"指标的典型场景:

  • 所有统计都是累积的
  • 关心的是速率和趋势
  • 用 Counter + rate() 自然表达

这再次验证了上一篇文章的结论:事件类 → Counter

2.5 设计总结:从问题到类型的映射

通过 CPU、内存、磁盘、网络四个例子,可以总结出一个通用映射:

问题类型 原始事实 指标类型 典型函数
累积时间 CPU 各模式的累计秒数 Counter rate()
当前状态 内存当前使用量 Gauge 直接读、比例计算
容量资源 磁盘总大小、可用大小 Gauge 比例、predict_linear
累积事件 网络累计字节数、错误数 Counter rate()、increase()

核心设计原则

flowchart TD A[监控问题] --> B{是累积的吗?} B -->|是| C[Counter] B -->|否| D{是当前状态吗?} D -->|是| E[Gauge] D -->|否| F[考虑Histogram] style C fill:#e1f5ff style E fill:#fff4e1 style F fill:#ffe1f5

这个决策树就是 node-exporter 的核心设计智慧,也是上一篇文章中"四种指标类型"在实际设计中的应用。


3. 架构剖析:三段式的极简设计

node-exporter 的架构可以抽象成三段式:

flowchart LR A[OS内核数据] --> B[Collector采集] B --> C[Registry注册] C --> D[HTTP暴露] D --> E[Prometheus抓取] style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#ffe1f5 style D fill:#ccffcc style E fill:#ffccee

3.1 采集层:Collector

每个 Collector 负责一个领域:

  • CPU Collector :读 /proc/stat,解析成 Counter
  • Memory Collector :读 /proc/meminfo,解析成 Gauge
  • Filesystem Collector :读 /proc/mounts + statfs(),解析成 Gauge
  • Network Collector :读 /proc/net/dev,解析成 Counter

Collector 的职责边界

每个 Collector 只做三件事:

  1. 读取 OS 数据:从 /proc、/sys 读取原始数据
  2. 单位换算:比如 kB → bytes
  3. 按规范暴露:命名、类型、标签、单位

不做的事情

  • 不存历史数据
  • 不做复杂计算
  • 不做聚合

这些都交给 Prometheus。

3.2 注册层:Registry

所有 Collector 向同一个 Registry 注册:

go 复制代码
registry := prometheus.NewRegistry()
registry.MustRegister(cpuCollector)
registry.MustRegister(memoryCollector)
registry.MustRegister(filesystemCollector)
registry.MustRegister(networkCollector)

每次 Prometheus 发起 scrape:

  1. HTTP 请求到达 /metrics
  2. Registry 调用所有 Collector 的 Collect() 方法
  3. 收集当前快照
  4. 输出 Prometheus 文本格式

3.3 暴露层:/metrics

HTTP handler 非常简单:

go 复制代码
http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))

输出格式:

ini 复制代码
# HELP node_cpu_seconds_total Seconds the cpus spent in each mode.
# TYPE node_cpu_seconds_total counter
node_cpu_seconds_total{cpu="0",mode="idle"} 123456.78
node_cpu_seconds_total{cpu="0",mode="user"} 45678.90

# HELP node_memory_MemTotal_bytes Memory information field MemTotal_bytes.
# TYPE node_memory_MemTotal_bytes gauge
node_memory_MemTotal_bytes 16777216000

3.4 设计智慧

这个三段式设计体现了几个关键思想:

1. 被动采集,不主动轮询

Exporter 不主动定时采集数据,一切由 Prometheus 的 scrape 驱动:

  • Prometheus 发起请求 → Exporter 才采集
  • 采集频率由 Prometheus 控制(scrape_interval)
  • Exporter 无状态,重启后立即可用

2. 职责分离

复制代码
Collector:负责采集
Registry:负责管理
HTTP Handler:负责暴露

每层只做自己的事,边界清晰。

3. 极简主义

整个 node-exporter 的核心逻辑不超过几百行代码。复杂的部分是:

  • 处理各种 Linux 内核接口的细节
  • 处理各种边界情况(文件不存在、权限不足)
  • 处理各种 Linux 发行版的差异

但核心架构永远是:读取 → 注册 → 暴露


4. 设计范式:如何复用这套思想

理解了 node-exporter,就可以把这套思想复用到任何 Exporter 设计中。

4.1 从问题类型选择指标类型

这是最核心的决策:

监控对象 问题 推荐类型
HTTP 请求数 累积了多少次? Counter
数据库连接数 当前有多少? Gauge
API 响应时间 分布如何? Histogram
错误次数 累积了多少次? Counter
队列长度 当前有多少? Gauge
磁盘使用量 当前用了多少? Gauge

通用规律

  • 资源类(容量、数量、长度)→ Gauge
  • 事件类(请求、错误、字节)→ Counter
  • 延迟类(响应时间、等待时间)→ Histogram

Histogram vs Summary 的选择

当需要监控分布(如响应时间)时:

  • Histogram:在服务端聚合,支持多实例聚合,可以灵活计算任意百分位

    • 优点:可以跨实例聚合,Prometheus 计算百分位
    • 缺点:需要预定义 bucket
    • 推荐:优先使用
  • Summary:在客户端预计算百分位(如 p50、p90、p99)

    • 优点:精确的百分位,无需 bucket,性能更好
    • 缺点:无法跨实例聚合,无法在 Prometheus 中重新计算
    • 使用场景:单实例且性能极端敏感

原则:除非单机性能极端敏感,否则优先用 Histogram。

这就是上一篇文章中讲的"四种类型"在 Exporter 设计中的具体应用。

4.2 暴露原始事实,不是加工观点

这是 node-exporter 最重要的设计原则。

反模式:直接暴露"观点"

bash 复制代码
# 不好的设计
app_cpu_usage_percent 85.5
app_memory_usage_percent 72.3
app_disk_usage_percent 60.1

问题:

  • 使用率是怎么算的?(时间窗口?)
  • 能不能按不同维度聚合?(不能)
  • 能不能看历史趋势?(只能看当前值)

正确:暴露"事实"

ini 复制代码
# 好的设计
app_cpu_seconds_total{mode="user"} 12345
app_cpu_seconds_total{mode="system"} 6789
app_memory_bytes{type="used"} 8589934592
app_memory_bytes{type="total"} 16777216000

然后用 PromQL 组合:

promql 复制代码
# 用户想要的"观点"
rate(app_cpu_seconds_total{mode="user"}[5m]) * 100
app_memory_bytes{type="used"} / app_memory_bytes{type="total"} * 100

4.3 命名、单位、标签的约定

命名规范

xml 复制代码
<namespace>_<subsystem>_<metric>_<unit>

示例:

arduino 复制代码
node_cpu_seconds_total          # namespace=node, metric=cpu, unit=seconds
mysql_queries_total             # namespace=mysql, metric=queries
http_request_duration_seconds   # namespace=http, metric=request_duration, unit=seconds

单位规范

  • 时间:_seconds(不要用 ms)
  • 大小:_bytes(不要用 KB、MB)
  • 比例:_ratio(0-1)或无单位(百分比直接用数字)

标签规范

只用低基数标签:

ini 复制代码
# 好:低基数
http_requests_total{method="GET", status="200"}

# 坏:高基数
http_requests_total{user_id="123456"}

为什么?

高基数标签会导致时间序列爆炸:

yaml 复制代码
100 万用户 × 5 个方法 × 10 个状态码 = 5000 万条时间序列

这会直接把 Prometheus 打爆。

4.4 模块化 Collector:按领域拆分

node-exporter 的模块化设计可以复用:

go 复制代码
// 业务 Exporter 示例
registry := prometheus.NewRegistry()

// 按业务领域拆分 Collector
registry.MustRegister(NewOrderCollector())      // 订单相关指标
registry.MustRegister(NewPaymentCollector())    // 支付相关指标
registry.MustRegister(NewUserCollector())       // 用户相关指标
registry.MustRegister(NewCacheCollector())      // 缓存相关指标

http.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))

每个 Collector 只负责一个领域:

go 复制代码
type OrderCollector struct{}

func (c *OrderCollector) Describe(ch chan<- *prometheus.Desc) {
    // 注册指标描述
}

func (c *OrderCollector) Collect(ch chan<- prometheus.Metric) {
    // 采集当前数据
    totalOrders := getOrderCount()
    ch <- prometheus.MustNewConstMetric(
        orderCountDesc,
        prometheus.CounterValue,
        float64(totalOrders),
    )
}

好处

  • 职责清晰,易于维护
  • 可以单独开关某个 Collector
  • 可以并行采集(如果需要)
  • 方便测试

4.5 极简 Exporter 骨架

一个最简单的 Exporter 骨架:

go 复制代码
package main

import (
    "net/http"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promhttp"
)

// 定义指标
var requestsTotal = prometheus.NewCounterVec(
    prometheus.CounterOpts{
        Name: "app_requests_total",
        Help: "Total number of requests",
    },
    []string{"method", "status"},
)

func init() {
    // 注册指标
    prometheus.MustRegister(requestsTotal)
}

func main() {
    // 业务代码
    http.HandleFunc("/api", func(w http.ResponseWriter, r *http.Request) {
        requestsTotal.WithLabelValues(r.Method, "200").Inc()
        w.Write([]byte("OK"))
    })
    
    // 暴露指标
    http.Handle("/metrics", promhttp.Handler())
    http.ListenAndServe(":8080", nil)
}

这就是 Exporter 的核心结构。node-exporter 做的,就是把这个模式复制到几十个 Collector 上,然后打磨细节。


5. 性能哲学:为什么足够轻量

node-exporter 能在生产环境大规模使用,是因为它足够轻。

5.1 不存历史,不做重计算

Exporter 的职责

  • 采集当前数据
  • 简单的单位换算
  • 暴露指标

不做的事情

  • 不存历史数据(这是 Prometheus 的事)
  • 不做复杂统计(这是 PromQL 的事)
  • 不做聚合(这也是 PromQL 的事)

结果:

  • 内存占用小(只需要存当前快照)
  • CPU 开销低(只是读文件和格式化输出)
  • 可以部署到任何机器(包括低配置机器)

5.2 被动采集,成本可控

Exporter 是被动的:

  • 不主动轮询
  • 不定时采集
  • 一切由 Prometheus 的 scrape 驱动

好处:

  • 采集频率可控(调整 scrape_interval)
  • 可以按需关闭某些 Collector(降低开销)
  • 不会因为监控系统反过来压垮被监控对象

5.3 充分利用 OS 接口

node-exporter 的数据来源几乎全是 /proc/sys

  • 这是 Linux 内核主动暴露的接口
  • 读取成本极低(内核内存映射)
  • 不需要额外的内核模块或代理
  • 对系统侵入性极低

这就是"站在巨人肩膀上"的设计智慧。

5.4 设计智慧

node-exporter 的性能哲学可以总结为:

把重活交给专业的人做

flowchart LR A[Exporter 轻量采集] --> B[Prometheus 存储和查询] B --> C[Grafana 可视化] style A fill:#e1f5ff style B fill:#fff4e1 style C fill:#ffe1f5
  • Exporter:我只负责采集,轻量简单
  • Prometheus:我负责存储和复杂查询,专业高效
  • Grafana:我负责可视化,美观友好

这种分工让每个组件都做自己擅长的事,整个系统才能高效运转。

这也是"简单优先"哲学的体现:不要让 Exporter 变成一个小型数据库


6. 下一步:从设计到落地

通过 node-exporter,我们学到的核心精髓可以浓缩为三条:

三条黄金法则

1. 暴露事实,不加工观点

  • 给累计时间,不给使用率
  • 给原始容量,不给百分比
  • 给累计字节,不给速率
  • 让 PromQL 负责视图组合

2. 按本质选类型

  • 累积类(事件、时间、字节)→ Counter
  • 状态类(容量、数量、长度)→ Gauge
  • 分布类(延迟、大小)→ Histogram
  • 识别出问题本质,类型选择自然而然

3. 命名规范 + 低基数标签

  • Counter 用 _total 后缀
  • 单位用 _seconds_bytes
  • 标签只用低基数维度(避免 user_id)
  • 遵循 <namespace>_<metric>_<unit> 格式

掌握这三条,你就能设计出可复用的监控指标。


详细设计原则

下面是更详细的设计原则和范式:

核心原则

  1. 暴露事实,不是观点

    • 不给加工后的指标(使用率)
    • 给原始数据(累计时间、当前容量)
    • 让 PromQL 负责视图组合
  2. 从问题到类型的映射

    • 累积类 → Counter
    • 状态类 → Gauge
    • 分布类 → Histogram
    • 一旦识别出问题本质,类型选择自然而然
  3. 极简三段式架构

    • Collector 采集
    • Registry 注册
    • HTTP 暴露
    • 职责分离,边界清晰
  4. 轻量性能哲学

    • 不存历史
    • 不做重计算
    • 被动采集
    • 充分利用 OS 接口

设计范式可复用

  • 命名、单位、标签规范
  • 模块化 Collector 结构
  • 低基数标签原则
  • 事实 vs 观点的界限

这些原则不只适用于服务器监控,同样适用于:

  • 应用监控(HTTP、数据库、缓存)
  • 业务监控(订单、支付、用户)
  • 中间件监控(Kafka、Redis、MySQL)

理解了 node-exporter 的设计智慧,你就掌握了 Exporter 设计的精髓。


但是,有了指标和 Exporter,还不够。监控的最终目的是什么?及时发现问题并告警

下一篇,我们将学习如何设计生产级的告警系统,以及如何把这套监控哲学延伸到告警规则的设计中。

下一篇:《告警的艺术:从夜莺引擎学习告警系统设计》


附录:Exporter 设计清单

设计前的问题清单

  • 我要监控什么系统?
  • 核心问题是什么?(Utilization?Saturation?Errors?)
  • 原始数据从哪来?(/proc?API?数据库?)
  • 哪些是"事实"?哪些是"观点"?

指标类型选择清单

  • 是累积的吗?→ Counter
  • 是当前状态吗?→ Gauge
  • 需要看分布吗?→ Histogram
  • 单实例且需要极致性能吗?→ Summary

命名规范清单

  • 格式:<namespace>_<metric>_<unit>
  • Counter 用 _total 后缀
  • 单位用 _seconds_bytes
  • 标签只用低基数维度

架构设计清单

  • 按领域拆分 Collector
  • 向 Registry 注册
  • /metrics 暴露
  • 不存历史,不做重计算
  • 被动采集,由 Prometheus 驱动

性能优化清单

  • 避免高基数标签
  • 避免频繁的网络调用
  • 避免复杂计算
  • 提供开关,可以关闭某些 Collector
  • 设置合理的采集超时
相关推荐
飞Link2 小时前
【CentOS】Linux(CentOS7)安装教程
linux·运维·服务器·centos
lifewange2 小时前
100 个接口,1000 个业务场景,如何设计自动化测试用例?框架是如何设计的?
运维·自动化·测试用例
牛奔2 小时前
Linux 的日志分析命令
linux·运维·服务器·python·excel
wanghowie2 小时前
01.01 Spring核心|IoC容器深度解析
java·后端·spring
深耕AI2 小时前
Docker Volumes详解
运维·docker·容器
Java中文社群2 小时前
国内直连GPT、Claude和Gemini?N8N这次更新真的绝了!
人工智能·后端
飞Link2 小时前
【Linux】Linux(CentOS7)配置SSH免密登录
linux·运维·服务器
飞Link2 小时前
【Java】Linux(CentOS7)下安装JDK8(Java)教程
java·linux·运维·服务器
tap.AI2 小时前
Deepseek(二)五分钟打造优质 PPT:从 DeepSeek 大纲到 Kimi 自动化生成
运维·自动化·powerpoint