Kurzanleitung zum Mikrometer

1. Einleitung

Das Mikrometer bietet eine einfache Fassade über den Instrumentierungsclients für eine Reihe gängiger Überwachungssysteme. Derzeit werden die folgenden Überwachungssysteme unterstützt: Atlas, Datadog, Graphit, Ganglien, Influx, JMX und Prometheus.

In diesem Artikel stellen wir die grundlegende Verwendung des Mikrometers und seine Integration in Spring vor.

Der Einfachheit halber nehmen wir als Beispiel den Mikrometeratlas, um die meisten unserer Anwendungsfälle zu demonstrieren.

2. Maven-Abhängigkeit

Fügen wir der pom.xml zunächst die folgende Abhängigkeit hinzu :

 io.micrometer micrometer-registry-atlas 0.12.0.RELEASE 

Die neueste Version finden Sie hier.

3. MeterRegistry

Im Mikrometer ist eine MeterRegistry die Kernkomponente für die Registrierung von Zählern. Wir können die Registrierung durchlaufen und die Metriken jedes Zählers weiterentwickeln, um im Backend eine Zeitreihe mit Kombinationen von Metriken und ihren Dimensionswerten zu generieren.

Die einfachste Form der Registrierung ist SimpleMeterRegistry . In den meisten Fällen sollten wir jedoch eine MeterRegistry verwenden, die explizit für unser Überwachungssystem entwickelt wurde. für Atlas ist es AtlasMeterRegistry .

Mit CompositeMeterRegistry können mehrere Registries hinzugefügt werden. Es bietet eine Lösung zum gleichzeitigen Veröffentlichen von Anwendungsmetriken auf verschiedenen unterstützten Überwachungssystemen.

Wir können jede MeterRegistry hinzufügen, die zum Hochladen der Daten auf mehrere Plattformen erforderlich ist:

CompositeMeterRegistry compositeRegistry = new CompositeMeterRegistry(); SimpleMeterRegistry oneSimpleMeter = new SimpleMeterRegistry(); AtlasMeterRegistry atlasMeterRegistry = new AtlasMeterRegistry(atlasConfig, Clock.SYSTEM); compositeRegistry.add(oneSimpleMeter); compositeRegistry.add(atlasMeterRegistry);

Es gibt eine statische globale Registrierungsunterstützung in Micrometer: Metrics.globalRegistry . Außerdem wird eine Reihe von statischen Buildern bereitgestellt, die auf dieser globalen Registrierung basieren, um Zähler in Metriken zu generieren :

@Test public void givenGlobalRegistry_whenIncrementAnywhere_thenCounted() { class CountedObject { private CountedObject() { Metrics.counter("objects.instance").increment(1.0); } } Metrics.addRegistry(new SimpleMeterRegistry()); Metrics.counter("objects.instance").increment(); new CountedObject(); Optional counterOptional = Metrics.globalRegistry .find("objects.instance").counter(); assertTrue(counterOptional.isPresent()); assertTrue(counterOptional.get().count() == 2.0); }

4. Tags und Meter

4.1. Stichworte

Eine Kennung eines Messgeräts besteht aus einem Namen und Tags. Es wird empfohlen, eine Namenskonvention zu befolgen, bei der Wörter durch einen Punkt getrennt werden, um die Portabilität von Metriknamen über mehrere Überwachungssysteme hinweg zu gewährleisten.

Counter counter = registry.counter("page.visitors", "age", "20s");

Tags können zum Schneiden der Metrik verwendet werden, um über die Werte nachzudenken. Im obigen Code ist page.visitors der Name des Messgeräts mit dem Alter = 20s als Tag. In diesem Fall soll der Zähler die Besucher der Seite im Alter zwischen 20 und 30 Jahren zählen.

Bei einem großen System können wir allgemeine Tags an eine Registrierung anhängen, beispielsweise, dass die Metriken aus einer bestimmten Region stammen:

registry.config().commonTags("region", "ua-east");

4.2. Zähler

Ein Zähler meldet lediglich eine Zählung über eine bestimmte Eigenschaft einer Anwendung. Wir können einen benutzerdefinierten Zähler mit dem fließenden Builder oder der Hilfsmethode jeder MetricRegistry erstellen :

Counter counter = Counter .builder("instance") .description("indicates instance count of the object") .tags("dev", "performance") .register(registry); counter.increment(2.0); assertTrue(counter.count() == 2); counter.increment(-1); assertTrue(counter.count() == 2);

Wie aus dem obigen Ausschnitt hervorgeht, haben wir versucht, den Zähler um eins zu verringern, aber wir können den Zähler nur monoton um einen festen positiven Betrag erhöhen.

4.3. Timer

Um Latenzen oder die Häufigkeit von Ereignissen in unserem System zu messen, können wir Timer verwenden . Ein Timer meldet mindestens die Gesamtzeit und die Anzahl der Ereignisse bestimmter Zeitreihen.

Beispielsweise können wir ein Anwendungsereignis aufzeichnen, das mehrere Sekunden dauern kann:

SimpleMeterRegistry registry = new SimpleMeterRegistry(); Timer timer = registry.timer("app.event"); timer.record(() -> { try { TimeUnit.MILLISECONDS.sleep(1500); } catch (InterruptedException ignored) { } }); timer.record(3000, MILLISECONDS); assertTrue(2 == timer.count()); assertTrue(4510 > timer.totalTime(MILLISECONDS) && 4500 <= timer.totalTime(MILLISECONDS));

Um lange laufende Ereignisse aufzuzeichnen , verwenden wir LongTaskTimer :

SimpleMeterRegistry registry = new SimpleMeterRegistry(); LongTaskTimer longTaskTimer = LongTaskTimer .builder("3rdPartyService") .register(registry); long currentTaskId = longTaskTimer.start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException ignored) { } long timeElapsed = longTaskTimer.stop(currentTaskId); assertTrue(timeElapsed / (int) 1e9 == 2);

4.4. Spur

Ein Messgerät zeigt den aktuellen Wert eines Messgeräts an.

Im Gegensatz zu anderen Messgeräten sollten Messgeräte nur dann Daten melden, wenn sie beobachtet werden. Messgeräte können nützlich sein, wenn Sie Statistiken zu Cache, Sammlungen usw. Überwachen:

SimpleMeterRegistry registry = new SimpleMeterRegistry(); List list = new ArrayList(4); Gauge gauge = Gauge .builder("cache.size", list, List::size) .register(registry); assertTrue(gauge.value() == 0.0); list.add("1"); assertTrue(gauge.value() == 1.0);

4.5. DistributionSummary

Die Verteilung von Ereignissen und eine einfache Zusammenfassung werden von DistributionSummary bereitgestellt :

SimpleMeterRegistry registry = new SimpleMeterRegistry(); DistributionSummary distributionSummary = DistributionSummary .builder("request.size") .baseUnit("bytes") .register(registry); distributionSummary.record(3); distributionSummary.record(4); distributionSummary.record(5); assertTrue(3 == distributionSummary.count()); assertTrue(12 == distributionSummary.totalAmount());

Darüber hinaus können DistributionSummary und Timer durch Quantile angereichert werden:

SimpleMeterRegistry registry = new SimpleMeterRegistry(); Timer timer = Timer.builder("test.timer") .quantiles(WindowSketchQuantiles .quantiles(0.3, 0.5, 0.95) .create()) .register(registry);

Im obigen Snippet sind drei Messgeräte mit den Tags Quantil = 0,3 , Quantil = 0,5 und Quantil = 0,95 in der Registrierung verfügbar, die die Werte angeben, unter die 95%, 50% bzw. 30% der Beobachtungen fallen.

Um diese Quantile in Aktion zu sehen, fügen wir die folgenden Datensätze hinzu:

timer.record(2, TimeUnit.SECONDS); timer.record(2, TimeUnit.SECONDS); timer.record(3, TimeUnit.SECONDS); timer.record(4, TimeUnit.SECONDS); timer.record(8, TimeUnit.SECONDS); timer.record(13, TimeUnit.SECONDS);

Dann können wir dies überprüfen, indem wir Werte in diesen drei Quantilmessgeräten extrahieren :

List quantileGauges = registry.getMeters().stream() .filter(m -> m.getType().name().equals("Gauge")) .map(meter -> (Gauge) meter) .collect(Collectors.toList()); assertTrue(3 == quantileGauges.size()); Map quantileMap = extractTagValueMap(registry, Type.Gauge, 1e9); assertThat(quantileMap, allOf( hasEntry("quantile=0.3",2), hasEntry("quantile=0.5", 3), hasEntry("quantile=0.95", 8)));

Außerdem unterstützt das Mikrometer auch Histogramme:

DistributionSummary hist = DistributionSummary .builder("summary") .histogram(Histogram.linear(0, 10, 5)) .register(registry);

Ähnlich wie bei Quantilen können wir nach dem Anhängen mehrerer Datensätze sehen, dass das Histogramm die Berechnung ziemlich gut handhabt:

Map histograms = extractTagValueMap(registry, Type.Counter, 1.0); assertThat(histograms, allOf( hasEntry("bucket=0.0", 0), hasEntry("bucket=10.0", 2), hasEntry("bucket=20.0", 2), hasEntry("bucket=30.0", 1), hasEntry("bucket=40.0", 1), hasEntry("bucket=Infinity", 0)));

Generally, histograms can help illustrate a direct comparison in separate buckets. Histograms can also be time scaled, which is quite useful for analyzing backend service response time:

SimpleMeterRegistry registry = new SimpleMeterRegistry(); Timer timer = Timer .builder("timer") .histogram(Histogram.linearTime(TimeUnit.MILLISECONDS, 0, 200, 3)) .register(registry); //... assertThat(histograms, allOf( hasEntry("bucket=0.0", 0), hasEntry("bucket=2.0E8", 1), hasEntry("bucket=4.0E8", 1), hasEntry("bucket=Infinity", 3)));

5. Binders

The Micrometer has multiple built-in binders to monitor the JVM, caches, ExecutorService and logging services.

When it comes to JVM and system monitoring, we can monitor class loader metrics (ClassLoaderMetrics), JVM memory pool (JvmMemoryMetrics) and GC metrics (JvmGcMetrics), thread and CPU utilization (JvmThreadMetrics, ProcessorMetrics).

Cache monitoring (currently, only Guava, EhCache, Hazelcast, and Caffeine are supported) is supported by instrumenting with GuavaCacheMetrics, EhCache2Metrics, HazelcastCacheMetrics, and CaffeineCacheMetrics. And to monitor log back service, we can bind LogbackMetrics to any valid registry:

new LogbackMetrics().bind(registry);

The usage of above binders are quite similar to LogbackMetrics and are all rather simple, so we won’t dive into further details here.

6. Spring Integration

Spring Boot Actuator provides dependency management and auto-configuration for Micrometer. Now it's supported in Spring Boot 2.0/1.x and Spring Framework 5.0/4.x.

We'll need the following dependency (the latest version can be found here):

 io.micrometer micrometer-spring-legacy 0.12.0.RELEASE 

Without any further change to existing code, we have enabled Spring support with the Micrometer. JVM memory metrics of our Spring application will be automatically registered in the global registry and published to the default atlas endpoint: //localhost:7101/api/v1/publish.

There're several configurable properties available to control metrics exporting behaviors, starting with spring.metrics.atlas.*. Check AtlasConfig to see a full list of configuration properties for Atlas publishing.

If we need to bind more metrics, only add them as @Bean to the application context.

Say we need the JvmThreadMetrics:

@Bean JvmThreadMetrics threadMetrics(){ return new JvmThreadMetrics(); }

As for web monitoring, it's auto-configured for every endpoint in our application, yet manageable via a configuration property: spring.metrics.web.autoTimeServerRequests.

The default implementation provides four dimensions of metrics for endpoints: HTTP request method, HTTP response code, endpoint URI, and exception information.

When requests are responded, metrics relating to request method (GET, POST, etc.) will be published in Atlas.

With Atlas Graph API, we can generate a graph to compare the response time for different methods:

By default, response codes of 20x, 30x, 40x, 50x will also be reported:

We can also compare different URIs :

or check exception metrics:

Note that we can also use @Timed on the controller class or specific endpoint methods to customize tags, long task, quantiles, and percentiles of the metrics:

@RestController @Timed("people") public class PeopleController { @GetMapping("/people") @Timed(value = "people.all", longTask = true) public List listPeople() { //... } }

Based on the code above, we can see the following tags by checking Atlas endpoint //localhost:7101/api/v1/tags/name:

["people", "people.all", "jvmBufferCount", ... ]

Micrometer also works in the function web framework introduced in Spring Boot 2.0. Metrics can be enabled by filtering the RouterFunction:

RouterFunctionMetrics metrics = new RouterFunctionMetrics(registry); RouterFunctions.route(...) .filter(metrics.timer("server.requests"));

Metrics from the data source and scheduled tasks can also be collected. Check the official documentation for more details.

7. Conclusion

In diesem Artikel haben wir das Metrikfassaden-Mikrometer vorgestellt. Durch das Abstrahieren und Unterstützen mehrerer Überwachungssysteme unter gemeinsamer Semantik erleichtert das Tool das Wechseln zwischen verschiedenen Überwachungsplattformen.

Wie immer finden Sie den vollständigen Implementierungscode dieses Artikels auf Github.