Vectorjmx_metrics源设计解读:基于 JMX 协议采集 JVM 指标(RFC 3642)
【免费下载链接】vectorA high-performance observability data pipeline.项目地址: https://gitcode.com/GitHub_Trending/vect/vector
本文是对 Vector 官方设计文档 RFC 3642(2020-08-28,采集 JVM 的 JMX 指标) 的完整技术解读。该 RFC 为 Vector 规划了一个名为jmx_metrics的新指标源(metrics source),用于通过 JMX(Java Management Extensions)协议直接连接 JVM 进程并采集其运行指标。读完本文,你将掌握该源的架构方案、完整的指标清单与命名约定、端到端的 TOML 配置方法,以及它与 Prometheus JMX exporter、Telegraf Jolokia 等既有方案的设计取舍。需要说明的是,本仓库当前快照中src/sources/尚未出现jmx_metrics的实现(全仓库仅此 RFC 提及 jmx),因此本文以该设计提案为主体,并结合仓库中现有源组件的注册模式与配置惯例做对照解读。
背景:为什么需要为 Vector 增加 JMX 指标源
JVM 是运行现代应用的主流平台之一,同时也是 Kafka、Cassandra 等大量基础设施工具的运行时基础。用户的诉求很直接:收集、转换并转发指标,以便更好地观察基于 JVM 的应用程序的性能表现。而 JMX 正是 Java 平台暴露这些运行指标的标准通道,通过 MBean 可以访问堆/非堆内存、GC、线程、类加载、文件描述符、操作系统与运行时信息等数据。
RFC 的动机也契合 Vector 自身的定位——"one tool"(一个工具即可完成可观测性数据的采集与转发):尽可能多地接入各类数据源,降低用户"无法从自己使用的工具中摄取指标"的概率。从仓库侧看,src/sources/mod.rs 中每个源都以 feature flag(如sources-prometheus_scrape、sources-host_metrics)条件编译注册,jmx_metrics若落地也会遵循同样的注册模式。
RFC 的范围界定(Scope)
RFC 将本次变更的范围收敛得非常小,仅包含一项内容:
- 新增一个用于采集基于 JVM 的指标的新源(source),通过 JMX 协议实现。
其余增强(如 MBean 的 accept/deny 过滤、规则化改写引擎)均被明确列入"未来工作"而非本 RFC 范围,这也符合 rfcs/README.md 中对 RFC "Keep the scope small"(保持范围小)的要求:将未来改进显式列入 Scope 之外,既让讨论聚焦,也让实现更快交付。
内部方案:jmx_metrics源的总体设计
技术选型:Rust JMX 客户端直连 JVM
RFC 建议构建一个名为jmx_metrics(名称待最终确认)的单一源,推荐实现方式是使用 Rust 的 JMX 客户端,按配置中指定的地址连接 JVM 服务器。这意味着用户需要在 JVM 实例上预先配置 JMX,并让其绑定到 Vector 可以查询的外部端口。相应的端到端数据流为:
JVM(开启 JMX 远程访问,暴露端口) │ JMX/RMI 协议 ▼ jmx_metrics 源(Rust JMX 客户端,按 endpoint 连接并抓取) │ 解析查询结果,转换为 Vector 指标事件 ▼ Vector 管道(transform → sink)运行模式:独立抓取 vs Java Agent
RFC 对比了两种主流做法,并引用了 Prometheus JMX exporter 的官方说明:
This exporter is intended to be run as a Java Agent, exposing a HTTP server and serving metrics of the local JVM. It can be also run as an independent HTTP server and scrape remote JMX targets, but this has various disadvantages, such as being harder to configure and being unable to expose process metrics (e.g., memory and CPU usage). Running the exporter as a Java Agent is thus strongly encouraged.
要点可归纳为:
- Java Agent 模式:agent 随 JVM 进程一起启动,作为本地 HTTP server 暴露指标,能拿到进程级指标(内存、CPU),但需要侵入 Java 应用启动参数;
- 独立抓取模式:Vector 以独立进程身份远程 scrape JMX 目标,配置更复杂且拿不到部分进程级指标,但无需改动 Java 应用;
- RFC 明确标注"这可能需要在推进过程中做一些测试(This may require some testing as we proceed)",并在文末的 Outstanding Questions 中保留了"Java agent or not(是否采用 Java agent 模式)"这一待决问题。
从仓库现状看,Vector 已有一个同类"主动抓取"型源——src/sources/prometheus/ 下的 Prometheus scrape 源(mod.rs),其"按 endpoint 周期性抓取、解析后产出指标事件"的模型,与jmx_metrics的设计在架构上同构,可作为实现时的参照。
完整指标清单:默认 JVM 指标全集
RFC 给出了通过 JMX 可获取的默认 JVM 指标全集(约 118 个),并逐个标注了建议的指标类型与标签。这部分是 RFC 的核心资产,下面按"已明确类型"与"类型待定(untyped)"两组完整保留。其中untyped的含义是"类型尚不明确,但很可能是 gauge",需要更深入的研究来确定:1)是否保留这些指标;2)若保留,其确切类型。
已明确类型的指标(36 个)
| 指标名 | 类型 | 标签 |
|---|---|---|
jmx_up | 0/1 运行状态(用作 uptime 指标) | — |
jmx_config_reload_success_total | counter | — |
jmx_config_reload_failure_total | counter | — |
jmx_process_cpu_seconds_total | counter | — |
jmx_process_start_time_seconds | gauge | — |
jmx_process_open_fds | gauge | — |
jmx_process_max_fds | gauge | — |
jmx_jvm_threads_current | gauge | — |
jmx_jvm_threads_daemon | gauge | — |
jmx_jvm_threads_peak | gauge | — |
jmx_jvm_threads_started_total | counter | — |
jmx_jvm_threads_deadlocked | gauge | — |
jmx_jvm_threads_deadlocked_monitor | gauge | — |
jmx_jvm_threads_state | gauge | state |
jmx_jvm_buffer_pool_used_bytes | gauge | pool |
jmx_jvm_buffer_pool_capacity_bytes | gauge | pool |
jmx_jvm_buffer_pool_used_buffers | gauge | pool |
jmx_jvm_classes_loaded | gauge | — |
jmx_jvm_classes_loaded_total | counter | — |
jmx_jvm_classes_unloaded_total | counter | — |
jmx_jvm_gc_collection_seconds | summary | gc |
jmx_jvm_memory_bytes_used | gauge | area |
jmx_jvm_memory_bytes_committed | gauge | area |
jmx_jvm_memory_bytes_max | gauge | area |
jmx_jvm_memory_bytes_init | gauge | area |
jmx_jvm_memory_pool_bytes_used | gauge | pool |
jmx_jvm_memory_pool_bytes_committed | gauge | pool |
jmx_jvm_memory_pool_bytes_max | gauge | pool |
jmx_jvm_memory_pool_bytes_init | gauge | pool |
jmx_jvm_memory_pool_allocated_bytes_total | counter | pool |
jmx_jvm_info | gauge | version、vendor、runtime |
jmx_java_lang_MemoryPool_UsageThresholdSupported | gauge | name |
jmx_java_lang_Threading_ThreadContentionMonitoringEnabled | gauge | — |
jmx_java_lang_OperatingSystem_CommittedVirtualMemorySize | gauge | — |
jmx_java_lang_GarbageCollector_LastGcInfo_memoryUsageAfterGc_used | counter? | name、key |
jmx_java_lang_Threading_ThreadContentionMonitoringSupported | gauge | — |
其中两个指标类型仍标注了 TBD(待定):
jmx_java_lang_Memory_HeapMemoryUsage_committed(maybe gauge? TBD)jmx_java_lang_OperatingSystem_TotalSwapSpaceSize(maybe gauge? TBD)
类型待定(untyped)的指标(82 个)
下表完整保留 RFC 中所有untyped指标及其标签约定:
| 指标名 | 标签 |
|---|---|
jmx_java_lang_MemoryPool_CollectionUsage_max | name |
jmx_java_lang_Runtime_StartTime | — |
jmx_java_lang_GarbageCollector_LastGcInfo_endTime | name |
jmx_java_lang_Memory_HeapMemoryUsage_max | — |
jmx_java_lang_MemoryPool_UsageThreshold | name |
jmx_java_lang_MemoryPool_CollectionUsageThresholdCount | name |
jmx_java_lang_Memory_NonHeapMemoryUsage_used | — |
jmx_java_lang_Threading_PeakThreadCount | — |
jmx_java_lang_MemoryPool_PeakUsage_used | name |
jmx_java_lang_ClassLoading_TotalLoadedClassCount | — |
jmx_java_lang_OperatingSystem_MaxFileDescriptorCount | — |
jmx_java_lang_ClassLoading_Verbose | — |
jmx_java_lang_GarbageCollector_LastGcInfo_id | name |
jmx_java_lang_Threading_CurrentThreadUserTime | — |
jmx_java_lang_GarbageCollector_LastGcInfo_memoryUsageAfterGc_committed | name、key |
jmx_java_lang_Threading_ThreadCount | — |
jmx_java_lang_MemoryPool_PeakUsage_committed | name |
jmx_java_lang_Memory_ObjectPendingFinalizationCount | — |
jmx_java_lang_MemoryPool_Usage_used | name |
jmx_java_lang_GarbageCollector_CollectionCount | name |
jmx_java_lang_Threading_SynchronizerUsageSupported | — |
jmx_java_lang_Runtime_BootClassPathSupported | — |
jmx_java_nio_BufferPool_Count | name |
jmx_java_lang_GarbageCollector_LastGcInfo_GcThreadCount | name |
jmx_java_lang_GarbageCollector_LastGcInfo_memoryUsageBeforeGc_committed | name、key |
jmx_java_lang_Threading_CurrentThreadCpuTimeSupported | — |
jmx_java_lang_ClassLoading_LoadedClassCount | — |
jmx_java_lang_MemoryPool_CollectionUsage_init | name |
jmx_java_lang_MemoryPool_PeakUsage_max | name |
jmx_java_lang_MemoryPool_Usage_max | name |
jmx_java_lang_GarbageCollector_Valid | name |
jmx_java_lang_GarbageCollector_LastGcInfo_memoryUsageBeforeGc_used | name、key |
jmx_java_lang_Threading_ThreadAllocatedMemoryEnabled | — |
jmx_java_lang_MemoryManager_Valid | name |
jmx_java_lang_MemoryPool_Usage_init | name |
jmx_java_lang_OperatingSystem_ProcessCpuLoad | — |
jmx_java_lang_MemoryPool_CollectionUsage_committed | name |
jmx_java_lang_OperatingSystem_TotalPhysicalMemorySize | — |
jmx_java_lang_Memory_NonHeapMemoryUsage_committed | — |
jmx_java_lang_Compilation_TotalCompilationTime | — |
jmx_java_lang_Memory_Verbose | — |
jmx_java_lang_MemoryPool_Valid | name |
jmx_java_lang_OperatingSystem_FreeSwapSpaceSize | — |
jmx_java_lang_MemoryPool_UsageThresholdExceeded | name |
jmx_java_lang_Threading_CurrentThreadCpuTime | — |
jmx_java_lang_MemoryPool_CollectionUsageThreshold | name |
jmx_java_lang_GarbageCollector_CollectionTime | name |
jmx_java_lang_Compilation_CompilationTimeMonitoringSupported | — |
jmx_java_lang_MemoryPool_Usage_committed | name |
jmx_java_lang_Memory_NonHeapMemoryUsage_init | — |
jmx_java_lang_MemoryPool_PeakUsage_init | name |
jmx_java_lang_GarbageCollector_LastGcInfo_startTime | name |
jmx_java_lang_OperatingSystem_AvailableProcessors | — |
jmx_java_lang_MemoryPool_CollectionUsageThresholdSupported | name |
jmx_java_lang_GarbageCollector_LastGcInfo_memoryUsageBeforeGc_max | name、key |
jmx_java_lang_ClassLoading_UnloadedClassCount | — |
jmx_java_nio_BufferPool_MemoryUsed | name |
jmx_java_nio_BufferPool_TotalCapacity | name |
jmx_java_lang_Memory_HeapMemoryUsage_used | — |
jmx_java_lang_MemoryPool_CollectionUsage_used | name |
jmx_java_lang_Memory_HeapMemoryUsage_init | — |
jmx_java_lang_OperatingSystem_SystemCpuLoad | — |
jmx_java_lang_GarbageCollector_LastGcInfo_memoryUsageAfterGc_init | name、keys |
jmx_java_lang_GarbageCollector_LastGcInfo_memoryUsageBeforeGc_init | name、key |
jmx_java_lang_Threading_ThreadAllocatedMemorySupported | — |
jmx_java_lang_Memory_NonHeapMemoryUsage_max | — |
jmx_java_lang_Threading_DaemonThreadCount | — |
jmx_java_lang_Threading_ThreadCpuTimeSupported | — |
jmx_java_lang_OperatingSystem_SystemLoadAverage | — |
jmx_java_lang_Threading_TotalStartedThreadCount | — |
jmx_java_lang_OperatingSystem_ProcessCpuTime | — |
jmx_java_lang_OperatingSystem_FreePhysicalMemorySize | — |
jmx_java_lang_Runtime_Uptime | — |
jmx_java_lang_MemoryPool_CollectionUsageThresholdExceeded | name |
jmx_java_lang_GarbageCollector_LastGcInfo_duration | name |
jmx_java_lang_Threading_ObjectMonitorUsageSupported | — |
jmx_java_lang_GarbageCollector_LastGcInfo_memoryUsageAfterGc_max | name、key |
jmx_java_lang_MemoryPool_UsageThresholdCount | name |
jmx_java_lang_Threading_ThreadCpuTimeEnabled | — |
jmx_java_lang_OperatingSystem_OpenFileDescriptorCount | — |
指标命名与标签规范
RFC 明确了三条命名与标签规则:
- 命名规则:
jmx_+ 指标名(jmx _ metric_name),与 JMX 内部命名保持一致; - 通用标签:所有指标都会被打上
endpoint和host两个标签,用于区分采集来源; - 维度标签:凡是带多实例的 MBean(如各 MemoryPool、各 GarbageCollector、各 BufferPool),以
pool、area、name、gc、state、key等标签区分维度,例如jmx_jvm_memory_bytes_used{area="heap"}与jmx_jvm_memory_pool_bytes_used{pool="G1 Eden Space"}。
此外 RFC 特别指出:上述清单只是默认 JVM 可通过 JMX 暴露的指标。具体应用(Kafka、Cassandra、Tomcat 等)会暴露各自专属的 MBean 和指标,实现时可以像 Prometheus jmx_exporter 的 JmxScraper 那样逐个遍历 MBean 并解析其输出,进而构造并返回指标。Prometheus exporter 中针对特定 MBean 对象的 accept/deny 模式,以及用于匹配并构造特定指标的规则化改写引擎,都被 RFC 视为合理(但属后续)的工作项——RFC 同时提出一个开放问题:这类过滤与改写,是否用 Vector 的 transform 来实现可能更有效?
配置示例(Doc-level Proposal)
RFC 为jmx_metrics源规划了如下配置结构(完整保留原文示例):
[sources.my_source_id] type = "jmx_metrics" # required endpoint = "service:jmx:rmi:///jndi/rmi://127.0.0.1:1234/jmxrmi" # required - address of the JMX webserver to scrape. username = "user" # optional - username for any JMX authentication. password = "password" # optional - password for any JMX authentication. scrape_interval_secs = 15 # optional, default, seconds namespace = "jmx" # optional, default is "jmx", namespace to put metrics under各字段的含义与默认值归纳如下:
| 字段 | 必填 | 默认值 | 说明 |
|---|---|---|---|
type | 是 | — | 固定为"jmx_metrics"(RFC 撰写时名称待最终确认) |
endpoint | 是 | — | 要抓取的 JMX 服务地址,典型形式为service:jmx:rmi:///jndi/rmi://<host>:<port>/jmxrmi,即标准 JMX RMI 连接 URL |
username | 否 | — | JMX 认证用户名(若 JVM 开启了 JMX 认证) |
password | 否 | — | JMX 认证密码(与username配合使用) |
scrape_interval_secs | 否 | 15 | 抓取间隔,单位秒,控制指标采集的周期 |
namespace | 否 | "jmx" | 指标所属的命名空间(namespace),用于将指标归类放置 |
RFC 还补充:后续应增加一份带认证(authentication)的配置指南,帮助用户在使用 JMX 用户名/密码认证的场景下完成端到端配置。
关于namespace字段,仓库中已有可对照的既有实现惯例:例如 src/sources/static_metrics.rs 中的namespace配置同样带有默认值(默认"static"),并在构造指标事件时通过namespace: Some(self.namespace.clone())注入。可以推断,jmx_metrics的namespace落地后会走相似的 serde 反序列化与默认值逻辑。
设计权衡:Rationale、Drawbacks 与 Alternatives
为什么值得做(Rationale)
JVM 是运行应用的流行平台,也是 Kafka、Cassandra 等基础设施工具的运行时基础,用户经常需要理解它的性能。同时,作为 Vector 愿景("one tool" 摄取并转发可观测性数据)的一部分,尽可能多地增加数据源,可以减少用户无法从自身工具摄取指标的概率。
代价(Drawbacks)
新增一个源带来的主要代价是额外的维护与集成测试负担——这也是每个新源组件都需要面对的成本。
被否决的替代方案(Alternatives)
RFC 认真评估了两条"不新增源"的替代路线:
- 借道 Prometheus jmx_exporter / Telegraf:让用户自行运行 Telegraf 或 Prometheus 的 jmx_exporter,再让 Vector 用现有的 Prometheus 源去抓取这些数据;
- 借道 jmxtrans:使用 jmxtrans 之类的工具,并为 Vector 编写一个 OutputWriter 插件。
RFC 作者最终否决了这两条路线,理由是它们与 Vector 的核心原则相冲突:
One Tool. All Data. - One simple tool gets your logs, metrics, and traces (coming soon) from A to B.
即"一个工具,全部数据":一个简单工具即可把日志、指标(以及即将支持的追踪)从 A 点送到 B 点。不过 RFC 也保留了灵活性:如果用户已经在运行 Prometheus,完全可以走 Prometheus 那条路径——这与仓库中现有的 src/sources/prometheus/ 源(scrape / remote_write / pushgateway 三种模式,见 mod.rs)天然衔接。
业界已有实践(Prior Art)
RFC 调研了当时业界的 JMX 指标采集方案,作为设计参考(此处仅列名称,详见原 RFC):
- Prometheus jmx_exporter:Java Agent 或独立 HTTP server 形式,自带维护的 agent;
- Telegraf jolokia 与 jolokia2:通过 Jolokia agent 进行采集;
- collectd GenericJMX 插件:collectd 生态的通用 JMX 采集方案;
- panopticon-tui:Scala 生态的相关实现;
- replicante-io agents 中的 kafka agent:面向 Kafka 的 agent 实现。
其中 Prometheus 与 Telegraf 方案均依赖 Java 侧 agent(Prometheus 使用自维护 agent,Telegraf 使用 Jolokia agent),这也呼应了前文"Java agent or not"这一待决问题。
待决问题与实施计划
Outstanding Questions(待决问题)
RFC 明确遗留的唯一关键问题:是否采用 Java agent 模式(Java agent or not)。该决定将直接影响用户侧的使用方式(是给 JVM 加启动参数,还是仅配置 Vector 的 endpoint 远程抓取),以及进程级指标的可得性。
Plan Of Attack(攻击计划)与 Future Work(未来工作)
实施以增量步骤推进,首要一步是:
- 提交包含初始源实现的 PR(Submit a PR with the initial source implementation)
计划中的未来工作包括:
- accept/deny 列表:针对 MBean 对象模式的过滤列表,用于控制采集哪些 MBean;
- 规则化改写引擎:用于匹配并构造特定指标的规则化引擎(对标 Prometheus jmx_exporter 的配置化能力)。
结合仓库现状:这类源在 Vector 中如何落地
虽然jmx_metrics在本仓库快照中尚未实现,但从仓库现有结构可以清晰推断其落地路径:
- 源注册模式:src/sources/mod.rs 展示了所有源的注册方式——以
#[cfg(feature = "sources-xxx")]特性开关声明模块,jmx_metrics落地时会以sources-jmx_metrics之类的 feature 出现在其中,并配套在根 Cargo.toml 中声明依赖; - 抓取型源参照:src/sources/prometheus/ 的 scrape 实现(周期性按 endpoint 抓取、解析文本、产出带 namespace 的指标事件)是
jmx_metrics最接近的架构参照; - 配置惯例参照:src/sources/static_metrics.rs 中的
namespace默认值与指标事件构造逻辑,为 RFC 中namespace = "jmx"的默认值设计提供了现成的实现范式; - 流程背景:本 RFC 位于仓库的 rfcs/ 目录,遵循 rfcs/README.md 所描述的 Vector RFC 流程——先以文档形式达成共识,再进入实施阶段,这解释了为什么该设计文档先行于实现存在。
对于希望在生产环境中采集 JVM 指标的读者,在jmx_metrics落地之前,最务实的路径正是 RFC 在 Alternatives 中给出的方案:为 JVM 部署 Prometheus jmx_exporter(或 Jolokia),再通过 Vector 现有的 Prometheus scrape 源将指标纳入统一管道。
【免费下载链接】vectorA high-performance observability data pipeline.项目地址: https://gitcode.com/GitHub_Trending/vect/vector
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考