Micrometer для Java: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

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                    

Рекомендаций