検証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構成を作るため以下のように依存を定義する。
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である。
@Slf4j
@Repository
public class EchoRepository {
@Cacheable("echo")
public String echo(String value) {
log.info("no cache: {}", value);
return value;
}
}
また、この処理を呼び出すRestControllerを実装
@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クラスにつけておく
@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.spec
にrecordStats
の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されるような実装にすれば可能(だがおすすめはあまりできない...)
@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
@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.yml
にspring.cache.cache-names
を書かなくても@Cacheable
がついたcacheのmetricsが動的にregisterされるようになる