Перейти к основному содержимому

Краткое руководство по микрометру

· 8 мин. чтения

1. Введение

Micrometer обеспечивает простую видимость поверх клиентов измерительных приборов для ряда популярных систем мониторинга. В настоящее время он поддерживает следующие системы мониторинга: Atlas, Datadog, Graphite, Ganglia, Influx, JMX и Prometheus.

В этом руководстве мы познакомим вас с базовым использованием Micrometer и его интеграцией со Spring.

Для простоты мы возьмем Micrometer Atlas в качестве примера, чтобы продемонстрировать большинство наших вариантов использования.

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

Для начала добавим в pom.xml следующую зависимость :

<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-atlas</artifactId>
<version>1.7.1</version>
</dependency>

Последнюю версию можно найти здесь .

3. МетрРеестр

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

Самая простая форма реестра — SimpleMeterRegistry . Но в большинстве случаев мы должны использовать MeterRegistry , специально разработанный для нашей системы мониторинга; для Атласа это 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 . Также предоставляется набор статических построителей на основе этого глобального реестра для генерации счетчиков в Метриках :

@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<Counter> counterOptional = Optional.ofNullable(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. Прилавок

Счетчик просто сообщает количество по указанному свойству приложения. Мы можем создать собственный счетчик с помощью построителя Fluent или вспомогательного метода любого 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() == 1);

Как видно из фрагмента выше, мы попытались уменьшить счетчик на единицу, но можем монотонно увеличивать счетчик только на фиксированную положительную величину.

4.3. Таймеры

Чтобы измерить задержки или частоту событий в нашей системе, мы можем использовать Timers . Таймер сообщит , по крайней мере, общее время и количество событий определенного временного ряда.

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

SimpleMeterRegistry registry = new SimpleMeterRegistry();
Timer timer = registry.timer("app.event");
timer.record(() -> {
try {
TimeUnit.MILLISECONDS.sleep(15);
} catch (InterruptedException ignored) {
}
});

timer.record(30, TimeUnit.MILLISECONDS);

assertTrue(2 == timer.count());
assertThat(timer.totalTime(TimeUnit.MILLISECONDS)).isBetween(40.0, 55.0);

Для записи длительных событий мы используем LongTaskTimer :

SimpleMeterRegistry registry = new SimpleMeterRegistry();
LongTaskTimer longTaskTimer = LongTaskTimer
.builder("3rdPartyService")
.register(registry);

LongTaskTimer.Sample currentTaskId = longTaskTimer.start();
try {
TimeUnit.MILLISECONDS.sleep(2);
} catch (InterruptedException ignored) { }
long timeElapsed = currentTaskId.stop();

assertEquals(2L, timeElapsed/((int) 1e6),1L);

4.4. Измерять

Манометр показывает текущее значение счетчика.

В отличие от других счетчиков датчики должны сообщать данные только при наблюдении. Датчики могут быть полезны при мониторинге статистики кеша или коллекций:

SimpleMeterRegistry registry = new SimpleMeterRegistry();
List<String> 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. DistributionСводка

Распределение событий и простое резюме обеспечивает 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")
.publishPercentiles(0.3, 0.5, 0.95)
.publishPercentileHistogram()
.register(registry);

Теперь во фрагменте выше в реестре будут доступны три датчика с тегами percentile=0.3 , centile =0.5 и percentile =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);

Затем мы можем проверить, извлекая значения из этих трех процентильных датчиков :

Map<Double, Double> actualMicrometer = new TreeMap<>();
ValueAtPercentile[] percentiles = timer.takeSnapshot().percentileValues();
for (ValueAtPercentile percentile : percentiles) {
actualMicrometer.put(percentile.percentile(), percentile.value(TimeUnit.MILLISECONDS));
}

Map<Double, Double> expectedMicrometer = new TreeMap<>();
expectedMicrometer.put(0.3, 1946.157056);
expectedMicrometer.put(0.5, 3019.89888);
expectedMicrometer.put(0.95, 13354.663936);

assertEquals(expectedMicrometer, actualMicrometer);

Кроме того, Micrometer также поддерживает цель уровня обслуживания (гистограмму):

DistributionSummary hist = DistributionSummary
.builder("summary")
.serviceLevelObjectives(1, 10, 5)
.register(registry);

Как и в случае с процентилями, после добавления нескольких записей мы видим, что гистограмма довольно хорошо справляется с вычислениями:

Map<Integer, Double> actualMicrometer = new TreeMap<>();
HistogramSnapshot snapshot = hist.takeSnapshot();
Arrays.stream(snapshot.histogramCounts()).forEach(p -> {
actualMicrometer.put((Integer.valueOf((int) p.bucket())), p.count());
});

Map<Integer, Double> expectedMicrometer = new TreeMap<>();
expectedMicrometer.put(1,0D);
expectedMicrometer.put(10,2D);
expectedMicrometer.put(5,1D);

assertEquals(expectedMicrometer, actualMicrometer);

Как правило, гистограммы могут помочь проиллюстрировать прямое сравнение в отдельных сегментах. Гистограммы также можно масштабировать по времени, что весьма полезно для анализа времени отклика серверной службы:

Duration[] durations = {Duration.ofMillis(25), Duration.ofMillis(300), Duration.ofMillis(600)};
Timer timer = Timer
.builder("timer")
.sla(durations)
.publishPercentileHistogram()
.register(registry);

5. Связующие

Micrometer имеет несколько встроенных привязок для мониторинга JVM, кешей, ExecutorService и служб ведения журналов.

Когда дело доходит до мониторинга JVM и системы, мы можем отслеживать метрики загрузчика классов ( ClassLoaderMetrics ), пул памяти JVM ( JvmMemoryMetrics ) и метрики GC ( JvmGcMetrics ), а также использование потоков и ЦП ( JvmThreadMetrics , ProcessorMetrics ).

Мониторинг кэша (в настоящее время поддерживаются только Guava, EhCache, Hazelcast и Caffeine) поддерживается с помощью инструментов GuavaCacheMetrics , EhCache2Metrics , HazelcastCacheMetrics и CaffeineCacheMetrics . А для мониторинга службы возврата журналов мы можем привязать LogbackMetrics к любому допустимому реестру:

new LogbackMetrics().bind(registry);

Использование вышеуказанных связывателей очень похоже на LogbackMetrics, и все они довольно просты, поэтому мы не будем вдаваться в подробности здесь.

6. Весенняя интеграция

Spring Boot Actuator обеспечивает управление зависимостями и автоматическую настройку для Micrometer. Теперь он поддерживается в Spring Boot 2.0/1.x и Spring Framework 5.0/4.x.

Нам понадобится следующая зависимость (последнюю версию можно найти здесь ):

<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-spring-legacy</artifactId>
<version>1.3.20</version>
</dependency>

Без каких-либо дополнительных изменений в существующем коде мы включили поддержку Spring с помощью Micrometer. Метрики памяти JVM нашего приложения Spring будут автоматически зарегистрированы в глобальном реестре и опубликованы в конечной точке атласа по умолчанию: http://localhost:7101/api/v1/publish .

Существует несколько настраиваемых свойств для управления поведением при экспорте метрик, начиная с spring.metrics.atlas.* . Проверьте AtlasConfig , чтобы увидеть полный список свойств конфигурации для публикации Atlas.

Если нам нужно связать больше метрик, добавьте их только как @Bean в контекст приложения.

Скажем, нам нужен JvmThreadMetrics :

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

Что касается веб-мониторинга, он автоматически настраивается для каждой конечной точки в нашем приложении, но им можно управлять с помощью свойства конфигурации spring.metrics.web.autoTimeServerRequests .

Реализация по умолчанию предоставляет четыре измерения метрик для конечных точек: метод запроса HTTP, код ответа HTTP, URI конечной точки и информацию об исключении.

При ответе на запросы метрики, относящиеся к методу запроса ( GET , POST и т. д .), будут опубликованы в Atlas.

С помощью Atlas Graph API мы можем создать график для сравнения времени отклика для разных методов:

./79d244a7f8802638a65e6ee686ff6ecd.png

По умолчанию также будут сообщены коды ответов 20x , 30x , 40x , 50x :

./bbd0eff13c88149d44c510bc5bda9426.png

Мы также можем сравнить разные URI:

./89f6b9a9f20b36678e90d11ea56a2f55.png

Или проверьте метрики исключений:

./12f44db4085afe41a97c60c4aae76f91.png

Обратите внимание, что мы также можем использовать @Timed в классе контроллера или конкретных методах конечной точки для настройки тегов, длительной задачи, квантилей и процентилей показателей:

@RestController
@Timed("people")
public class PeopleController {

@GetMapping("/people")
@Timed(value = "people.all", longTask = true)
public List<String> listPeople() {
//...
}

}

Основываясь на приведенном выше коде, мы можем увидеть следующие теги, проверив конечную точку Atlas http://localhost:7101/api/v1/tags/name :

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

Micrometer также работает в функциональной веб-инфраструктуре, представленной в Spring Boot 2.0. Мы можем включить метрики, отфильтровав RouterFunction :

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

Мы также можем собирать метрики из источника данных и запланированных задач. Проверьте официальную документацию для более подробной информации.

7. Заключение

В этой статье мы представили метрику фасада Micrometer. Абстрагируясь и поддерживая несколько систем мониторинга с общей семантикой, инструмент позволяет легко переключаться между различными платформами мониторинга.

Как всегда, полный код реализации этой статьи можно найти на Github .