Article
第 1 章 概述与核心概念
1.1 什么是 Prometheus
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| Prometheus | 开源的系统监控和警报工具包,由 SoundCloud 开发并于 2012 年开源,现为 CNCF 项目。支持多维数据模型和强大的查询语言 PromQL。 | 主要适用于时间序列数据的采集、存储与告警,不适用于日志聚合或追踪(需配合其他系统如 Loki、Jaeger)。 |
| 时间序列数据 | 按时间顺序记录的指标值序列,每个序列由指标名和一组标签(key=value)唯一标识。 | 数据具有高写入频率、不可变性、按时间索引的特点。 |
| PromQL (Prometheus Query Language) | Prometheus 的查询语言,用于对时间序列数据执行选择和聚合操作,支持算术、函数、逻辑运算等。 | 初学者需掌握基本语法如 rate()、increase()、histogram_quantile() 等常用函数。 |
1.2 Prometheus 在 Java 应用监控中的角色
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 监控目标 | Prometheus 通过 HTTP 协议定期从 Java 应用暴露的 /metrics 端点拉取监控数据。 | 需确保 Java 应用启动了 HTTP 服务并正确暴露指标端点。 |
| 客户端库支持 | Java 应用使用 prometheus-client 库来创建和管理指标,并提供标准格式的指标输出。 | 推荐使用官方客户端库以保证兼容性和稳定性。 |
| 实时可观测性 | 提供方法调用次数、响应延迟、错误率、JVM 状态等运行时信息,提升故障排查效率。 | 不应过度暴露敏感业务数据作为指标,避免信息泄露。 |
| 警报集成 | 结合 Alertmanager 可基于 Java 应用指标设置阈值触发警报(如异常率突增)。 | 警报规则应在 Prometheus Server 中配置,而非 Java 应用内。 |
1.3 核心数据模型:Metrics 类型详解
| 指标类型 | 说明 | 注意事项 |
|---|---|---|
| Counter | 累积计数器,仅能递增或保持不变,常用于请求数、错误数等累计统计。 | 重启后重置为 0,Prometheus 使用 rate() 或 increase() 函数处理重置问题。 |
| Gauge | 可任意增减的瞬时值,适合表示温度、内存使用量、当前在线用户数等。 | 可用于记录可上升也可下降的数值,无需担心单调性。 |
| Histogram | 对观测值(如请求延迟)进行采样并分桶统计,生成多个时间序列:样本总数、总和及各区间计数。 | 默认产生 _count、_sum、_bucket{le="x"} 多个时间序列;适合计算分位数。 |
| Summary | 类似 Histogram,但直接在客户端计算分位数(如 95%, 99%),生成 _count、_sum、_quantile 序列。 | 分位数计算在客户端完成,受采样窗口影响,不如 Histogram 灵活。 |
| Untyped | 表示类型未知的指标,用于无法预先确定类型的场景。 | 少见用途,一般应明确指定指标类型以获得更好语义支持。 |
1.4 拉模型(Pull Model)与服务发现
| 概念名称 | 说明 | 注意事项 |
|---|---|---|
| 拉模型 (Pull Model) | Prometheus Server 主动通过 HTTP 协议周期性地从被监控目标拉取 /metrics 数据。 | 要求目标服务必须持续可用且暴露 HTTP 接口;网络防火墙需允许访问。 |
| 推模型对比 | 传统监控系统(如 StatsD)采用客户端主动推送方式。 | Push 模式适合短暂生命周期任务,Pull 模式更适合长期运行服务。 |
| 服务发现 (Service Discovery) | Prometheus 支持自动发现监控目标,无需手动维护静态列表。支持 Kubernetes、Consul、DNS 等多种机制。 | 动态环境下推荐启用服务发现以减少运维负担。 |
| Scraping Interval | 指定 Prometheus 拉取指标的频率,默认通常为 15 秒或 30 秒。 | 设置过短会增加系统负载,过长则降低监控实时性。 |
1.5 Prometheus 生态组件简介(Prometheus Server, Alertmanager, Grafana)
| 组件名称 | 说明 | 注意事项 |
|---|---|---|
| Prometheus Server | 核心组件,负责抓取、存储时间序列数据,提供 PromQL 查询接口。 | 需合理配置 retention period 和 storage backend(本地或远程)。 |
| Alertmanager | 处理由 Prometheus Server 发出的警报,支持去重、分组、静默、通知路由等功能。 | 不直接接收指标,只处理告警事件;可集成邮件、Slack、Webhook 等通知渠道。 |
| Grafana | 开源可视化平台,支持连接 Prometheus 作为数据源,构建丰富的仪表盘。 | 推荐用于展示 Java 应用的关键性能指标(KPIs)。 |
| Exporters | 用于将第三方系统(如 MySQL、Node.js、JMX)的指标转化为 Prometheus 格式。 | Java 应用可通过 jmx_exporter 暴露 JVM 底层指标。 |
| Pushgateway | 允许短期作业将指标推送到中间网关,供 Prometheus 拉取。 | 解决无法持久暴露 /metrics 端点的问题;避免滥用以防数据堆积。 |
第 2 章 Java 集成 Prometheus 基础
2.1 引入 Prometheus 客户端库(prometheus-client)
| 方法/依赖项 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Maven 依赖添加 | <dependency> <groupId>io.prometheus</groupId> <artifactId>simpleclient</artifactId> <version>0.16.0</version> </dependency> | 引入核心客户端库 | io.prometheus simpleclient 0.16.0 | 版本号应根据实际需求选择最新稳定版;注意与其他依赖兼容性。 |
| simpleclient_httpserver | simpleclient_httpserver | 提供嵌入式 HTTP 服务器用于暴露 /metrics 端点 | io.prometheus simpleclient_httpserver 0.16.0 | 必须引入此模块才能启动 HTTP 服务暴露指标。 |
| simpleclient_hotspot | simpleclient_hotspot | 提供对 JVM 内部指标(GC、内存池、线程等)的支持 | io.prometheus simpleclient_hotspot 0.16.0 | 用于收集 JVM 相关指标,建议生产环境启用。 |
| simpleclient_common | simpleclient_common | 包含通用工具类和基础度量类型抽象 | 自动随核心库引入 | 一般无需单独声明依赖。 |
2.2 启动一个简单的 HTTP 服务暴露指标
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| HTTPServer | new HTTPServer(int port) | 启动一个监听指定端口的 HTTP 服务,用于暴露 /metrics 端点 | HTTPServer server = new HTTPServer(8080); | 确保端口未被占用;建议使用非 80/443 的专用监控端口。 |
| HTTPServer | new HTTPServer(InetSocketAddress addr, boolean daemon) | 指定绑定地址和是否为守护线程启动服务 | InetSocketAddress addr = new InetSocketAddress("localhost", 8080); HTTPServer server = new HTTPServer(addr, true); | daemon 设为 true 可防止主线程退出时阻塞 JVM 关闭。 |
| CollectorRegistry.defaultRegistry | static final CollectorRegistry defaultRegistry | 默认的指标注册表,所有指标默认注册于此 | CollectorRegistry registry = CollectorRegistry.defaultRegistry; | 可自定义 registry,但多数情况使用默认即可。 |
| MetricsServlet | new MetricsServlet() | 用于 Servlet 容器中暴露指标(替代独立 HTTPServer) | ServletContextHandler context = new ServletContextHandler(); context.addServlet(new ServletHolder(new MetricsServlet()), "/metrics"); | 适用于嵌入 Jetty 等 Web 服务器场景。 |
2.3 注册自定义指标的基本流程
| 步骤 | 说明 | 注意事项 |
|---|---|---|
| 1. 创建指标实例 | 使用 Counter.build()、Gauge.build() 等工厂方法构建指标对象 | 必须调用 .register() 才能生效。 |
| 2. 设置名称与帮助文本 | 使用 .name("my_counter") 和 .help("description") | 名称应符合命名规范(字母、数字、下划线,小写开头)。 |
| 3. 注册到 CollectorRegistry | 调用 .register() 方法将指标注册到 registry | 默认注册到 defaultRegistry;可传参指定其他 registry。 |
| 4. 更新指标值 | 调用 inc()、set()、observe() 等方法更新值 | 确保线程安全,尤其在并发环境中。 |
| 5. 暴露 HTTP 端点 | 启动 HTTPServer 或部署 MetricsServlet | 客户端库会自动将注册表中的指标序列化为文本格式返回。 |
2.4 使用默认导出器(DefaultExports)收集 JVM 指标
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
DefaultExports.initialize() | public static void initialize() | 注册所有默认的 JVM 相关指标收集器 | DefaultExports.initialize(); | 应在应用启动早期调用一次即可。 |
| StandardExports | new StandardExports().register() | 导出 JVM 基本信息(如版本、类加载数) | new StandardExports().register(); | 已包含在 DefaultExports 中,无需重复调用。 |
| MemoryPoolsExports | new MemoryPoolsExports().register() | 导出各内存池(Eden, Survivor, Old Gen 等)使用情况 | new MemoryPoolsExports().register(); | 可观察 GC 行为和内存压力。 |
| GarbageCollectorExports | new GarbageCollectorExports().register() | 导出 GC 次数与耗时指标 | new GarbageCollectorExports().register(); | 关键性能诊断指标来源。 |
| ThreadExports | new ThreadExports().register() | 导出线程数量(总线程数、守护线程数等) | new ThreadExports().register(); | 有助于发现线程泄漏问题。 |
| FileDescriptorExports | new FileDescriptorExports().register() | 导出文件描述符使用数量(仅 Linux/Unix) | new FileDescriptorExports().register(); | 防止达到系统限制导致连接失败。 |
| BufferPoolExports | new BufferPoolExports().register() | 导出 NIO 缓冲池(direct, mapped)使用情况 | new BufferPoolExports().register(); | 适用于大量 I/O 操作的应用。 |
第 3 章 核心指标类型与使用
3.1 Counter(计数器)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| build | Counter.build() | 创建一个 Counter 构建器实例 | Counter counter = Counter.build(); | 必须调用 register() 才能生效。 |
| name | .name(String name) | 设置指标名称 | .name("requests_total") | 名称只能包含字母、数字、下划线,建议小写开头。 |
| help | .help(String help) | 设置帮助文本(描述) | .help("Total number of HTTP requests") | 建议提供清晰说明以便理解指标含义。 |
| labelNames | .labelNames(String... labelNames) | 定义标签名(维度) | .labelNames("method", "status") | 标签名用于后续区分不同维度的数据。 |
| register | .register() | 注册指标到默认 CollectorRegistry | Counter counter = Counter.build().name("requests_total").help("Total requests").register(); | 注册后可通过返回的 Counter 实例更新值。 |
| inc() | void inc() | 增加计数器值 1 | counter.inc(); | 最常用方法,适用于请求数、错误数等递增场景。 |
| inc(double amt) | void inc(double amt) | 增加指定数值 | counter.inc(2.5); | 可用于按字节数、处理量等非整数单位累加。 |
| get() | double get() | 获取当前计数值(仅用于测试) | double value = counter.get(); | 不建议在生产中频繁读取,主要用于单元测试验证。 |
3.2 Gauge(仪表)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| build | Gauge.build() | 创建一个 Gauge 构建器实例 | Gauge gauge = Gauge.build(); | 用于构造 Gauge 指标。 |
| name | .name(String name) | 设置指标名称 | .name("memory_usage_bytes") | 遵循命名规范。 |
| help | .help(String help) | 设置帮助文本 | .help("Current memory usage in bytes") | 提供语义说明。 |
| labelNames | .labelNames(String... labelNames) | 定义标签名 | .labelNames("region") | 支持多维数据切片。 |
| register | .register() | 注册指标到默认注册表 | Gauge gauge = Gauge.build().name("cpu_temp_celsius").help("CPU temperature").register(); | 返回 Gauge 实例用于后续操作。 |
| set(double val) | void set(double val) | 设置当前值 | gauge.set(45.2); | 适用于温度、内存占用等可上下波动的值。 |
| inc() | void inc() | 值加 1 | gauge.inc(); | 例如线程数增加。 |
| inc(double amt) | void inc(double amt) | 值增加指定数量 | gauge.inc(1024); | 可用于资源增长。 |
| dec() | void dec() | 值减 1 | gauge.dec(); | 例如任务完成减少待处理数。 |
| dec(double amt) | void dec(double amt) | 值减少指定数量 | gauge.dec(512); | 与 inc 对应。 |
| add(double val) | void add(double val) | 同 inc(double) | gauge.add(3.5); | 别名方法,功能相同。 |
| sub(double val) | void sub(double val) | 同 dec(double) | gauge.sub(2.0); | 别名方法。 |
3.3 Histogram(直方图)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| build | Histogram.build() | 创建 Histogram 构建器 | Histogram hist = Histogram.build(); | 用于记录样本分布。 |
| name | .name(String name) | 设置指标名称 | .name("request_duration_seconds") | 通常以 _seconds 结尾表示时间单位。 |
| help | .help(String help) | 设置描述信息 | .help("Request duration in seconds") | 明确指标用途。 |
| labelNames | .labelNames(String... labelNames) | 定义标签 | .labelNames("handler") | 支持按维度划分直方图。 |
| buckets | .buckets(double... bounds) | 自定义桶边界(如 0.1, 0.3, 1.0) | .buckets(0.1, 0.3, 0.5, 1.0) | 默认桶为 [.005, .01, .025, ..., 10.0],应根据业务调整。 |
| exponentialBuckets | .exponentialBuckets(double start, double factor, int count) | 创建指数增长的桶 | .exponentialBuckets(0.1, 2.0, 5) | 生成 [0.1, 0.2, 0.4, 0.8, 1.6] 共 5 个桶。 |
| linearBuckets | .linearBuckets(double start, double width, int count) | 创建等宽桶 | .linearBuckets(0, 10, 5) | 生成 [0,10), [10,20), …, [40,50) 共 5 个桶。 |
| register | .register() | 注册直方图 | Histogram hist = Histogram.build().name("duration").help("Latency").register(); | 注册后可用于观察样本。 |
| observe(double val) | void observe(double val) | 记录一个样本值 | hist.observe(0.45); | 所有桶中满足 le 条件的计数都会 +1,同时 _sum 和 _count 也更新。 |
3.4 Summary(摘要)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| build | Summary.build() | 创建 Summary 构建器 | Summary summary = Summary.build(); | 用于客户端计算分位数。 |
| name | .name(String name) | 设置指标名称 | .name("response_size_bytes") | 命名清晰。 |
| help | .help(String help) | 设置帮助文本 | .help("Response size distribution") | 描述用途。 |
| labelNames | .labelNames(String... labelNames) | 定义标签 | .labelNames("endpoint") | 支持多维分析。 |
| quantile | .quantile(double quantile, double error) | 定义要计算的分位数及允许误差 | .quantile(0.95, 0.01) | 误差越小精度越高但内存消耗越大。 |
| maxAgeSeconds | .maxAgeSeconds(int seconds) | 设置滑动窗口最大持续时间 | .maxAgeSeconds(600) | 默认 300 秒(5 分钟)。 |
| ageBuckets | .ageBuckets(int count) | 设置滑动窗口的桶数量 | .ageBuckets(5) | 每个桶代表 maxAgeSeconds/ageBuckets 的时间段。 |
| register | .register() | 注册摘要指标 | Summary summary = Summary.build().name("latency").quantile(0.99, 0.001).register(); | 完成配置并注册。 |
| observe(double val) | void observe(double val) | 记录一个样本值 | summary.observe(123.4); | 用于后续分位数计算。 |
3.5 Untyped(未指定类型)
| 方法名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| build | Untyped.build() | 创建 Untyped 构建器 | Untyped untyped = Untyped.build(); | 表示类型未知的指标。 |
| name | .name(String name) | 设置名称 | .name("unknown_type_metric") | 少见使用场景。 |
| help | .help(String help) | 设置描述 | .help("Metric with unknown type") | 不推荐主动使用。 |
| labelNames | .labelNames(String... labelNames) | 添加标签支持 | .labelNames("type") | 可带维度。 |
| register | .register() | 注册指标 | Untyped metric = Untyped.build().name("m").help("desc").register(); | 成功注册后可通过 set 设置值。 |
| set(double val) | void set(double val) | 设置当前值 | metric.set(42.0); | 类似 Gauge 但无语义含义。 |
| get() | double get() | 获取当前值 | double v = metric.get(); | 仅测试用途。 |
第 4 章 自定义指标开发实践
4.1 创建和注册自定义指标
| 步骤 | 说明 | 注意事项 |
|---|---|---|
| 1. 选择合适的指标类型 | 根据数据特性选择 Counter、Gauge、Histogram 或 Summary | 错误选择类型会导致查询困难或资源浪费。 |
| 2. 定义指标名称 | 使用小写字母、数字和下划线,以 _total、_seconds 等后缀增强语义 | 避免使用大写字母和特殊字符。 |
| 3. 添加帮助文本 | 使用 .help() 提供清晰描述 | 有助于团队成员理解和使用指标。 |
| 4. 设置标签(可选) | 使用 .labelNames() 定义维度 | 标签不宜过多,避免”高基数”问题。 |
5. 调用 register() 完成注册 | 将指标注册到 CollectorRegistry | 注册后自动出现在 /metrics 端点中。 |
| 6. 保存引用以更新值 | 将返回的指标实例保存为字段或常量 | 后续通过该引用调用 inc()、set() 等方法。 |
4.2 使用标签(Labels)进行维度划分
| 方法/概念 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| labelNames | .labelNames("method", "status") | 在构建时声明标签名 | Counter counter = Counter.build().name("http_requests_total").labelNames("method", "status").help("HTTP requests by method and status").register(); | 必须提前声明所有标签名。 |
| labels(String… values) | counter.labels("GET", "200") | 获取特定标签组合的 Child 实例 | counter.labels("GET", "200").inc(); | 参数顺序必须与 labelNames 定义一致。 |
| 高基数风险 | - | 标签值过多导致时间序列爆炸 | 如使用用户 ID 作为标签 | 会导致内存和查询性能问题,应避免。 |
| 推荐标签命名 | 小写字母、下划线分隔 | 统一风格便于查询 | status_code 而非 statusCode | 与 Prometheus 社区规范保持一致。 |
| 常见标签用途 | method、status、handler、region 等 | 划分监控维度 | 按 HTTP 方法和状态码统计请求 | 有助于定位问题来源。 |
4.3 多实例指标管理(Child 和 LabelSet)
| 概念/方法 | 说明 | 注意事项 |
|---|---|---|
| Child | 指标类的内部类,表示带特定标签值的指标实例 | 每个唯一标签组合对应一个 Child。 |
| LabelSet | 一组标签名到标签值的映射 | 用于唯一标识一个 Child。 |
| 自动 Child 创建 | 第一次调用 labels() 时自动创建 Child | 后续相同标签组合返回同一 Child 实例。 |
| 内存消耗 | 每个 Child 占用一定内存 | 高基数标签会导致内存泄漏,需谨慎设计。 |
| 预创建 Child | 可通过 register 方法预定义常用 Child | 减少运行时开销,提高性能。 |
| 线程安全 | Child 实例本身是线程安全的 | 可在多线程环境中安全调用 inc()、set() 等方法。 |
4.4 线程安全与指标更新最佳实践
| 实践建议 | 说明 | 注意事项 |
|---|---|---|
| 指标实例线程安全 | 所有标准指标类型(Counter、Gauge 等)的方法都是线程安全的 | 可在多个线程中并发调用 inc()、set() 等方法。 |
| 避免在循环中创建指标 | 指标应在初始化阶段创建并复用 | 循环中 build().register() 会导致重复注册异常或内存泄漏。 |
| 使用静态常量保存指标引用 | 将注册后的指标实例保存为 private static final 字段 | 便于全局访问且避免重复创建。 |
| 控制标签基数 | 避免使用高变化性的值作为标签(如请求 ID、用户名) | 防止时间序列数量爆炸。 |
| 合理选择指标类型 | 请求计数用 Counter,延迟用 Histogram,瞬时值用 Gauge | 类型错用会影响 PromQL 查询效果。 |
| 监控指标本身性能 | 过多指标或频繁更新会影响应用性能 | 生产环境应定期审查指标使用情况。 |
| 使用 Summary 或 Histogram 记录延迟 | 推荐使用 Histogram,因其更灵活且支持任意分位数计算 | Summary 在客户端计算分位数,不够精确。 |
第 5 章 高级特性与性能优化
5.1 Collector 与 Custom Collector 的实现
| 方法/类 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Collector | abstract class Collector | Prometheus 客户端中用于自定义指标收集的基类 | public class CustomCollector extends Collector { ... } | 必须重写 collect() 方法。 |
| collect() | List collect() | 返回当前收集到的指标样本列表 | @Override public List collect() { ... } | 每次拉取时调用,应尽量轻量、快速完成。 |
| MetricFamilySamples | new MetricFamilySamples(String name, Type type, String help, List samples) | 表示一组同名指标(含多个标签变体) | List samples = new ArrayList<>(); samples.add(new Sample("my_metric", Arrays.asList("label1"), Arrays.asList("value1"), 123.0)); return Arrays.asList(new MetricFamilySamples("my_metric", Type.GAUGE, "desc", samples)); | 必须指定指标名、类型、帮助文本和样本列表。 |
| Sample | new Sample(String name, List labelNames, List labelValues, double value) | 表示一个具体的指标样本 | new Sample("http_requests_total", Arrays.asList("method"), Arrays.asList("GET"), 42.0) | labelNames 和 labelValues 必须一一对应。 |
| register() | void register() | 将自定义 Collector 注册到 CollectorRegistry | new CustomCollector().register(); | 注册后其指标将出现在 /metrics 端点中。 |
| Type 枚举 | Type.COUNTER, Type.GAUGE, Type.HISTOGRAM, Type.SUMMARY | 指定指标类型 | Type.COUNTER | 必须与实际数据语义匹配。 |
5.2 使用 Pushgateway 进行推送式上报
| 方法/类 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| PushGateway | new PushGateway(String address) | 创建指向 Pushgateway 服务的客户端 | PushGateway pg = new PushGateway("localhost:9091"); | 地址格式为 host:port。 |
| pushAdd(CollectorRegistry, String) | void pushAdd(CollectorRegistry registry, String job) | 将指标以”增量”方式推送到指定 job | pg.pushAdd(CollectorRegistry.defaultRegistry, "batch_job_123"); | 相同 job 的多次 pushAdd 会累加指标。 |
| push(CollectorRegistry, String) | void push(CollectorRegistry registry, String job) | 覆盖式推送,替换该 job 下所有指标 | pg.push(CollectorRegistry.defaultRegistry, "batch_job_123"); | 适用于短期批处理任务状态上报。 |
| push(CollectorRegistry, String, String) | void push(CollectorRegistry registry, String job, String instance) | 指定 job 和 instance 标签推送 | pg.push(registry, "cron_job", "server1"); | 更细粒度控制目标标识。 |
| delete(String job) | void delete(String job) | 删除指定 job 的所有指标 | pg.delete("batch_job_123"); | 用于清理过期任务数据。 |
| disableHostnameValidation() | pg.disableHostnameValidation() | 禁用 HTTPS 主机名验证(调试用) | pg.disableHostnameValidation(); | 仅用于测试环境,生产不建议使用。 |
| setConnectionFactory() | pg.setConnectionFactory(...) | 设置自定义 HTTP 连接工厂(如支持 HTTPS) | - | 用于复杂网络环境集成。 |
5.3 指标收集性能调优建议
| 建议项 | 说明 | 注意事项 |
|---|---|---|
| 减少指标数量 | 只暴露必要指标,避免冗余 | 指标过多会增加序列化和网络传输开销。 |
| 控制标签基数 | 避免使用高基数标签(如用户 ID、请求 ID) | 高基数是性能杀手,会导致内存暴涨和查询变慢。 |
| 批量更新替代频繁小更新 | 合并多个 inc() 调用为 inc(n) | 减少同步开销,提升吞吐。 |
| 避免在 hot path 中创建字符串 | 标签值尽量复用,避免临时拼接 | 字符串创建和 GC 会影响应用性能。 |
| 使用本地缓存 Child 实例 | 缓存 labels(...) 返回的 Child | 避免重复查找,提升多标签场景性能。 |
| 选择合适采集间隔 | Prometheus scrape_interval 不宜过短 | 默认 15s 或 30s 足够,过短增加系统压力。 |
| 监控指标本身开销 | 关注 /metrics 端点响应时间和大小 | 若响应慢或体积大,需优化指标设计。 |
| 使用 Histogram 替代 Summary | 推荐 Histogram,更灵活且服务端计算分位数 | Summary 在客户端计算,精度低且难以聚合。 |
5.4 指标命名规范与最佳实践
| 规范项 | 说明 | 注意事项 |
|---|---|---|
| 小写字母开头 | 所有指标名应以小写字母开头 | 如 http_requests_total |
| 使用下划线分隔 | 单词间用下划线 _ 分隔 | 如 jvm_memory_bytes_used |
| 添加语义后缀 | Counter 用 _total,Gauge 用 _current 或 _bytes,Histogram 用 _duration_seconds | 明确单位和类型 |
| 避免缩写 | 使用完整单词(如 seconds 而非 sec) | 提高可读性 |
| 按层级组织 | 优先级:应用名 > 模块名 > 指标名 | 如 payment_service_db_connections_used |
| 标签名小写 | 标签名也应小写并用下划线分隔 | 如 status_code 而非 statusCode |
| 限制标签数量 | 每个指标标签数建议不超过 5-6 个 | 过多标签增加复杂性和开销 |
| 文档化指标 | 建立内部指标字典,记录每个指标用途 | 便于新人理解和审计 |
第 6 章 Spring Boot 集成 Prometheus
6.1 使用 Micrometer 集成 Prometheus
| 依赖项 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| micrometer-core | <dependency><br> <groupId>io.micrometer</groupId><br> <artifactId>micrometer-core</artifactId><br></dependency> | Micrometer 核心库,提供统一的指标抽象 | 自动引入 | 所有监控功能的基础,无需显式声明。 |
| micrometer-registry-prometheus | <dependency><br> <groupId>io.micrometer</groupId><br> <artifactId>micrometer-registry-prometheus</artifactId><br></dependency> | Prometheus 注册表实现 | io.micrometer micrometer-registry-prometheus | 必须引入才能生成 Prometheus 格式指标。 |
| MeterRegistry | @Autowired MeterRegistry registry | 注入 Micrometer 的指标注册表 | @Autowired private MeterRegistry registry; | 用于创建和管理自定义指标实例。 |
| @Timed 注解 | @Timed("http.server.requests") | 方法级监控,自动记录调用次数和延迟 | @GetMapping("/api") @Timed("api.duration") public String api() { ... } | 可用于 Controller 或 Service 方法,支持 percentiles 配置。 |
| 自动指标 | - | 自动暴露 JVM、HTTP 请求、Tomcat 等运行时指标 | 如 jvm_memory_used_bytes, http_server_requests_seconds_count | 开箱即用,极大减少手动编码。 |
6.2 配置 /actuator/prometheus 端点
| 配置项 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 启用 actuator | <dependency><br> <groupId>org.springframework.boot</groupId><br> <artifactId>spring-boot-starter-actuator</artifactId><br></dependency> | 添加 Spring Boot Actuator 支持 | org.springframework.boot spring-boot-starter-actuator | 必须引入。 |
| 暴露 prometheus 端点 | management.endpoints.web.exposure.include=prometheus | 在 application.yml 中配置 | management: endpoints: web: exposure: include: prometheus | 默认不暴露,需显式开启。 |
| 自定义端点路径 | management.endpoints.web.path-mapping.prometheus=/metrics | 修改默认路径 | management: endpoints: web: path-mapping: prometheus: /metrics | 便于与 Nginx 等反向代理集成。 |
| 安全控制 | management.endpoint.prometheus.enabled=false | 控制端点是否启用 | 结合 Spring Security 限制访问 IP 或添加认证 | 生产环境建议启用认证,防止信息泄露。 |
6.3 自定义指标在 Spring Boot 中的注册
| 方法 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| Counter.builder() | Counter.builder(String name).register(MeterRegistry) | 创建并注册计数器 | Counter.builder("login.attempts").tag("result", "success").register(registry).increment(); | 使用注入的 MeterRegistry。 |
| Gauge.builder() | Gauge.builder(String name, T obj, ToDoubleFunction valueFunction) | 创建 Gauge 指标 | Gauge.builder("queue.size", queue, Queue::size).register(registry);Gauge.builder("db.connections.used", dataSource, DataSource::getActiveCount).register(registry); | 第二个参数为状态持有对象,函数用于提取值。 |
| Timer.builder() | Timer.builder(String name).register(registry) | 创建 Timer 记录耗时 | Timer timer = Timer.builder("api.call.duration").register(registry);timer.record(() -> api.call()); | 支持 record(Runnable) 简化调用。 |
| DistributionSummary.builder() | DistributionSummary.builder(String name).register(registry) | 类似 Histogram,用于非时间值分布 | DistributionSummary.builder("payload.size.bytes").register(registry).record(1024); | 适用于请求大小、消息长度等。 |
| @Bean 定义指标 | @Bean MeterBinder customMeter() { ... } | 通过 Bean 方式注册可复用指标 | @Bean public MeterBinder customCounter() { return registry -> Counter.builder("custom.event").register(registry); } | 适合全局共享指标,便于测试和复用。 |
6.4 结合 Grafana 展示 Spring Boot 应用监控
| 实践项 | 说明 | 注意事项 |
|---|---|---|
| 添加 Prometheus 数据源 | 在 Grafana 中配置 Prometheus 服务地址 | 如 http://prometheus-server:9090 |
| 导入 Spring Boot 仪表盘 | 使用官方 ID 为 6756 的 “Spring Boot Application” 仪表盘 | 可在 Grafana 官网下载并导入 |
| 自定义面板 | 使用 PromQL 查询自定义指标 | 如 rate(http_server_requests_seconds_count{application="myapp"}[5m]) |
| 设置告警规则 | 在 Grafana 或 Prometheus 中设置阈值告警 | 如错误率 > 1% 触发告警 |
| 多环境区分 | 使用 application、profile、instance 等标签区分不同环境 | 查询时添加 {application="prod-app"} |
| 仪表盘共享 | 将常用仪表盘导出为 JSON 并版本化管理 | 便于团队协作和恢复 |
第 7 章 实战案例与监控设计
7.1 监控 REST API 请求量与延迟
| 方法/指标 | 说明 | 注意事项 |
|---|---|---|
http_server_requests_seconds_count | Micrometer 自动提供的 HTTP 请求计数器 | 可通过 method、uri、status 等标签切片分析。 |
http_server_requests_seconds_sum | 请求延迟总和 | 用于计算平均延迟:sum / count。 |
rate() 函数 | rate(http_server_requests_seconds_count[5m]) | 计算每秒请求数(QPS),排除重启影响。 |
histogram_quantile() | histogram_quantile(0.95, sum(rate(http_server_requests_seconds_bucket[5m]))) | 计算 95% 分位延迟,识别慢请求。 |
@Timed 注解 | 在 Controller 方法上使用 | 自动记录该方法的调用次数和延迟分布。 |
| 自定义标签 | 添加业务相关标签如 tenant_id | 增强多租户场景下的可观测性。 |
7.2 跟踪方法调用次数与异常率
| 方法/指标 | 说明 | 注意事项 |
|---|---|---|
| Counter 记录调用次数 | counter.increment() | 在方法入口处调用。 |
| Counter 记录异常次数 | try { ... } catch (Exception e) { errorCounter.increment(); throw e; } | 捕获后递增并重新抛出,不影响业务逻辑。 |
| @Timed + extraTags | @Timed(extraTags = {"error", "false"}) 和 catch 块中手动标记 | 区分成功与失败调用。 |
| 异常率计算 | rate(error_counter[5m]) / rate(total_counter[5m]) | Prometheus 中计算错误率。 |
| Gauge 记录当前处理中任务数 | inc() 进入时,dec() 退出时 | 反映系统负载压力。 |
7.3 数据库连接池监控
| 指标名称 | 说明 | 注意事项 |
|---|---|---|
hikaricp_connections | HikariCP 连接总数(Gauge) | 包括空闲和活跃连接。 |
hikaricp_connections_active | 当前活跃连接数(Gauge) | 高值可能表示慢查询或连接泄漏。 |
hikaricp_connections_idle | 空闲连接数(Gauge) | 与 active 之和等于 total。 |
hikaricp_connections_pending | 等待获取连接的线程数(Gauge) | >0 表示连接不足,需扩容或优化。 |
hikaricp_connection_timeout | 连接获取超时次数(Counter) | 持续增长需立即排查。 |
| 自定义监控 | 定期记录连接使用率:active/total | 可设置告警阈值(如 >80%)。 |
7.4 JVM 内存与 GC 指标分析
| 指标名称 | 说明 | 注意事项 |
|---|---|---|
jvm_memory_used_bytes | 各内存区(heap, non-heap)已使用内存 | 观察老年代使用趋势,判断是否内存泄漏。 |
jvm_memory_max_bytes | 各内存区最大容量 | heap 通常等于 -Xmx 设置值。 |
jvm_gc_pause_seconds_count | GC 停顿次数 | 区分 young gc 和 full gc。 |
jvm_gc_pause_seconds_sum | GC 停顿总时间 | 计算平均停顿时长。 |
jvm_threads_states_threads | 各状态线程数量 | blocked 线程过多可能表示锁竞争。 |
| Full GC 频率告警 | increase(jvm_gc_pause_seconds_count{action="end of major GC"}[1h]) > 5 | 1 小时内发生 5 次以上 Full GC 触发告警。 |
| 老年代使用率 | jvm_memory_used_bytes{area="heap",id="PS Old Gen"} / jvm_memory_max_bytes{area="heap",id="PS Old Gen"} | 持续接近 100% 需优化或扩容。 |
第 8 章 安全与生产部署
8.1 指标端点的安全保护(认证与授权)
| 方法 | 说明 | 注意事项 |
|---|---|---|
| Spring Security 集成 | 配置安全规则保护 /actuator/prometheus | http.authorizeRequests().requestMatchers("/actuator/prometheus").hasRole("MONITOR"); |
| 网络层防护 | 使用防火墙或 Ingress 限制访问 IP | 仅允许 Prometheus Server IP 访问。 |
| Basic Auth | 在反向代理(如 Nginx)上配置用户名密码 | location /actuator/prometheus { auth_basic "Prometheus"; auth_basic_user_file /etc/nginx/.htpasswd; } |
| JWT/OAuth2 | 复杂场景下使用令牌认证 | 需定制化开发。 |
| 禁用敏感端点 | production 环境关闭 env、beans 等敏感 actuator 端点 | management.endpoints.web.exposure.include=health,info,prometheus |
8.2 大规模应用中的指标聚合策略
| 策略 | 说明 | 注意事项 |
|---|---|---|
| 分层采集 | 边缘服务 -> Service Mesh (Istio) -> Prometheus Federation | 减轻中心 Server 压力。 |
| Prometheus Federation | 上层 Prometheus 只拉取下层聚合结果 | global: external_labels: level: region |
| Thanos 或 Cortex | 使用支持水平扩展的长期存储方案 | 提供全局查询视图。 |
| 指标采样 | 只采集关键业务指标,忽略低价值数据 | 如关闭某些 trace-level 指标。 |
| 降频采集 | 非核心服务使用更长的 scrape_interval | 如从 15s 调整为 60s。 |
8.3 高并发场景下的指标采集稳定性
| 实践 | 说明 | 注意事项 |
|---|---|---|
| 指标更新异步化 | 使用队列缓冲指标更新,异步刷入 | 避免阻塞业务线程。 |
| 减少 synchronized 块 | 避免在指标更新路径上加锁 | 使用无锁数据结构(如 LongAdder)。 |
| 监控 /metrics 响应时间 | 记录端点自身性能 | 若 >1s 需优化。 |
| 限制指标数量 | 避免创建过多时间序列 | 检查 CollectorRegistry.defaultRegistry.metricFamilySamples().size()。 |
| 使用 Histogram 替代 Summary | Summary 在客户端计算分位数开销大 | Histogram 仅记录原始值。 |
| 资源隔离 | 将监控组件部署在独立线程池 | 防止相互影响。 |
8.4 Prometheus 配置与 Java 应用的协同部署
| 实践 | 说明 | 注意事项 |
|---|---|---|
| 统一 scrape_interval | Java 应用指标更新频率与 Prometheus 拉取间隔匹配 | 如均为 30s。 |
| 正确设置 job 和 instance 标签 | 确保 Prometheus 配置中的 target 正确标识应用 | static_configs: - targets: ['app1:8080'] labels: job: payment-service |
| 启用 relabeling | 动态重写标签,便于分类和路由 | 使用 replacement 和 regex。 |
| 配置 scrape_timeout | 设置合理的超时时间 | scrape_timeout: 10s |
| 健康检查集成 | 将 /actuator/health 与 Prometheus 告警联动 | job up 状态异常触发告警。 |
| 版本对齐 | 保持 prometheus-client 与 Spring Boot / Micrometer 版本兼容 | 查阅官方兼容性矩阵。 |