对于企业级的应用程序来说,我们需要通过运行指标(metrics)的监控,来了解(监控)程序的运行状态。Vert.x的核心组件内置了大量的运行指标,并支持通过Micrometer来管理这些运行指标并向后端报告。
目前Vertx内置运行指标的核心组件包括: TCP/HTTP client and servers, DatagramSocket, EventBus and pools,Vertx也支持自定义指标。
Micrometer简介
Micrometer provides a simple facade over the instrumentation clients for a number of popular monitoring systems. Currently, it supports the following monitoring systems: Atlas, Datadog, Graphite, Ganglia, Influx, JMX, and Prometheus.
Micrometer为市场上主流的监控(子)系统提供了一个简单的"门面",目前Micrometer支持的监控系统包括:Atlas, Datadog, Graphite, Ganglia, Influx, JMX, and Prometheus. Micrometer实现了运行指标监控的一个门面设计模式(Facade Pattern),你可以用SLF4J来理解Micrometer,只不过slf4j用于管理程序日志,而micrometer用于管理程序运行指标。门面模式提供了一个统一的,与实现无关的接口,我们只使用接口进行编程,而不依赖具体实现。面模式来简化程序与主流监控系统的整合交互,降低了与他们的耦合。
如果不使用slf4j,我们的程序要同时支持JUL,Log4j,Logback等多种日志系统是很麻烦的,而通过slf4j,我们的程序只需要使用slf4j的标准日志接口(API)来记录日志,而日志记录的具体实现,则在运行时,由slf4j通过配置或者SPI机制适配到具体的日志框架完成。
Micrometer也是一个类似的门面框架,只不过用来采集程序指标的,Micrometer提供了标准的指标采集接口(门面),我们程序只使用这些稳定的标准的API来记录指标,而不用关系后端复杂的实现(如程序指标是上报到Influx? 还是Prometheus? 或者同时上报),通过Micrometer极大的简化了运行指标的采集编程,且我们的程序只依赖稳定的接口,而非变化的具体实现。
Micrometer中,使用Meter接口来抽象一个监控指标,监控指标则由MeterRegistry实例创建和持有。
- 根据指标类型的不同,Meter实现类包括: Timer, Counter, Gauge, DistributionSummary, LongTaskTimer, FunctionCounter, FunctionTimer, and TimeGauge.
- 根据指标上报(export)的后端不同,MeterRegistry的实现类包括: SimpleMeterRegistry, CompositeMeterRegistry, AtlasMeterRegistry, JmxMeterRegistry, PrometheusMeterRegistry, …
Micrometer还提供了大量内置针对JVM的监控指标,称之为Binders:
- 可用于监控JVM类加载的指标(class loader metrics) : ClassLoaderMetrics;
- 可用于监控JVM内存池的指标(JVM memory pool): JvmMemoryMetrics;
- 用于监控JVM垃圾收集的指标(GC metrics): JvmGcMetrics;
- 用于监控线程和CPU使用率的指标(thread and CPU utilization): JvmThreadMetrics, ProcessorMetrics;
Micrometer示例(JMX)
首先必须引入相关依赖:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.13.6</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-jmx</artifactId>
<version>1.13.6</version>
</dependency>
自定义监控指标的编程的大致逻辑是,先创建对应的MeterRegistry实例, 再创建对应的指标实例,并注册到MeterRegistry实例中; 而使用内置的指标这需要创建对应的Binder,并绑定到MeterRegistry实例中; MeterRegistry实例就可以将注册的监控值上报到对应的后端或者直接从MeterRegistry获取监控值。
MeterRegistry registry
//= new SimpleMeterRegistry(); // 无后端(保存在内存)
= new JmxMeterRegistry(JmxConfig.DEFAULT, Clock.SYSTEM); // 后端为JMX
// 创建一个Counter类型指标, 并注册到registry中
Counter counter1 = Counter.builder("my.counter.metric1")
.description("indicates periodic timer fire count.")
.tags("periodic", "Micrometer2")
.register(registry);
Vertx vertx = Vertx.vertx();
vertx.setPeriodic(4000, timerId -> {
// timer执行一次,指标值(+1)
counter1.increment();
});
// 通过registry, 获取指标值
double count = registry.get("my.counter.metric1").counter().count();
LOGGER.info("my.counter.metric1: " + count);
通过以下方式使用内建的binder实例,并绑定registry
JvmMemoryMetrics jvmMemoryMetrics = new JvmMemoryMetrics();
ProcessorMetrics processorMetrics = new ProcessorMetrics();
jvmMemoryMetrics.bindTo(registry);
processorMetrics.bindTo(registry);
double cpuUsage = registry.get("system.cpu.usage").gauge().value();
name对应的指标有可能不止一个, 需要根据tag区分; 例如:
double gss = registry.get("jvm.memory.used").tag("area", "heap").tag("id", "G1 Survivor Space").gauge().value();
double geg = registry.get("jvm.memory.used").tag("area", "heap").tag("id", "G1 Eden Space").gauge().value();
double gog = registry.get("jvm.memory.used").tag("area", "heap").tag("id", "G1 Old Gen").gauge().value();
LOGGER.info("CPU Usage%: " + cpuUsage);
LOGGER.info("Memory Heap : \n"
+ " G1 Survivor Space: " + gss
+ "\n G1 Old Gen: " + gog
+ "\n G1 Eden Space: " + geg);
可以通过以下方式遍历registry的所有指标
for (Meter meter : registry.getMeters()) {
String meterInfo = "";
Meter.Id meterId = meter.getId();
//meterId.getDescription();
meterInfo += (meterId.getName() + ", ");
meterInfo += (meterId.getType() + ", ");
meterInfo += (meterId.getTags() + ", ");
if (meter instanceof Counter) {
Counter c = (Counter) meter;
meterInfo += c.count();
} else if (meter instanceof Gauge) {
Gauge g = (Gauge) meter;
meterInfo += g.value();
} else if (meter instanceof CumulativeFunctionCounter) {
CumulativeFunctionCounter<?> cc = (CumulativeFunctionCounter<?>) meter;
meterInfo += cc.count();
} else {
meterInfo += meter.toString();
}
LOGGER.info(meterInfo);
}
执行结果如下:
2024-11-07 15:51:25 [信息] my.counter.metric1: 30.0
2024-11-07 15:51:25 [信息] CPU Usage%: 0.01603586752462649
2024-11-07 15:51:25 [信息] Memory Heap :
G1 Survivor Space: 4194304.0
G1 Old Gen: 368128.0
G1 Eden Space: 1.2582912E7
2024-11-07 15:51:29 [信息] my.counter.metric1: 31.0
2024-11-07 15:51:29 [信息] CPU Usage%: 0.04454708016282827
2024-11-07 15:51:29 [信息] Memory Heap :
G1 Survivor Space: 4194304.0
G1 Old Gen: 368128.0
G1 Eden Space: 1.2582912E7
通过JDK自带的jconsole,可以查看Micrometer上报的监控指标。
Vert.x Micrometer Metrics
Vert.x支持通过Micrometer将监控指标上报给后端,以上报给Prometheus为例,首先需要引入对应的依赖:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-micrometer-metrics</artifactId>
<version>4.5.10</version>
</dependency>
<!--
注意, Vert.x文档引入的依赖'micrometer-registry-prometheus'是会报错的, 需要使用'micrometer-registry-prometheus-simpleclient'
https://docs.micrometer.io/micrometer/reference/implementations/prometheus.html
Micrometer uses the Prometheus Java Client under the hood; there are two versions of it and Micrometer supports both. If you want to use the "new" client (1.x), use micrometer-registry-prometheus but if you want to use the "legacy" client (0.x), use micrometer-registry-prometheus-simpleclient.
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.13.6</version>
</dependency>
-->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus-simpleclient</artifactId>
<version>1.13.6</version>
</dependency>
Vertx有两种配置方式,一种是使用内置HTTP服务器将运行指标暴露给prometheus,注意,这种方式内置服务器的端口不要与应用的其他服务器端口相同;因一种是绑定到已有的http服务器中。
内置方式:
VertxPrometheusOptions vertxPrometheusOptions = new VertxPrometheusOptions()
.setEnabled(true)
.setStartEmbeddedServer(true)
.setEmbeddedServerOptions(new HttpServerOptions().setPort(8081))
.setEmbeddedServerEndpoint("/metrics") // prometheus默认使用的path为/metrics
;
MicrometerMetricsOptions metricsOptions = new MicrometerMetricsOptions().setPrometheusOptions(vertxPrometheusOptions).setEnabled(true);
Vertx vertx = Vertx.vertx(new VertxOptions().setMetricsOptions(metricsOptions));
整合到程序现有http服务器方式:
VertxPrometheusOptions vertxPrometheusOptions = new VertxPrometheusOptions().setEnabled(true);
MicrometerMetricsOptions metricsOptions = new MicrometerMetricsOptions().setPrometheusOptions(vertxPrometheusOptions).setEnabled(true);
Vertx vertx = Vertx.vertx(new VertxOptions().setMetricsOptions(metricsOptions));
Router router = Router.router(vertx);
router.route("/metrics").handler(PrometheusScrapingHandler.create());
vertx.createHttpServer().requestHandler(router).listen(8080);
下面写一个完整的案例:
package vertx.mon;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.ext.web.Router;
import io.vertx.micrometer.MicrometerMetricsOptions;
import io.vertx.micrometer.VertxPrometheusOptions;
import io.vertx.micrometer.backends.BackendRegistries;
public class Micrometer2 {
public static void main(String[] args) {
VertxPrometheusOptions vertxPrometheusOptions = new VertxPrometheusOptions()
.setEnabled(true)
.setStartEmbeddedServer(true)
.setEmbeddedServerOptions(new HttpServerOptions().setPort(8081))
.setEmbeddedServerEndpoint("/metrics");
MicrometerMetricsOptions metricsOptions = new MicrometerMetricsOptions().setPrometheusOptions(vertxPrometheusOptions).setEnabled(true);
Vertx vertx = Vertx.vertx(new VertxOptions().setMetricsOptions(metricsOptions));
添加自定指标,这里使用了micrometer内置的JVM binder:ProcessorMetrics, 包含JVM CPU使用率相关指标。
MeterRegistry meterRegistry = BackendRegistries.getDefaultNow();
ProcessorMetrics processorMetrics = new ProcessorMetrics();
processorMetrics.bindTo(meterRegistry);
HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);
router.route("/").handler(routingContext -> {
routingContext.response().end("hello");
});
server.requestHandler(router).listen(8080);
}
}
运行程序,可以通过对应端口(8081)和路径(/metrics)查看运行指标(注意,Vertx Http服务器的指标需要实际访问http页面才会出来):
在prometheus中配置抓取:
- job_name: "vertx_exporter"
scrape_interval: 10s
static_configs:
- targets: ["172.18.240.1:8081"]
这样,在prometheus就可以存储监控指标:
后续,还可以根据需要,通过Grafana生成可视化监控图表。
vertx_http_server_response_bytes_count{code="200",method="GET",route="/",} 3.0
vertx_http_server_response_bytes_sum{code="200",method="GET",route="/",} 15.0
vertx_http_server_response_bytes_count{code="404",method="GET",route="",} 1.0
vertx_http_server_response_bytes_sum{code="404",method="GET",route="",} 53.0