4. 指标数据模型:Metric、Label、Timestamp、Value 四要素

聊到监控数据采集,很多人第一反应是「怎么把数据拿过来」。但我觉得,真正决定一个监控系统能走多远的,是数据模型怎么设计。

说白了,数据模型就是监控系统的「骨架」。骨架歪了,后面再怎么填肉也长不成人样。我见过太多项目,前期随便搞个 key-value 存着,等规模一上来,查询慢、聚合难、存储爆炸,最后只能推倒重来。

今天咱们就聊聊监控数据模型的四个核心要素:Metric、Label、Timestamp、Value。这四个东西,你搞明白了,监控系统就稳了一半。

4.1 四要素拆解:一个都不能少

先看一个最典型的监控数据点长什么样:

http_requests_total{method="GET", endpoint="/api/users", status="200"} 1024 1700000000

这行数据里,四个要素全齐了:

  • Metric(指标名)http_requests_total,表示「HTTP 请求总数」
  • Label(标签){method="GET", endpoint="/api/users", status="200"},描述这个指标的维度
  • Value(值)1024,当前这个时间点的具体数值
  • Timestamp(时间戳)1700000000,Unix 时间戳,精确到秒

嗯,这里要注意:时间戳不是可选项。我见过有人把时间戳省了,想着「反正采集端自己打时间」。结果呢?网络延迟、采集端时钟漂移,数据全乱套了。时间戳必须由数据源自己带,采集端只做透传。

4.2 Metric 命名:别让名字成为你的坑

指标名怎么起?我个人习惯遵循 Prometheus 的命名规范:

  • 命名空间:用 namespace_ 开头,比如 nginx_mysql_app_
  • 层级结构:用下划线分隔,比如 app_requests_totalapp_requests_duration_seconds
  • 单位后缀:带上单位,比如 _seconds_bytes_total
  • 不要用驼峰:全小写 + 下划线,避免大小写敏感带来的混乱

最佳实践示例:

# 好的命名
app_http_requests_total
app_http_request_duration_seconds
db_query_latency_milliseconds

# 坏的命名
AppHttpRequestsTotal    # 驼峰,容易搞混
http_requests           # 没有命名空间,冲突风险高
latency                 # 没有单位,谁知道是秒还是毫秒?

我在项目中遇到过一件事:两个团队各自开发微服务,一个用 request_count,一个用 requests_total。结果聚合查询时,同一个指标名对应了两种语义,排查了整整两天。从那以后,我强制要求所有指标名必须带命名空间前缀。

4.3 Label 设计:维度爆炸是最大的敌人

Label 是监控数据模型的灵魂。没有 Label,你只能看到「总请求数」,有了 Label,你才能看到「每个接口的请求数」「每个状态码的分布」。

但 Label 也是一把双刃剑。你想想看,每个 Label 组合都会产生一个新的时间序列。如果有 10 个 Label,每个 Label 有 10 个取值,那就是 10^10 = 100 亿条序列。存储直接爆炸。

我曾经踩过的坑:

给每个 HTTP 请求都加了一个 request_id 标签,想着方便追踪。结果一天产生了 2 亿条时间序列,存储集群直接 OOM。后来才明白:高基数(High Cardinality)标签是监控系统的头号杀手

Label 设计的原则,我总结为三条:

  1. 低基数优先:Label 的取值数量控制在 100 以内。比如 status_code(几十种)、method(几种)、region(几十个)
  2. 不要放唯一标识user_idrequest_idip 这些高基数标签,请放到日志里,别放监控里
  3. 标签值标准化:统一大小写、去空格、去特殊字符。比如 status="200" 不要写成 status="200 OK"

小技巧:

如果你不确定某个标签是否合适,问自己一个问题:「这个标签的取值会不会超过 1000 种?」如果会,就别放。

4.4 Timestamp 与 Value:精度和范围的选择

时间戳和值看起来简单,但细节不少。

要素 推荐格式 注意事项
Timestamp Unix 秒级时间戳 不要用毫秒或纳秒,除非你的场景需要亚秒级精度。秒级时间戳兼容性最好,存储也省空间
Value float64 大部分监控系统用 64 位浮点数。注意:不要存字符串,不要存布尔值,统一转成数值

为什么推荐秒级?我做过一个对比测试:同样 1 亿条数据,秒级时间戳占 8 字节,毫秒级占 12 字节。看起来不多,但乘以 1 亿,就是 400MB 的差距。而且大部分监控场景(比如 10 秒采集一次),秒级精度完全够用。

4.5 数据模型设计最佳实践总结

说了这么多,我画了一张图,把核心逻辑串起来:

指标数据模型四要素 监控数据点 Metric 指标名 Label 标签 Timestamp 时间戳 Value 值 设计最佳实践 • Metric:命名空间 + 下划线 + 单位,全小写 • Label:低基数(<100种取值),不放唯一标识,值标准化 • Timestamp:Unix 秒级时间戳,由数据源生成 • Value:float64 数值类型,不存字符串或布尔值

这张图把四要素和最佳实践串在了一起。你设计数据模型时,就照着这个框架来,基本不会出大问题。

4.6 一个完整的例子

最后,我拿一个真实场景来演示:监控一个电商应用的订单接口。

# 指标定义
app_order_created_total{status="success", region="cn-beijing", env="prod"} 1523 1700000000
app_order_created_total{status="failed", region="cn-beijing", env="prod"} 12 1700000000
app_order_created_total{status="success", region="cn-shanghai", env="prod"} 987 1700000000

# 指标名:app_order_created_total
#   命名空间:app_
#   业务域:order_
#   指标名:created_total(累计值)
# 标签:
#   status:success / failed(低基数,2种取值)
#   region:cn-beijing / cn-shanghai(低基数,几十种)
#   env:prod / staging / dev(低基数,几种)
# 时间戳:1700000000(秒级)
# 值:1523(float64)

这个设计,你想想看,是不是既清晰又高效?查询时,可以按 region 聚合看总订单量,也可以按 status 看成功率,还能按 env 区分环境。而且每个标签的基数都很低,不会导致序列爆炸。

避坑指南:

我曾经在一个项目中,把 user_id 作为标签存了进去。结果用户量从 1 万涨到 100 万,时间序列从 10 万条涨到 1000 万条,查询响应时间从 100ms 涨到 30 秒。最后不得不重建数据,把 user_id 从标签里移除,改用日志系统存储。

数据模型设计,说白了就是「取舍」的艺术。你不可能什么维度都存,也不可能什么查询都支持。抓住 Metric、Label、Timestamp、Value 这四个要素,把基数和精度控制好,你的监控系统就能跑得稳、查得快、存得省。


专注资料整理