У меня есть компонент Spring, который объявляет некоторые кеши с помощью файла ehcache.xml
со следующей конфигурацией:
<?xml version = "1.0" encoding = "UTF-8"?>
<eh:config
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:eh = "http://www.ehcache.org/v3"
xsi:schemaLocation = "http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.3.xsd">
<eh:cache alias = "Cache1">
<eh:expiry>
<eh:ttl unit = "minutes">1</eh:ttl>
</eh:expiry>
<eh:resources>
<eh:heap>10000</eh:heap>
</eh:resources>
</eh:cache>
<!-- [...] -->
</eh:config>
которые затем используются аналогично следующему:
import org.springframework.cache.annotation.Cacheable;
public class ThingDoer {
@Cacheable("Cache1")
public Integer doSomethingCached(int value) {
return value + 3;
}
// ...
}
При этом я вижу, что в Prometheus появляются метрики различных кешей:
bash> 2>&1 curl -v --silent 'http://localhost:9000/actuator/prometheus' | grep cache_gets | sort
cache_gets_total{cache = "Cache1",cache_manager = "cacheManager",hostname = "localhost",name = "Cache1",result = "hit",} 0.0
cache_gets_total{cache = "Cache1",cache_manager = "cacheManager",hostname = "localhost",name = "Cache1",result = "miss",} 0.0
cache_gets_total{cache = "Cache2",cache_manager = "cacheManager",hostname = "localhost",name = "Cache2",result = "hit",} 0.0
cache_gets_total{cache = "Cache2",cache_manager = "cacheManager",hostname = "localhost",name = "Cache2",result = "miss",} 0.0
cache_gets_total{cache = "Cache3",cache_manager = "cacheManager",hostname = "localhost",name = "Cache3",result = "hit",} 0.0
cache_gets_total{cache = "Cache3",cache_manager = "cacheManager",hostname = "localhost",name = "Cache3",result = "miss",} 0.0
[...]
Все кеши, которые я определил в своем файле ehcache.xml
, автоматически раскрывают свои метрики.
Теперь я создал самодельный кеш, который выполняет сложные операции при запросе, и использовал для этого интерфейс com.google.common.cache.LoadingCache
, поскольку он предназначен для замены другого экземпляра такого кеша с как можно меньшим количеством изменений в остальной части кода. Поэтому я создал собственный кеш со следующей сигнатурой:
import com.google.common.cache.AbstractLoadingCache;
public class CustomCache extends AbstractLoadingCache<String, Object> {
public CustomCache(Function<String, Object> valueLoader) {
// ...
}
// ...
}
Он правильно объявляет метод stats()
, который возвращает com.google.common.cache.CacheStats
, содержащий статистику кеша. Параметр valueLoader
его конструктора используется для загрузки нового значения, когда оно отсутствует в кеше.
Теперь вот набросок того, как используется этот кеш:
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
public class CustomThingDoer {
private final CustomCache customCache;
public CustomThingDoer() {
this.customCache = new CustomCache(this::loadNewObject);
}
public Optional<Object> doSomethingCached(String value) {
try {
return Optional.of(customCache.get(value));
} catch (ExecutionException e) {
System.out.println("Something wrong happened");
return Optional.empty();
}
}
private Object loadNewObject(String value) {
// Do something non-static
}
}
Поскольку кеш предназначен для использования в местах, где его методы вызываются напрямую (т. е. нет аннотации @Cacheable
для использования этого кеша, используются вызовы get
), и поскольку метод загрузки отсутствующих значений зависит от экземпляра класс, в котором он определен (т. е. рассматриваемый метод представляет собой выборку в базе данных, код которой зависит от вещей, определенных в экземпляре CustomThingDoer
), я хочу зарегистрировать его вручную в MeterRegistry
.
Для этого я передаю компонент MeterRegistry
моему CustomThingDoer
в конструкторе и выполняю следующее:
GuavaCacheMetrics.monitor(meterRegistry, this.customCache, "MyCustomCache");
Но теперь, когда это сделано, вот какие метрики кэша я получаю в Prometheus:
bash> 2>&1 curl -v --silent 'http://localhost:9000/actuator/prometheus' | grep cache_gets | sort
cache_gets_total{cache = "MyCustomCache",hostname = "localhost",result = "hit",} 0.0
cache_gets_total{cache = "MyCustomCache",hostname = "localhost",result = "miss",} 0.0
Теперь отображаются только метрики моего пользовательского кэша. Более того, поле cache_manager
отсутствует.
Я подозреваю, что, поскольку я регистрирую свой кеш вручную, какой-то предикат нарушается с помощью automagic, и он никогда не добавляет автоматически сгенерированные кеши EhCache в MeterRegistry
.
Я хочу иметь как метрики своего пользовательского кэша, так и метрики неявно созданных кешей EhCache. Если возможно, мне бы не хотелось менять используемый интерфейс (т. е. `com.google.common.cache.LoadingCache), чтобы внести минимальное количество изменений в код (и, честно говоря, в модульные тесты). Кто-нибудь знает, в чем может быть проблема или какое у меня может быть решение?
Похоже, это связано с давней ошибкой в Micrometer: https://github.com/micrometer-metrics/micrometer/issues/877.
Это было исправлено несколько лет назад, но оно до сих пор имеет побочные эффекты, среди которых необходимость иметь один и тот же набор ключей тегов для каждого имени метрики, иначе некоторые из них не учитываются. https://github.com/micrometer-metrics/micrometer/issues/877#issuecomment-944894069