之前在 聊聊 SpringBoot3 的 Micrometer Tracing 这篇文章中我介绍了 SpringBoot3 使用 Micrometer Tracing 来作为分布式链路组件的来龙去脉,在那篇文章中也提及了 SpringBoot 在 可观测性部分官方默认使用的是 Micrometer 来实现。
实际上,在可观测性部分,OpenTelemetry 正在往大一统的方向不断前进,SpringBoot 即时默认使用 Micrometer 来补充其在可观测性上的板块,但是社区也从未停止过对于 SpringBoot 集成 OpenTelemetry 讨论;SpringBoot 实际上先是在 tracing 上完成了对于 OTEL tracing 的桥接,但是对于 metrics 却迟了一些动作,可以从这里看到关于 SpringBoot 对于可观测性的计划 Observability Planning。关于 OpenTelemetry support in Spring 的讨论,在这个 issue 中有比较详细的记录 Consolidate OpenTelemetry support in Spring。
在本篇文章中,我将向您介绍如何使用 OpenTelemetry Java agent 来捕获 SpringBoot Metrics。并希望通过本篇来了解 Micrometer metrics 以及 OpenTelemetry metrics 在指标收集中的差异以及两种报文协议的区别。
环境及前置步骤准备
环境
基本依赖
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
基于 Micrometer metrics 的指标收集
基于前面的环境和前置步骤,搭建一个简单的 SpringBoot 样例工程。前面提到,SpringBoot 默认使用 micrometer 来实现指标的收集,下面在依赖中添加基于 Prometheus 协议的 registry 来暴露 micrometer 指标。
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
在 配置文件中配置暴露 prometheus endpoint。
启动应用之后,可以通过 http://localhost:8080/actuator/prometheus 来查看默认的一些指标信息。关于指标信息的解释可以查阅 springboot 官方文档 Metrics。这里我添加一个默认的自定义指标 hello.total 来统计调用 /hello 接口的次数,以便于后面来区分不同协议中的表现,代码如下:
请求此接口之后得到的指标信息如下:
~ curl -i http://localhost:8080/actuator/prometheus | grep hello_total
# HELP hello_total
# TYPE hello_total counter
hello_total 1.0
到这里,初步完成相关的准备工作,这里提供出去的 /actuator/prometheus 实际上就是我们常规生产环境中对外暴露的监控数据获取的 endpoint,以便于 promethus 来拉取指标数据,这里不再赘述。
使用 OpenTelemetry Collector 来抓取 /actuator/prometheus 数据
首先,需要部署一个 OpenTelemetry Collector ,这里可以根据官方文档来部署,https://opentelemetry.io/docs/collector/installation/。我这里是从 GitHub 下载的可执行文件安装的,可以根据你的操作系统来选择具体的可执行文件下载。然后根据官方文档的配置来创建一个 config.yaml 文件,用于指定相应的 receivers 和 exporters,下面是参考官方文档创建的配置文件:
receivers:
prometheus:
config:
scrape_configs:
- job_name: "springboot-otel-guides"
scrape_interval: 5s # 采集的时间间隔
metrics_path: '/actuator/prometheus' #采集的目标服务对外提供的 metric endpoint path
static_configs:
- targets: ["localhost:8080"] #采集的目标服务地址
exporters:
prometheus:
endpoint: "localhost:8889" #收集器对外暴露访问的地址,这里仅配置了 prometheus 的 exporters
service:
pipelines:
metrics:
receivers: [prometheus] # 使用 prometheus 协议,对应上面的 receivers#prometheus
processors: [batch]
exporters: [prometheus] # 对应上面 exporters#prometheus
启动 OTEL Collector
./otelcol --config=config.yaml
启动日志大致如下:
Downloads ./otelcol-contrib_0.96.0_darwin_arm64/otelcol-contrib --config=config.yaml
2024-03-21T15:58:26.162+0800 info service@v0.96.0/telemetry.go:55 Setting up own telemetry...
2024-03-21T15:58:26.162+0800 info service@v0.96.0/telemetry.go:97 Serving metrics {"address": ":8888", "level": "Basic"}
2024-03-21T15:58:26.162+0800 info service@v0.96.0/service.go:143 Starting otelcol-contrib... {"Version": "0.96.0", "NumCPU": 8}
2024-03-21T15:58:26.162+0800 info extensions/extensions.go:34 Starting extensions...
2024-03-21T15:58:26.163+0800 info prometheusreceiver@v0.96.0/metrics_receiver.go:240 Starting discovery manager {"kind": "receiver", "name": "prometheus", "data_type": "metrics"}
2024-03-21T15:58:26.164+0800 info prometheusreceiver@v0.96.0/metrics_receiver.go:231 Scrape job added {"kind": "receiver", "name": "prometheus", "data_type": "metrics", "jobName": "springboot-otel-guides"}
2024-03-21T15:58:26.164+0800 info service@v0.96.0/service.go:169 Everything is ready. Begin running and processing data.
2024-03-21T15:58:26.164+0800 warn localhostgate/featuregate.go:63 The default endpoints for all servers in components will change to use localhost instead of 0.0.0.0 in a future version. Use the feature gate to preview the new default. {"feature gate ID": "component.UseLocalHostAsDefaultHost"}
2024-03-21T15:58:26.164+0800 info prometheusreceiver@v0.96.0/metrics_receiver.go:282 Starting scrape manager {"kind": "receiver", "name": "prometheus", "data_type": "metrics"}
通过 http://localhost:8889/metrics 访问,查看之前自定义的 hello_total
~ curl -i http://localhost:8889/metrics | grep hello_total
# HELP hello_total
# TYPE hello_total counter
hello_total{instance="localhost:8080",job="springboot-otel-guides"} 1
这里我仅是将 Micrometer 采集的指标数据(本质上使用的是 PromethusMeterRegistry,而不是 Micrometer 默认的 SimpleMeterRegistry)通过 promethus 协议同步到 OpenTelemetry Collector。下面来将 Micrometer 采集的方式换成 OpenTelemetry Java Agent 方式采集。
使用 OpenTelemetry Java Agent 代替 Micrometer
前面我配置了 OpenTelemetry Collector 来抓取 Micrometer 采集的数据,这小节会使用 OpenTelemetry Java Agent 代替 Micrometer 来收集指标,使用 OpenTelemetry Line Protocol (otlp) 协议来提供指标数据给 OpenTelemetry Collector。
1、首先将前面的 config.yaml 配置文件修改使用 otlp 协议
receivers:
otlp:
protocols:
grpc:
http:
exporters:
prometheus:
endpoint: "localhost:8889" # 还是适用 prometheus 对外暴露数据
service:
pipelines:
metrics:
receivers: [otlp] # 这里使用 otlp 协议
processors: [batch]
exporters: [prometheus]
2、下载 OpenTelemetry Java Agent
从 github 上下载 agent,地址:https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases;默认情况下,代理中的指标是禁用的,这里可以通过设置环境变量来启用 OTEL_METRICS_EXPORTER=otlp。
export OTEL_METRICS_EXPORTER=otlp
java -javaagent:opentelemetry-javaagent.jar -jar target/springboot-otel-guides-0.0.1-SNAPSHOT.jar
3、移除关于 promethus 的配置和依赖
# application.properties 中移除
management.endpoints.web.exposure.include=prometheus
# pom.xml 中移除
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
重新启动 OTEL Collector 和 应用程序之后,再次访问 /hello,然后查看指标信息
~ curl -i http://localhost:8889/metrics | grep hello_total
# HELP hello_total
# TYPE hello_total counter
hello_total{job="springboot-otel-guides"} 1 #指标的表现形式和之前不同
前面两种,我使用了两种不同的采集方式(PromethusMeterRegistry 和 OpenTelemetryMeterRegistry) 和 两种不同的 receivers 方式将指标信息采集到 OpenTelemetry Collector 中。在这篇文章中提到的指标差异问题,在我的测试过程中其实体现不是很明显,从源码调试中捕获到的信息是,Micrometer 的 Metrics.globalRegistry 中,除了opentelemetry-javaagent 中的OpenTelemetryMeterRegistry 之外,还有 Micrometer 自己的 SimpleMeterRegistry,但是实际情况看起来是 runtime 使用了 OpenTelemetryMeterRegistry 而不是 SimpleMeterRegistry,这也就和文章中提及的可能注册"错误的 MeterRegistry 实例" 的条件没有成立。
实际上,SpringBoot 已经默认集成了 otlp exporters,其基本具备和 promethus 同样的接入方式。下面的案例中,将会去除 agent,直接使用 otlp registry。
SpringBoot 使用 otlp registry
因为在前面的步骤中已经移除了 promethus 相关的依赖和配置,应用服务通过集成 agent 来采集指标数据,在下面的测试中,将会移除 agent,然后引入新的依赖,使得能够使用 otlp 协议来发布指标数据。
引入 micrometer-registry-otlp 和 opentelemetry-exporter-otlp 依赖
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-otlp</artifactId>
<version>1.12.3</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.36.0</version>
</dependency>
management.otlp.metrics.export.url=http://localhost:4318/v1/metrics
修改 OpenTelemetry Collector 配置
receivers:
otlp:
protocols: # otlp 对应的两种协议方式
grpc:
endpoint: localhost:4317
http:
endpoint: localhost:4318
exporters:
debug:
verbosity: detailed
file:
path: /Users/glmapper/Downloads/metrics-otlp.json # 为了方便查看,这里我将指标信息直接 export 到文件中
service:
pipelines:
metrics:
receivers: [otlp] # 使用 otlp
exporters: [file] # 使用 file
PS:OtlpMeterRegistry 中默认使用了 http 协议,并通过 http://localhost:4318/v1/metrics 接口向 OpenTelemetry Collector POST 指标数据。
重新启动 OpenTelemetry Collector 和应用服务程序,下面是访问 /hello 之后,采集到的自定义指标数据如下:
{
"name": "hello.total",
"sum": {
"dataPoints": [
{
"startTimeUnixNano": "1711090372507000000",
"timeUnixNano": "1711090415605000000",
"asDouble": 1
}
],
"aggregationTemporality": 2,
"isMonotonic": true
}
},
从这里数据结构看,与前面使用 promethus 协议的指标数据结构差异是非常大的
# HELP hello_total
# TYPE hello_total counter
hello_total{job="springboot-otel-guides"} 2
这里我把 两种协议版本的数据样例格式提供一下,以便于需要的同学自行查看
otlp v1.1.0 版本的数据格式,其内部通过 opentelemetry/proto/resource/v1/metrics.proto 来描述
从源码角度分析 prometheus 和 otlp 协议的指标收集
这里主要以 micrometer 来看,围绕 micrometer-registry-otlp 和 micrometer-registry-prometheus 两个依赖实现的 MeterRegistry 指标收集代码来分析。MeterRegistry 作为 Micrometer 提供的指标管理的顶层抽象类,其除了提供一组共不同厂商扩展的抽象方法(如 newCounter等)之外,主要核心能力是内部通过维护一个 CHM 类型的 meterMap 来统一管理 Meter 数据。micrometer-registry-otlp 和 micrometer-registry-prometheus 两个包中最核心的也就是对于 MeterRegistry 抽象类的子类扩展实现。从指标数据的透出方式来看, micrometer-registry-otlp 是通过一个定时任务线程池来主动将指标数据 report 给 OpenTelemeter Collector,而 micrometer-registry-prometheus 则是提供一个对外访问的 endpoint 以供收集器来拉取指标数据。
prometheus#PrometheusMeterRegistry
prometheus 的实现 SpringBoot 对外提供的 endpoint 对应的类是 PrometheusScrapeEndpoint,其数据是从它内部持有的 collectorRegistry 对象获取,CollectorRegistry负责维护当前系统中所有的Collector实例。HTTPServer在接收到HTTP请求之后,会从 CollectorRegistry 中拿到所有的Collector实例,并调用其collect()方法获取所有样本,最后格式化为 Prometheus 的标准输出。CollectorRegistry 是 prometheus 客户端提供的能力。同样持有 CollectorRegistry 还有 PrometheusMeterRegistry,前面提到在 micrometer 中所有的指标数据是存在 meterMap 中的,但是在创建 Meter 时,PrometheusMeterRegistry 也会将 Meter 通过 applyToCollector 方法同步到 CollectorRegistry,这样就实现了 prometheus 和 micrometer 对于指标数据的链接。
otlp#OtlpMeterRegistry
这里主要分析下 OtlpMeterRegistry 的 push 模型,OtlpMeterRegistry 实现了 micrometer PushMeterRegistry 类,其内部提供了 publish 方法的实现,OtlpMeterRegistry#publish 实现了 meter 到 OpenTelemetry 的具体逻辑。
当流量进入之后,经过 /hello 接口中的自定义埋点代码,此时 meterRegistry 的注入实例为 OtlpMeterRegistry;但是实际上 OtlpMeterRegistry 对于 meter 数据并没有像 prometheus 那样还提供了内部自己模型的桥接逻辑 ,OtlpMeterRegistry 数据的管理是完全依托于 MeterRegistry 的。
从架构角度分析 OpenTelemetry Collector 数据流向
这里包括前面实例中的几种场景,首先是 OpenTelemetry Collector pull 应用服务 prometheus endpoint 吐出的数据,otlp collector 使用的是 receiver 和 expose 使用的是 prometheus 协议。
第二个是使用 OpenTelemetry Java instrumentation agent 收集指标的数据流向
最后是使用 micrometer-registry-otlp 方式,它使用的是类似于 micrometer-registry-prometheus 通过业务系统中通过代码打点的方式采集的,但是上报方式是和 agent 上报是一致的,这里就不单独提供数据流程示意图了。
总结
本文主要是研究 SpringBoot 如何集成 metrics ,文章中提供了基于 micrometer 和 agent 两种指标采集的形式;基于 micrometer 的方式又细分了基于 prometheus 协议和 otlp 协议两种方式。并且在使用 Otel collector 作为指标数据的中间组件,并提供了相应的实示例代码和示意图。希望通过本文能够帮助大家使用和理解 Springboot 中对于 micrometer 和 otlp 收集指标数据的基本流程和原理。
领取专属 10元无门槛券
私享最新 技术干货