Prometheus最佳实践(二)——代码埋点
Prometheus 代码埋点(Instrumentation)最佳实践
本文档来自 Prometheus 官方文档 - Instrumentation,提供在应用程序和服务中进行指标埋点的指导原则和具体建议。
一、总体原则
- “埋点一切”:每个库、子系统和服务都应至少包含几个基础指标,以反映其运行状态。
- 埋点应内置于代码中:在使用指标的同一文件中实例化指标对象,便于从告警 → 控制台 → 代码快速定位问题。
- 日志与指标联动:每有一行日志输出,就应有一个对应的计数器(counter)。这有助于量化错误频率和持续时间。
二、按服务类型分类的埋点策略
1. 在线服务型(Online-serving)
用户或系统期待立即响应(如 HTTP 服务、数据库)。
关键指标:
- 请求总数(
requests_total) - 错误数(
errors_total,按错误类型打标签) - 延迟(使用 Histogram 或 Summary)
- 正在处理的请求数(Gauge,
in_flight_requests)
建议:
- 客户端与服务端均需埋点,便于对比排查问题。
- 统一在请求结束时计数(便于与错误、延迟对齐)。
2. 离线处理型(Offline-processing)
无实时响应要求,常涉及批处理或多阶段流水线。
关键指标(每阶段):
- 输入项数量
- 处理中项数量(Gauge)
- 最后处理时间戳(Unix 时间,非“距今多久”)
- 输出项数量
- 若使用批处理,还需记录批次输入/输出量
高级技巧:
- 引入心跳机制:注入带时间戳的虚拟任务贯穿整个流水线,各阶段上报最新看到的心跳时间,可追踪端到端延迟。
- 对于无空闲期的系统,可省略显式心跳。
3. 批处理作业(Batch jobs)
非持续运行,难以通过 Pull 模式采集。
关键指标(应推送到 Pushgateway):
- 上次成功完成时间戳(
last_success_timestamp_seconds) - 总运行时间(Gauge)
- 各主要阶段耗时(Gauge)
- 上次完成时间(无论成功与否)
- 作业特有统计量(如处理记录总数)
补充建议:
- 若作业运行时间 > 几分钟,同时支持 Pull 模式(如启动 HTTP 端点),以便监控资源使用、外部调用延迟等。
- 若作业频率很高(如 <15 分钟一次),考虑改造成守护进程(daemon),按离线处理模式监控。
三、子系统与库的埋点
库应开箱即用:无需用户额外配置即可自动暴露指标。
对外部资源访问的库(如网络、磁盘、IPC):
- 必须包含:调用次数、错误数、延迟(Histogram/Summary)
内部复杂库:可增加内部错误、延迟及通用统计指标。
标签区分使用场景:
- ✅ 数据库连接池:用
database="db1"区分不同数据库
- ✅ 数据库连接池:用
❌ DNS 客户端库:无需区分不同调用者
四、通用组件埋点建议
错误处理
- 每次失败都应递增一个计数器。
- 同时提供总尝试次数指标,便于计算失败率。
线程池(Threadpool)
- 队列中请求数(Gauge)
- 正在使用的线程数(Gauge)
- 总线程数(Gauge)
- 已处理任务数(Counter)
- 任务处理耗时(Histogram)
- 任务在队列中等待时间(Histogram)
缓存(Cache)
- 总查询数(
cache_requests_total) - 命中数(
cache_hits_total) - 缓存操作延迟(Histogram)
- 后端系统指标:缓存所代理的原始服务的请求、错误、延迟也应保留
自定义 Collector
导出采集耗时(Gauge,单位秒)
导出采集过程中遇到的错误数(Counter)
⚠️ 这是少数允许用 Gauge 表示“持续时间”的场景(另一场景是批处理作业耗时),因为它们描述的是单次抓取/推送行为,而非长期分布。
五、技术注意事项
标签(Labels)使用
合并同类项:多个相似指标应合并为一个带标签的指标。
✅http_responses_total{code="200"}、{code="404"}
❌http_responses_200_total、http_responses_404_total避免动态生成指标名:所有变化维度应使用标签表示(代理其他监控系统除外)。
警惕高基数:
- 单个指标的标签组合(labelset)应尽量控制在 <10 种
- 全局高基数指标(>100)应严格限制数量
- 示例:10,000 节点 × 每节点 10 个文件系统 = 10 万时间序列(可接受);若再 × 10,000 用户 = 1 亿(不可接受)
指标类型选择
| 类型 | 特点 | 使用场景 |
|---|---|---|
| Counter | 只增(可重置) | 事件累计数(请求、错误、字节) |
| Gauge | 可增可减 | 瞬时状态(内存、温度、进行中请求数) |
| Histogram / Summary | 分布统计 | 延迟、响应大小等 |
📌 规则:值能下降 → 用 Gauge;否则用 Counter。
时间戳 vs. 持续时间
导出事件发生的时间戳(Unix 秒),而非“距今多少秒”。
- 例如:
last_success_timestamp_seconds - 查询时用
time() - last_success_timestamp_seconds计算间隔 - 优点:避免更新逻辑卡死导致指标失效
- 例如:
性能敏感代码
- 高频调用路径(>10 万次/秒)需谨慎:
- 减少指标更新次数
- 避免在循环内创建带标签的指标(应缓存
With()或labels()结果) - 注意获取系统时间可能涉及 syscall,有开销
避免缺失指标
- 对已知可能存在的指标,即使初始值为 0 也应暴露。
- 多数客户端库(Go/Java/Python)会自动为无标签指标注册 0 初始值。
💡 核心思想:良好的埋点设计是可观测性的基石。合理的指标命名、类型选择和标签使用,能极大提升故障排查效率和系统可维护性。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 楚歌!
