Skip to content

Instantly share code, notes, and snippets.

@cherniag
Created December 2, 2020 14:34
Show Gist options
  • Save cherniag/8d0a779e822780cac05b877a6b138fba to your computer and use it in GitHub Desktop.
Save cherniag/8d0a779e822780cac05b877a6b138fba to your computer and use it in GitHub Desktop.
loading cache with async refresh
package com.test.service.configuration;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.stats.CacheStats;
import com.github.benmanes.caffeine.cache.stats.StatsCounter;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.index.qual.NonNegative;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.test.integration.http.Data;
import com.test.integration.http.HttpClient;
@Slf4j
@Configuration
public class CacheContext {
@Bean
public Executor cacheReloadExecutor() {
return Executors.newSingleThreadExecutor(new ThreadFactoryBuilder()
.setNameFormat("cache-refresh-%d")
.setDaemon(true)
.build());
}
@Bean
public LoadingCache<Object, Data> cacheLoader(Executor cacheReloadExecutor,
StatsCounter statsCounter,
HttpClient client) {
return Caffeine.newBuilder()
.maximumSize(10_000)
.refreshAfterWrite(30, TimeUnit.SECONDS)
.expireAfterWrite(10, TimeUnit.MINUTES)
.removalListener((k, v, c) -> log.debug("Remove value form cache because of {}", c))
.executor(cacheReloadExecutor)
.recordStats(() -> statsCounter)
.build(key -> client.getData());
}
// @TODO: move to dedicated class
@Bean
public StatsCounter statsCounter(MeterRegistry meterRegistry) {
String cacheName = "cache.data";
return new StatsCounter() {
private static final String COUNTER = "counter.";
private static final String TIMER = "timer.";
private final Counter hits = Counter.builder(COUNTER + cacheName + ".hits").register(meterRegistry);
private final Counter misses = Counter.builder(COUNTER + cacheName + ".misses").register(meterRegistry);
private final Counter reloads = Counter.builder(COUNTER + cacheName + ".reloads").register(meterRegistry);
private final Counter reloadFails = Counter.builder(COUNTER + cacheName + ".reloads.failed").register(meterRegistry);
private final Timer loadTimer = Timer.builder(TIMER + cacheName + ".reloads").register(meterRegistry);
private final Timer loadFailedTimer = Timer.builder(TIMER + cacheName + ".reloads.failed").register(meterRegistry);
private final Counter evictions = Counter.builder(COUNTER + cacheName + ".evictions").register(meterRegistry);
@Override
public void recordHits(@NonNegative int count) {
hits.increment(count);
}
@Override
public void recordMisses(@NonNegative int count) {
misses.increment(count);
}
@Override
public void recordLoadSuccess(@NonNegative long loadTime) {
reloads.increment(loadTime);
loadTimer.record(loadTime, TimeUnit.NANOSECONDS);
}
@Override
public void recordLoadFailure(@NonNegative long loadTime) {
reloadFails.increment(loadTime);
loadFailedTimer.record(loadTime, TimeUnit.NANOSECONDS);
}
@Override
public void recordEviction() {
evictions.increment();
}
@Override
public CacheStats snapshot() {
return new CacheStats(
(long) hits.count(),
(long) misses.count(),
(long) reloads.count(),
(long) reloadFails.count(),
loadTimer.count(),
(long) evictions.count(),
0L);
}
@Override
public String toString() {
return snapshot().toString();
}
};
}
// inject this bean and use:
//
// @Autowired
// private Supplier<Data> dataSupplier;
@Bean
public Supplier<Data> dataSupplier(LoadingCache<Object, Data> loadingCache) {
Object dummyKey = new Object();
return () -> loadingCache.get(dummyKey);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment