Обновление Java-микрометра с динамическими метками

Я использую датчик Java Micrometer для измерения показателя, имеющего ряд меток. Значения меток извлекаются динамически, но ограничены.

Например: у меня есть метрика withdraw.cycle с меткой currency.

Поскольку Micrometer не содержит ссылки на сам датчик, код выглядит довольно громоздким.

У меня есть два способа сделать это:

private void updateGauge(String currency, int minutes) {
    Tags tags = Tags.of("currency", currency);
    Gauge.builder("withdraw.cycle",() -> minutes)
         .tags(tags)
         .strongReference(true)
         .register(meterRegistry);
}

ИЛИ

private final Map<Tags, AtomicLong> cycleGaugeMap = new ConcurrentHashMap<>();

// Use cycleGaugeMap to hold the reference to the gauge
private void updateGauge(String currency, int minutes) {
    Tags tags = Tags.of("currency", currency);
    if (cycleGaugeMap.containsKey(tags)) {
        cycleGaugeMap.get(tags).set(minutes);
    } else {
        AtomicLong old = cycleGaugeMap.putIfAbsent(tags, meterRegistry.gauge("withdraw.cycle", tags,
                                                                                     new AtomicLong(minutes)));
        if (old != null) {
            old.set(minutes);
        }
    }
 }

Вопрос: Правильные ли это способы сделать это или у Micrometer есть неизвестный мне API для таких случаев? Мне бы очень хотелось знать «стандартный» и элегантный способ сделать это.

Использует ли ваше приложение Spring-Boot?

Anish B. 18.09.2023 08:48

@АнишБ. Да, это приложение для весенней загрузки.

J Freebird 18.09.2023 12:14

Я бы сказал первый вариант, поскольку у Micrometer есть карта всех метров. Метод Register либо создаст новый датчик, либо вернет существующий, если комбинация имени и тегов уже использовалась. (более подробную информацию см. в документе JavaDoc о методе регистрации). Вместо непосредственного использования построителя датчиков вы можете использовать метод датчика в реестре. Пример:meterRegistry.gauge("withdraw.cycle", Tags.of("currency",currency ), минуты);

Hans-Christian 19.09.2023 13:11
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
4
3
167
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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

// maintain a reference to myGauge
AtomicInteger myGauge = registry.gauge("numberGauge", new AtomicInteger(0));

// ... elsewhere you can update the value it holds using the object reference
myGauge.set(27);
myGauge.set(11);

Однако я бы немного изменил этот код, поскольку в нем возникли условия гонки:

  private final ConcurrentMap<String, AtomicLong> cycleGaugeMap = new ConcurrentHashMap<>();
  // Use cycleGaugeMap to hold the reference to the gauge
  private void updateGauge(String currency, int minutes) {
    cycleGaugeMap.compute(currency, (k, v) -> {
      if (v == null) {
        v = meterRegistry.gauge("withdraw.cycle", Tags.of("currency", currency), new AtomicLong(minutes));
      } else {
        v.set(minutes);
      }
      return v;
    });
  }

А как насчет первого? Хранить ссылку в собственном коде — не самый чистый способ. Мне понадобится несколько карт, если у меня будет больше показателей.

J Freebird 19.09.2023 14:16

@JFreebird это не сработает. Вы можете проверить это, используя следующий пример кода: ``` SimpleMeterRegistrymeterRegistry = new SimpleMeterRegistry(); Теги tags = Tags.of("валюта", "а"); Gauge.builder("withdraw.cycle",() -> 10) .tags(tags) .strongReference(true) .register(meterRegistry); System.out.println(meterRegistry.getMetersAsString()); Gauge.builder("withdraw.cycle",() -> 20) .tags(tags) .strongReference(true) .register(meterRegistry); System.out.println(meterRegistry.getMetersAsString()); ``` Будет напечатано "...10.0"

silh 19.09.2023 14:36

@JFreebird, и причина в том, что register не будет создавать новый датчик, если он уже существует с такой комбинацией тегов (и других свойств). И он не будет переназначать поставщика для существующей задолженности.

silh 19.09.2023 14:39

Я думаю, что второй пример Аниша Б. кажется проще. Это не требует вычислений карты. Датчик может напрямую получать значения с карты, и я могу просто использовать классы Integer/Long и т. д. в качестве записей карты вместо атомарных классов.

J Freebird 20.09.2023 19:02
Ответ принят как подходящий

Пожалуйста, следите за этим выпуском на GitHub здесь.

Обобщить:

  • Первый способ обновления датчика — через MultiGauge:

По определению, Micrometer поддерживает один специальный тип калибра, называемый MultiGauge, чтобы помочь контролировать растущий или сокращающийся список критерии. Эта функция позволяет выбрать набор четко ограниченных, но немного измененный набор критериев из чего-то вроде SQL-запроса и сообщить некоторую метрику для каждой строки в качестве датчика.

Рабочий пример:

public class Example {

    private static final SimpleMeterRegistry registry = new SimpleMeterRegistry();
    private static final MultiGauge gauge = MultiGauge.builder("withdraw.cycle").register(registry);

    private static final Map<String, Number> values = new ConcurrentHashMap<>(); 

    private static void updateGauge(String currency, int minutes) {
        values.put(currency, minutes);
        List<MultiGauge.Row<?>> rows = values.entrySet()
            .stream()
            .map(row -> MultiGauge.Row.of(Tags.of("currency", row.getKey()), row.getValue()))
            .collect(Collectors.toList());
        gauge.register(rows, true);
    }

    public static void main(String[] args) throws InterruptedException {
       updateGauge("Rupee", 1);
       System.out.println(registry.getMetersAsString());
       System.out.println("--------");
       updateGauge("Dollar", 2);
       System.out.println(registry.getMetersAsString());
       System.out.println("--------");
       updateGauge("Rupee", 3);
       System.out.println(registry.getMetersAsString());
    }

Выход:

withdraw.cycle(GAUGE)[currency='Rupee']; value=1.0
--------
withdraw.cycle(GAUGE)[currency='Dollar']; value=2.0
withdraw.cycle(GAUGE)[currency='Rupee']; value=1.0
--------
withdraw.cycle(GAUGE)[currency='Dollar']; value=2.0
withdraw.cycle(GAUGE)[currency='Rupee']; value=3.0
  • Второй подход к обновлению датчика через сам Gauge.

Рабочий пример:

public class Example {

    private static final SimpleMeterRegistry registry = new SimpleMeterRegistry();

    private static final Map<String, Number> values = new ConcurrentHashMap<>();

    private static void updateGauge(String currency, int minutes) {
        Tags tags = Tags.of("currency", currency);
        values.put(currency, minutes);
        if (registry.find("withdraw.cycle").tags(tags).gauge() == null) {
            Gauge.builder("withdraw.cycle", () -> values.get(currency))
                    .tags(tags)
                    .register(registry);
        }
    }


    public static void main(String[] args) throws InterruptedException {
        updateGauge("Rupee", 1);
        System.out.println(registry.getMetersAsString());
        System.out.println("--------");
        updateGauge("Dollar", 2);
        System.out.println(registry.getMetersAsString());
        System.out.println("--------");
        updateGauge("Rupee", 3);
        System.out.println(registry.getMetersAsString());
    }
}

Выход:

withdraw.cycle(GAUGE)[currency='Rupee']; value=1.0
--------
withdraw.cycle(GAUGE)[currency='Dollar']; value=2.0
withdraw.cycle(GAUGE)[currency='Rupee']; value=1.0
--------
withdraw.cycle(GAUGE)[currency='Dollar']; value=2.0
withdraw.cycle(GAUGE)[currency='Rupee']; value=3.0

Я думаю, это оптимальный подход, который вы искали.

Я попробовал MultiGauge, но это не работает. Датчик с новыми тегами просто перезапишет предыдущие. Если вы перейдете со второго updateGauge на updateGauge("Dollar", 2);, останется только датчик с тегом Dollar. Rupee перезаписан.

J Freebird 19.09.2023 19:17

Второй подход, кажется, работает хорошо и довольно элегантно.

J Freebird 19.09.2023 19:39

Многомерный способ @JFreebird тоже должен был сработать. Позвольте мне это проверить.

Anish B. 19.09.2023 20:17

@JFreebird Я обновил свой ответ и исправил часть MultiGauge. Кроме того, обновите пример, добавив в него несколько вариантов использования. Пожалуйста, подтвердите сейчас. Все работает. :)

Anish B. 20.09.2023 03:52

@JFreebird Пожалуйста, примите ответ, поскольку теперь все работает.

Anish B. 20.09.2023 03:52

@JFreebird Я думаю, что подход MultiGauge гораздо чище, чем обычный. Но вы можете выбрать по своему желанию.

Anish B. 20.09.2023 03:58

Micrometer не предоставляет прямой API для обновления значения датчика с помощью динамически извлекаемых меток, поэтому ваши подходы являются разумными обходными путями. Однако ваш второй подход с использованием ConcurrentHashMap для хранения ссылок на датчики может быть улучшен для ясности и безопасности. Вот обновленная версия вашего второго подхода:

private final ConcurrentMap<Tags, AtomicLong> cycleGaugeMap = new ConcurrentHashMap<>();

private void updateGauge(String currency, int minutes) {
    Tags tags = Tags.of("currency", currency);
    AtomicLong gaugeValue = cycleGaugeMap.computeIfAbsent(tags, key -> {
        AtomicLong gauge = new AtomicLong(minutes);
        meterRegistry.gauge("withdraw.cycle", tags, gauge);
        return gauge;
    });

    gaugeValue.set(minutes);
}

В этой обновленной версии computeIfAbsent используется для атомарной проверки наличия датчика в cycleGaugeMap. Если он не существует, он создает датчик и регистрирует его в Micrometer, прежде чем вернуть ссылку на него. Это гарантирует, что создание и извлечение датчиков выполняется атомарно, и позволяет избежать необходимости отдельно проверять и помещатьIfAbsent.

Этот подход является более кратким и по-прежнему потокобезопасным. Он также ясно передает ваше намерение динамически создавать датчики и управлять ими, отслеживая их для будущих обновлений.

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

Спасибо за ответ. Но я думаю, что второй пример Аниша Б. кажется более чистым. Это не требует вычислений карты. Датчик может напрямую получать значение из карты, и я могу просто использовать Integer/Long и т. д. в качестве записей карты вместо атомарных классов.

J Freebird 20.09.2023 19:01

Другие вопросы по теме