跳到主要内容

Micrometer 缓存监控

DeepSeek V3 中英对照 Cache Micrometer Cache Instrumentations

Micrometer 支持将指标绑定到多种流行的缓存库。每个实现都支持基本功能,例如缓存命中与未命中,从中可以推导出缓存命中率在一段时间内的基本信息。Micrometer 使用一个函数跟踪计数器来监控诸如命中和未命中之类的事情,不仅让你了解缓存整个生命周期内的命中和未命中(例如,Guava 的 CacheStats 暴露的基本指标),还让你了解给定时间间隔内的命中和未命中。

示例

为了展示缓存监控的功能,我们从一个简单的程序开始,该程序使用 reactor-netty 读取玛丽·雪莱的《弗兰肯斯坦》的全部内容,并将每个单词放入缓存中(如果该单词尚未被缓存):

import reactor.netty.http.client.HttpClient;
import reactor.core.publisher.Flux;
import io.github.resilience4j.cache.Cache;

public class CacheMonitoringExample {
public static void main(String[] args) {
HttpClient client = HttpClient.create();
Cache<String, String> cache = Cache.of("wordCache");

client.get()
.uri("http://www.gutenberg.org/files/84/84-0.txt")
.responseContent()
.asString()
.flatMapMany(content -> Flux.fromArray(content.split("\\s+")))
.filter(word -> !cache.getIfPresent(word).isPresent())
.doOnNext(word -> cache.put(word, word))
.blockLast();
}
}
java

在这个示例中,我们使用 reactor-netty 从古腾堡项目下载《弗兰肯斯坦》的文本内容,并将其拆分为单词流。然后,我们使用 resilience4j 的缓存功能来存储每个单词,如果该单词尚未在缓存中。

// read all of Frankenstein
HttpClient.create("www.gutenberg.org")
.get("/cache/epub/84/pg84.txt")
.flatMapMany(res -> res.addHandler(wordDecoder()).receive().asString())
.delayElements(Duration.ofMillis(10)) // one word per 10 ms
.filter(word -> !word.isEmpty())
.doOnNext(word -> {
if (cache.getIfPresent(word) == null)
cache.put(word, 1);
})
.blockLast();
java

下图展示了一个缓存中的命中与未命中情况,该缓存已调优为最多可容纳 10,000 个条目:

命中与未命中

图 1. 命中与未命中,在 Prometheus 中查看

rate(book_guava_requests_total[10s])
none

通过将命中次数除以所有 get 操作的总和(无论每次操作是命中还是未命中),我们可以得出在仅使用 10,000 个单词读取 Frankenstein 时的命中率上限概念:

命中率

图 2. 命中率,由 Prometheus 查看

sum(rate(book_guava_requests_total{result="hit"}[1m])) / sum(rate(book_guava_requests_total[1m]))
none

在实际场景中,我们根据存储和加载效率之间的权衡来调整缓存。你可以基于一些上限值来创建警报,例如当缓存未命中率超过某个上限值,或者缓存命中率低于某个下限值时。设置未命中率的上限比设置命中率的下限更好。对于这两种比率,如果没有任何活动,值都会降至 0。下图显示了未命中率超过 10% 的情况:

未命中率(已警告)

图 3. 当未命中率超过 10% 时发出警报

缓存实现

Micrometer 可以监控各种缓存实现:

Caffeine

// Setting up instrumentation
LoadingCache<String, String> cache = Caffeine.newBuilder().recordStats().build(key -> "");

CaffeineCacheMetrics<String, String, Cache<String, String>> metrics = new CaffeineCacheMetrics<>(cache, "testCache",
expectedTag);

// Binding manually
MeterRegistry registry = new SimpleMeterRegistry();
metrics.bindTo(registry);

// Binding through a static method
MeterRegistry meterRegistry = new SimpleMeterRegistry();
CaffeineCacheMetrics.monitor(meterRegistry, cache, "testCache", expectedTag);
java

Ehcache 2

// Setting up instrumentation
static Cache cache;

EhCache2Metrics metrics = new EhCache2Metrics(cache, expectedTag);

// Binding manually
MeterRegistry registry = new SimpleMeterRegistry();
metrics.bindTo(registry);

// Binding through a static method
MeterRegistry meterRegistry = new SimpleMeterRegistry();
EhCache2Metrics.monitor(meterRegistry, cache, expectedTag);
java

Guava

// Setting up instrumentation
LoadingCache<String, String> cache = CacheBuilder.newBuilder().recordStats().build(new CacheLoader<>() {
public String load(String key) {
return "";
}
});

GuavaCacheMetrics<String, String, Cache<String, String>> metrics = new GuavaCacheMetrics<>(cache, "testCache",
expectedTag);

// Binding manually
MeterRegistry registry = new SimpleMeterRegistry();
metrics.bindTo(registry);

// Binding through a static method
MeterRegistry meterRegistry = new SimpleMeterRegistry();
GuavaCacheMetrics.monitor(meterRegistry, cache, "testCache", expectedTag);
java

Hazelcast

// Setting up instrumentation
static IMap<String, String> cache;

HazelcastCacheMetrics metrics = new HazelcastCacheMetrics(cache, expectedTag);

// Binding manually
MeterRegistry meterRegistry = new SimpleMeterRegistry();
metrics.bindTo(meterRegistry);

// Binding through a static method
MeterRegistry meterRegistry = new SimpleMeterRegistry();
HazelcastCacheMetrics.monitor(meterRegistry, cache, expectedTag);
java

JCache

// Setting up instrumentation
Cache<String, String> cache;

JCacheMetrics<String, String, Cache<String, String>> metrics;

metrics = new JCacheMetrics<>(cache, expectedTag);

// Binding manually
MeterRegistry meterRegistry = new SimpleMeterRegistry();
metrics.bindTo(meterRegistry);

// Binding through a static method
MeterRegistry meterRegistry = new SimpleMeterRegistry();
JCacheMetrics.monitor(meterRegistry, cache, expectedTag);
java