Micrometer для Java: полное руководство с примерами
1. Что это и зачем нужна
Micrometer — это библиотека для сбора метрик приложений на Java (и JVM-языках), предоставляющая фасад поверх популярных систем мониторинга, таких как Prometheus, Graphite, Datadog, InfluxDB, New Relic и других. Она реализует спецификацию Micrometer Observation API и является де-факто стандартом для сбора метрик в экосистеме Spring Boot (начиная с версии 2.0).
Зачем она нужна? В современных микросервисных архитектурах критически важно понимать, что происходит с приложением: сколько запросов обрабатывается, какова задержка ответов, сколько памяти потребляется, как часто возникают ошибки. Micrometer позволяет унифицировать сбор этих данных, не привязываясь к конкретному бэкенду мониторинга. Вы пишете код один раз, а переключаться между Prometheus, Graphite или CloudWatch можно простым изменением зависимости в pom.xml или build.gradle.
Библиотека предоставляет богатый набор инструментов: счетчики (Counter), гистограммы (Histogram), таймеры (Timer), распределения (DistributionSummary), а также поддержку тегов (tags) для многомерной агрегации метрик. Встроенная интеграция с JVM (сборщик мусора, потоки, память) и популярными фреймворками (Spring, Hibernate, Jetty, Tomcat) делает её незаменимой для production-мониторинга.
2. Установка
Micrometer распространяется через Maven Central. Для Java-проектов используется Maven или Gradle. Ниже приведены точные команды для добавления зависимостей.
Maven (pom.xml)
<!-- Основной модуль Micrometer -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
<version>1.14.5</version> <!-- Проверьте актуальную версию на mvnrepository.com -->
</dependency>
<!-- Регистрация для Prometheus -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<version>1.14.5</version>
</dependency>
Gradle (build.gradle)
dependencies {
implementation 'io.micrometer:micrometer-core:1.14.5'
implementation 'io.micrometer:micrometer-registry-prometheus:1.14.5'
}
Важно: Если вы используете Spring Boot, достаточно добавить spring-boot-starter-actuator и micrometer-registry-prometheus — Spring Boot автоматически сконфигурирует MeterRegistry.
3. Быстрый старт — минимальный рабочий пример
Создадим простое консольное приложение, которое собирает метрики и экспортирует их в формате Prometheus.
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
public class QuickStart {
public static void main(String[] args) throws InterruptedException {
// 1. Создаём реестр метрик для Prometheus
PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
// 2. Создаём счётчик
Counter counter = Counter.builder("my.counter")
.description("Пример счётчика")
.tag("env", "test")
.register(registry);
// 3. Инкрементируем счётчик в цикле
for (int i = 0; i < 5; i++) {
counter.increment();
Thread.sleep(1000);
}
// 4. Выводим метрики в формате Prometheus
System.out.println("=== Prometheus Metrics ===");
System.out.println(registry.scrape());
}
}
Вывод в консоль:
=== Prometheus Metrics ===
# HELP my_counter_total Пример счётчика
# TYPE my_counter_total counter
my_counter_total{env="test",} 5.0
4. Основные методы/функции/классы
Рассмотрим 8 ключевых компонентов Micrometer, которые покрывают 95% задач сбора метрик.
4.1. Counter (Счётчик)
Сигнатура: Counter.builder(String name) -> Counter.Builder
Что делает: Создаёт счётчик, который может только увеличиваться (монотонно возрастающее значение). Используется для подсчёта событий: количество запросов, ошибок, обработанных сообщений.
Counter requests = Counter.builder("http.requests")
.description("Total HTTP requests")
.tag("method", "GET")
.register(registry);
requests.increment(); // +1
requests.increment(5.0); // +5
4.2. Timer (Таймер)
Сигнатура: Timer.builder(String name) -> Timer.Builder
Что делает: Измеряет время выполнения операций и частоту событий. Автоматически записывает гистограмму, p50/p95/p99 перцентили и количество событий.
Timer timer = Timer.builder("db.query")
.description("Database query latency")
.publishPercentiles(0.5, 0.95, 0.99)
.register(registry);
timer.record(() -> {
// Ваш код, время которого измеряется
Thread.sleep(100);
});
// Или через Sample
Timer.Sample sample = Timer.start(registry);
Thread.sleep(50);
sample.stop(timer);
4.3. Gauge (Датчик)
Сигнатура: Gauge.builder(String name, Object obj, ToDoubleFunction<? super Object> f)
Что делает: Отображает текущее значение некоторой величины (может как увеличиваться, так и уменьшаться). Типичные примеры: размер очереди, количество потоков, заполненность пула соединений.
Queue<String> queue = new LinkedList<>();
Gauge gauge = Gauge.builder("queue.size", queue, Queue::size)
.description("Current queue size")
.register(registry);
queue.add("item1");
System.out.println(registry.scrape()); // queue.size = 1.0
4.4. DistributionSummary (Сводка распределения)
Сигнатура: DistributionSummary.builder(String name) -> DistributionSummary.Builder
Что делает: Агрегирует распределение значений (не временных, а произвольных). Например, размеры HTTP-ответов, баллы пользователей, суммы транзакций. Отличается от Timer тем, что не измеряет время.
DistributionSummary summary = DistributionSummary.builder("response.size")
.description("Response payload size in bytes")
.baseUnit("bytes")
.publishPercentiles(0.5, 0.95)
.register(registry);
summary.record(1024); // 1 KB
summary.record(2048); // 2 KB
summary.record(512); // 0.5 KB
4.5. MeterRegistry (Реестр метрик)
Сигнатура: MeterRegistry (абстрактный класс)
Что делает: Центральный класс, управляющий всеми метриками. Через него создаются Counter, Timer, Gauge и другие. Конкретные реализации: PrometheusMeterRegistry, GraphiteMeterRegistry, SimpleMeterRegistry (для тестов).
// Создание реестра для Prometheus
PrometheusMeterRegistry promRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
// Для тестов — SimpleMeterRegistry
SimpleMeterRegistry simpleRegistry = new SimpleMeterRegistry();
Counter c = simpleRegistry.counter("test.counter");
c.increment();
System.out.println(simpleRegistry.getMetersAsString());
4.6. Tags (Теги/Метки)
Сигнатура: Tag.of(String key, String value) или Tags.of(String key1, String value1, String key2, String value2, ...)
Что делает: Позволяют добавлять размерности к метрикам. Например, разбивать HTTP-запросы по method и status. Теги — основа многомерного мониторинга.
Counter counter = Counter.builder("http.requests")
.tag("method", "POST") // Один тег
.tags("status", "200", "env", "prod") // Несколько тегов через Tags
.register(registry);
// Альтернативный способ
Counter counter2 = registry.counter("http.requests",
Tags.of("method", "GET", "status", "404"));
4.7. MeterFilter (Фильтр метрик)
Сигнатура: MeterFilter (интерфейс)
Что делает: Позволяет модифицировать метрики на лету: переименовывать, добавлять/удалять теги, изменять значения. Полезно для стандартизации или скрытия чувствительных данных.
registry.config()
.meterFilter(MeterFilter.renameTag("http.requests", "method", "http_method"))
.meterFilter(MeterFilter.ignoreTags("password"))
.meterFilter(new MeterFilter() {
@Override
public Meter.Id map(Meter.Id id) {
return id.withName("app." + id.getName());
}
});
4.8. Observation (Наблюдение) — API высокого уровня
Сигнатура: Observation.createNotStarted(String name, ObservationRegistry registry)
Что делает: Объединяет метрики, трейсинг и логирование в единый контекст. Позволяет создавать комплексные наблюдения с автоматическим созданием Counter, Timer и тегов.
ObservationRegistry obsRegistry = ObservationRegistry.create();
Observation observation = Observation.createNotStarted("http.server.requests", obsRegistry)
.lowCardinalityKeyValue("method", "GET")
.highCardinalityKeyValue("traceId", "abc123");
observation.start();
try {
// Обработка запроса
Thread.sleep(50);
} catch (Exception e) {
observation.error(e);
throw e;
} finally {
observation.stop();
}
5. Реальный практический пример (100-200 строк кода)
Создадим полноценное приложение — симулятор REST-сервиса с мониторингом через Prometheus. Приложение будет обрабатывать "запросы" и собирать метрики: количество запросов, время обработки, размер ответа, количество ошибок.
import io.micrometer.core.instrument.*;
import io.micrometer.prometheus.PrometheusConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;
public class MetricsDemoApp {
private final PrometheusMeterRegistry registry;
private final Counter requestCounter;
private final Counter errorCounter;
private final Timer requestTimer;
private final DistributionSummary responseSizeSummary;
private final AtomicInteger activeConnections = new AtomicInteger(0);
private final Random random = new Random();
public MetricsDemoApp() {
// 1. Создаём реестр
registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
// 2. Счётчики
requestCounter = Counter.builder("app.requests.total")
.description("Total number of requests")
.register(registry);
errorCounter = Counter.builder("app.errors.total")
.description("Total number of errors")
.register(registry);
// 3. Таймер с перцентилями
requestTimer = Timer.builder("app.request.duration")
.description("Request processing time")
.publishPercentiles(0.5, 0.95, 0.99)
.sla(Duration.ofMillis(50), Duration.ofMillis(100), Duration.ofMillis(200))
.register(registry);
// 4. Распределение размера ответа
responseSizeSummary = DistributionSummary.builder("app.response.size")
.description("Response size in bytes")
.baseUnit("bytes")
.publishPercentiles(0.5, 0.95)
.register(registry);
// 5. Gauge для активных соединений
Gauge.builder("app.active.connections", activeConnections, AtomicInteger::get)
.description("Current active connections")
.register(registry);
}
public String handleRequest(String endpoint) {
// Увеличиваем счётчик активных соединений
activeConnections.incrementAndGet();
// Начинаем измерение времени
Timer.Sample sample = Timer.start(registry);
try {
// Симулируем обработку запроса
Thread.sleep(random.nextInt(150) + 10); // 10-160 мс
// Симулируем ошибку с вероятностью 10%
if (random.nextDouble() < 0.1) {
throw new RuntimeException("Simulated error");
}
// Формируем "ответ"
String response = "{\"endpoint\":\"" + endpoint + "\",\"result\":\"ok\"}";
byte[] responseBytes = response.getBytes();
// Записываем размер ответа
responseSizeSummary.record(responseBytes.length);
// Увеличиваем счётчик запросов с тегом endpoint
requestCounter.increment();
return response;
} catch (Exception e) {
// Увеличиваем счётчик ошибок
errorCounter.increment();
return "{\"error\":\"" + e.getMessage() + "\"}";
} finally {
// Останавливаем таймер
sample.stop(requestTimer);
// Уменьшаем счётчик активных соединений
activeConnections.decrementAndGet();
}
}
public void simulateTraffic(int requestsCount) throws InterruptedException {
String[] endpoints = {"/api/users", "/api/orders", "/api/products"};
for (int i = 0; i < requestsCount; i++) {
String endpoint = endpoints[random.nextInt(endpoints.length)];
handleRequest(endpoint);
Thread.sleep(random.nextInt(200)); // Пауза между запросами
}
}
public String getPrometheusMetrics() {
return registry.scrape();
}
public static void main(String[] args) throws InterruptedException {
MetricsDemoApp app = new MetricsDemoApp();
System.out.println("=== Starting traffic simulation ===");
app.simulateTraffic(50);
System.out.println("=== Prometheus Metrics ===");
System.out.println