LoginSignup
5
4

More than 5 years have passed since last update.

Spring Bootでcacheにcaffeineを利用した際のmetricsをprometheusで取得する

Last updated at Posted at 2018-10-31

検証version

spring boot 2.1.0.RELEASE

Spring BootでCacheを利用する

まずCacheを利用するための設定をする。やることは

  • spring-boot-starter-cache の依存を入れる
  • Cacheableなmethodを書く。これはControllerに置かない。別のComponentに置く。
  • ConfigurationクラスかApplicationクラスに@EnableCachingをつける

の3点

spring-boot-starter-cache の依存を入れる

gradleならbuild.gradleのdependenciesに指定。webのsimple構成を作るため以下のように依存を定義する。

build.gradle
dependencies {
    implementation('org.springframework.boot:spring-boot-starter-actuator')
    implementation('org.springframework.boot:spring-boot-starter-cache')
    implementation('org.springframework.boot:spring-boot-starter-web')
    compileOnly('org.projectlombok:lombok')
    testImplementation('org.springframework.boot:spring-boot-starter-test')
}

Cacheableなmethodを定義する

適当にRepository classを用意して以下のように実装し、一度echoしたvalueはcacheから値を取り出すようにする。@Cacheableのvalueに設定した文字列はcacheNameである。

EchoRepository
@Slf4j
@Repository
public class EchoRepository {

    @Cacheable("echo")
    public String echo(String value) {
        log.info("no cache: {}", value);
        return value;
    }
}

また、この処理を呼び出すRestControllerを実装

EchoController
@RequiredArgsConstructor
@RestController
public class EchoController {

    private final EchoRepository repository;

    @GetMapping("echo")
    public String echo(@RequestParam  String value) {
        return repository.echo(value);
    }
}

ConfigurationクラスかApplicationクラスに@EnableCachingをつける

とりあえずApplicationクラスにつけておく

DemoApplication
@EnableCaching
@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

テスト

2つのparameterで2回ずつ呼び出し

$ curl http://localhost:8080/echo?value=test
test$ 
$ curl http://localhost:8080/echo?value=test
test$ 
$ curl http://localhost:8080/echo?value=test1
test1$ 
$ curl http://localhost:8080/echo?value=test1
test1$ 

log を確認

2018-10-31 23:41:26.824  INFO 3663 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 18 ms
2018-10-31 23:41:26.914  INFO 3663 --- [nio-8080-exec-1] c.k.demo.repository.EchoRepository       : no cache: test
2018-10-31 23:41:34.690  INFO 3663 --- [nio-8080-exec-3] c.k.demo.repository.EchoRepository       : no cache: test1

cacheされているようだ

Caffeineを使う

cache自体の実装は選択が可能である。デフォルトだとConcurrentHashMapを使ったSimpleな実装が選択されるようだが、ここではCaffeineを使う。

利用するには、dependenciesに
implementation('com.github.ben-manes.caffeine:caffeine')
を追加すればよい

Caffeineのmetricsを収集する

micrometerの実装に、Caffeineのmetricsを収集する実装がされている。これを利用したい。

Spring Boot actuatorでprometheusのendpointを提供する

まず、dependenciesに
implementation('io.micrometer:micrometer-registry-prometheus')
を追加する。次に、application.yml
management.endpoints.web.exposure.include: prometheus
を記載して、prometheusのendpointが叩けるようにし、試しにcurlでrequestしてみるとdefaultのmetricsやAutoConfigurationでregisterされるmetricsが取得できるようになる。
なお、actuatorのbase pathはmanagement.endpoints.web.base-pathで定義されていて、デフォルトが/actuatorになっている

$ curl http://localhost:8080/actuator/prometheus 2>/dev/null | head
# HELP tomcat_global_request_seconds  
# TYPE tomcat_global_request_seconds summary
tomcat_global_request_seconds_count{name="http-nio-8080",} 4.0
tomcat_global_request_seconds_sum{name="http-nio-8080",} 0.597
# HELP tomcat_threads_config_max_threads  
# TYPE tomcat_threads_config_max_threads gauge
tomcat_threads_config_max_threads{name="http-nio-8080",} 200.0
# HELP tomcat_sessions_active_current_sessions  
# TYPE tomcat_sessions_active_current_sessions gauge
tomcat_sessions_active_current_sessions 0.0

Caffeineのmetricsを収集するための設定追加

spring.cache.caffeine.specrecordStatsのparameterを入れる
例えば spring.cache.caffeine.spec: maximumSize=500,expireAfterAccess=600s,recordStats をapplication.ymlに定義
metrics収集の面ではrecordStatsだけあれば良いが、その他の設定可能な値は CaffeineSpec.java あたりを参照

これで取得され始めると思ったが...

先程実装した/echoを叩いた後に、prometheusのendpointを呼ぶ

$ curl http://localhost:8080/echo?value=test
test$ 
$ curl http://localhost:8080/actuator/prometheus 2>/dev/null | grep cache
# HELP tomcat_cache_access_total  
# TYPE tomcat_cache_access_total counter
tomcat_cache_access_total 0.0
# HELP tomcat_cache_hit_total  
# TYPE tomcat_cache_hit_total counter
tomcat_cache_hit_total 0.0

...

spring.cache.cache-namesの設定

どうもCacheMetricsRegistrarあたりの実装を見る感じだと、先に静的に利用しているcacheのcache nameをapplication.ymlに書いておかないとMicrometerにregisterされないようになっているようだ
なので、cache関連の設定は、

spring.cache:
  cache-names: echo
  caffeine.spec: maximumSize=500,expireAfterAccess=600s,recordStats

のようにする。再度確認すると取得できた

]$ curl http://localhost:8080/actuator/prometheus 2>/dev/null | grep cache
# HELP cache_eviction_weight The sum of weights of evicted entries. This total does not include manual invalidations.
# TYPE cache_eviction_weight gauge
cache_eviction_weight{cache="echo",cacheManager="cacheManager",name="echo",} 0.0
# HELP cache_gets_total the number of times cache lookup methods have returned an uncached (newly loaded) value, or null
# TYPE cache_gets_total counter
cache_gets_total{cache="echo",cacheManager="cacheManager",name="echo",result="miss",} 1.0
cache_gets_total{cache="echo",cacheManager="cacheManager",name="echo",result="hit",} 0.0
# HELP tomcat_cache_hit_total  
# TYPE tomcat_cache_hit_total counter
tomcat_cache_hit_total 0.0
# HELP cache_size The number of entries in this cache. This may be an approximation, depending on the type of cache.
# TYPE cache_size gauge
cache_size{cache="echo",cacheManager="cacheManager",name="echo",} 1.0
# HELP cache_evictions_total cache evictions
# TYPE cache_evictions_total counter
cache_evictions_total{cache="echo",cacheManager="cacheManager",name="echo",} 0.0
# HELP cache_puts_total The number of entries added to the cache
# TYPE cache_puts_total counter
cache_puts_total{cache="echo",cacheManager="cacheManager",name="echo",} 0.0
# HELP tomcat_cache_access_total  
# TYPE tomcat_cache_access_total counter
tomcat_cache_access_total 0.0

@Cachableで指定したcacheのmetricsを動的にregisterしたい

spring.cache.cache-names@Cachableで指定したcache名をつらつらと書いていくのは、数が増えると漏れが発生しそうだし面倒に感じる。現状このような実装をしたい場合の解として、力技だがCaffeineCacheManagerの派生クラスを作りCaffeineCacheがcreateされるときにMeterRegistryにregisterされるような実装にすれば可能(だがおすすめはあまりできない...)

InstrumentedCaffeineCacheManager
@RequiredArgsConstructor
public class InstrumentedCaffeineCacheManager extends CaffeineCacheManager {

    private final MeterRegistry meterRegistry;

    @Override
    protected Cache<Object, Object> createNativeCaffeineCache(String name) {
        Cache<Object, Object> nativeCache = super.createNativeCaffeineCache(name);
        CaffeineCacheMetrics.monitor(meterRegistry, nativeCache, name, Collections.emptyList());
        return nativeCache;
    }
}

これをBeanとして登録。ただし、CaffeineCacheConfigurationでは単にBean生成する他に処理があるのでcopy

CacheConfig
@EnableConfigurationProperties(CacheProperties.class)
@EnableCaching // Application classからこちらに移動した
@Configuration
@RequiredArgsConstructor
public class CacheConfig {

    private final MeterRegistry meterRegistry;

    private final CacheProperties cacheProperties;

    // CaffeineCacheConfigurationの実装をcopy
    @Bean
    public CaffeineCacheManager cacheManager() {
        CaffeineCacheManager cacheManager = createCacheManager();
        List<String> cacheNames = cacheProperties.getCacheNames();
        if (!CollectionUtils.isEmpty(cacheNames)) {
            cacheManager.setCacheNames(cacheNames);
        }
        return cacheManager;
    }
    // ここで独自CaffeineCacheManagerを生成
    private CaffeineCacheManager createCacheManager() {
        CaffeineCacheManager cacheManager = new InstrumentedCaffeineCacheManager(meterRegistry);
        setCacheBuilder(cacheManager);
        return cacheManager;
    }

    private void setCacheBuilder(CaffeineCacheManager cacheManager) {
        String specification = cacheProperties.getCaffeine().getSpec();
        if (StringUtils.hasText(specification)) {
            cacheManager.setCacheSpecification(specification);
        }
    }
}

これで、application.ymlspring.cache.cache-namesを書かなくても@Cacheableがついたcacheのmetricsが動的にregisterされるようになる

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4