Я использую датчик 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 для таких случаев? Мне бы очень хотелось знать «стандартный» и элегантный способ сделать это.
@АнишБ. Да, это приложение для весенней загрузки.
Я бы сказал первый вариант, поскольку у Micrometer есть карта всех метров. Метод Register либо создаст новый датчик, либо вернет существующий, если комбинация имени и тегов уже использовалась. (более подробную информацию см. в документе JavaDoc о методе регистрации). Вместо непосредственного использования построителя датчиков вы можете использовать метод датчика в реестре. Пример:meterRegistry.gauge("withdraw.cycle", Tags.of("currency",currency ), минуты);
Вы правы насчет того, что микрометр не хранит ссылку на датчик, поэтому ваш второй подход выглядит правильным и соответствует тому, что упомянуто в документации по микрометру :
// 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;
});
}
А как насчет первого? Хранить ссылку в собственном коде — не самый чистый способ. Мне понадобится несколько карт, если у меня будет больше показателей.
@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"
@JFreebird, и причина в том, что register
не будет создавать новый датчик, если он уже существует с такой комбинацией тегов (и других свойств). И он не будет переназначать поставщика для существующей задолженности.
Я думаю, что второй пример Аниша Б. кажется проще. Это не требует вычислений карты. Датчик может напрямую получать значения с карты, и я могу просто использовать классы Integer/Long и т. д. в качестве записей карты вместо атомарных классов.
Пожалуйста, следите за этим выпуском на GitHub здесь.
Обобщить:
По определению, 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
перезаписан.
Второй подход, кажется, работает хорошо и довольно элегантно.
Многомерный способ @JFreebird тоже должен был сработать. Позвольте мне это проверить.
@JFreebird Я обновил свой ответ и исправил часть MultiGauge. Кроме того, обновите пример, добавив в него несколько вариантов использования. Пожалуйста, подтвердите сейчас. Все работает. :)
@JFreebird Пожалуйста, примите ответ, поскольку теперь все работает.
@JFreebird Я думаю, что подход MultiGauge гораздо чище, чем обычный. Но вы можете выбрать по своему желанию.
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 и т. д. в качестве записей карты вместо атомарных классов.
Использует ли ваше приложение Spring-Boot?