Кратко ръководство за микрометър

1. Въведение

Micrometer осигурява проста фасада над клиентите на инструментариума за редица популярни системи за наблюдение. В момента той поддържа следните системи за наблюдение: Atlas, Datadog, Graphite, Ganglia, Influx, JMX и Prometheus.

В тази статия ще ви представим основното използване на микрометъра и неговата интеграция с Spring.

За улеснение ще вземем Micrometer Atlas като пример, за да демонстрираме повечето от нашите случаи на използване.

2. Зависимост на Maven

Като начало нека добавим следната зависимост към pom.xml :

 io.micrometer micrometer-registry-atlas 0.12.0.RELEASE 

Най-новата версия можете да намерите тук.

3. MeterRegistry

В Micrometer, MeterRegistry е основният компонент, използван за регистриране на измервателни уреди. Можем да прегледаме регистъра и да разширим показателите на всеки измервателен уред, за да генерираме времеви редове в бекенда с комбинации от метрики и техните стойности на измерения.

Най-простата форма на регистъра е SimpleMeterRegistry . Но в повечето случаи трябва да използваме MeterRegistry, изрично проектиран за нашата система за наблюдение; за Atlas това е AtlasMeterRegistry .

CompositeMeterRegistry позволява да се добавят множество регистри. Той предоставя решение за публикуване на показатели за приложения едновременно в различни поддържани системи за наблюдение.

Можем да добавим всеки MeterRegistry, необходим за качване на данните на множество платформи:

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

В Micrometer има статична поддръжка на глобален регистър: Metrics.globalRegistry . Също така се предоставя набор от статични конструктори, базирани на този глобален регистър, за да генерират измервателни уреди в Metrics :

@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. Етикети и метри

4.1. Етикети

Идентификаторът на измервателния уред се състои от име и тагове. Препоръчва се да следваме конвенция за именуване, която разделя думите с точка, за да се гарантира преносимостта на имената на метрики в множество системи за наблюдение.

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

Етикетите могат да се използват за нарязване на показателя за разсъждения относно стойностите. В горния код page.visitors е името на измервателния уред, чийто етикет е age = 20s . В този случай броячът е предназначен да брои посетителите на страницата на възраст между 20 и 30 години.

За голяма система можем да добавим общи маркери към регистър, да кажем, че показателите са от определен регион:

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

4.2. Брояч

А Counter съобщава само за определен брой собственост на заявление. Можем да изградим персонализиран брояч с плавния конструктор или помощния метод на всеки MetricRegistry :

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);

Както се вижда от горния фрагмент, ние се опитахме да намалим брояча с един, но можем да увеличим брояча само монотонно с фиксирана положителна сума.

4.3. Таймери

За да измерим латентността или честотата на събитията в нашата система, можем да използваме таймери . A Таймер ще докладва най-малко общото време и събития разчитат на конкретни времеви серии.

Например можем да запишем събитие на приложение, което може да продължи няколко секунди:

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));

За да записваме продължителни събития, използваме 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. Габарит

Манометър показва текущата стойност на метър.

За разлика от другите измервателни уреди, измервателните уреди трябва да докладват данни само когато се наблюдават. Измервателните уреди могат да бъдат полезни при наблюдение на статистически данни за кеш памет, колекции и др .:

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 :

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());

Освен това, DistributionSummary и Timers могат да бъдат обогатени с квантили:

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

В фрагмента по-горе в регистъра ще бъдат налични три измервателни уреда с тагове quantile = 0,3 , quantile = 0,5 и quantile = 0,95 , показващи стойностите, под които попадат съответно 95%, 50% и 30% от наблюденията.

За да видим тези квантили в действие, нека добавим следните записи:

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);

След това можем да проверим чрез извличане на стойности в тези три квантилни измервателни уреда :

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)));

Освен това, Micrometer също поддържа хистограми:

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

Подобно на квантили, след като добавим няколко записа, можем да видим, че хистограмата се справя с изчисленията доста добре:

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

В тази статия представихме метричните фасадни микрометри. Чрез абстрахиране и поддържане на множество системи за наблюдение под обща семантика, инструментът прави превключването между различни платформи за наблюдение доста лесно.

Както винаги, пълният код за изпълнение на тази статия може да бъде намерен в Github.