業務でguavaのLoadingCacheの実装をしたので、そのときのメモ。
3行
- Javaでキャッシュ実装を簡単に行うことができる
-
LoadingCache<K,V>
というKey-Valueでデータを保持することができる - JVMでデータを保持しておくため、メモリの使いすぎには注意
サンプル実装
SpringBootでローカル環境にREST APIサーバを構築します。
$ curl http://localhost:8080/pokemon\?id\=150
{"id":150,"name":"mewtwo","weight":1220,"height":20}
上記でSpringBootにGETリクエストすると、SpringBootがpokeapiにアクセスして、指定されたidのポケモンデータを取得します。1度データを取りに行くと、SpringBootはそのデータをキャッシュとして保持します。
Controllerの実装
CacheBuilder
を使うことで、一度pokeapiにアクセスしたデータをキャッシュしておきます。キャッシュの保持期間やデータ数はexpireAfterAccess
やmaximumSize
で設定することができます。下記はmaximumSize=1
なので1つのデータしかキャッシュしません。
@RestController
class PokemonController() {
private val log = LoggerFactory.getLogger(PokemonController::class.java)
private val restTemplate = RestTemplate()
// キャッシュ設定
private val pokemonCache = CacheBuilder.newBuilder()
.maximumSize(1) // キャッシュするデータ数
.expireAfterAccess(10, TimeUnit.SECONDS) // キャッシュする期間
.build(object: CacheLoader<Int, Pokemon>() { // idがKeyでPokemonがValue
@Throws(Exception::class)
override fun load(id: Int): Pokemon {
return getPokemon(id) // このメソッドの戻り値をキャッシュ
}
})!!
@RequestMapping("/pokemon")
fun findPokemon(id: Int): Pokemon {
val stopwatch = Stopwatch.createStarted()
val pokemon = pokemonCache.get(id)
stopwatch.stop()
log.info("Elapsed time: $stopwatch")
return pokemon
}
fun getPokemon(@RequestParam id: Int): Pokemon {
return restTemplate.getForObject(
"https://pokeapi.co/api/v2/pokemon/$id", Pokemon::class.java)
}
}
実行結果
strestを使って、本当にキャッシュが効いているか確認します。
- id=151にアクセス(遅い。初回のアクセスのため)
- id=151にアクセス(速い。151はキャッシュされている)
- id=150にアクセス(遅い。150は初回アクセスのため)
- id=151にアクセス(遅い。キャッシュは1データだけなので、151のキャッシュは効いてないから遅い)
$ yarn strest test.yml
✔ Testing request_151_1st succeeded (0.195s)
✔ Testing request_151_2nd succeeded (0.007s)
✔ Testing request_150 succeeded (0.129s)
✔ Testing request_151_3rd succeeded (0.111s)
[ Strest ] ✨ Done in 0.462s
✨ Done in 2.24s.
Kotlinの方のログはこんな感じ。
2019-02-23 21:41:51.117 INFO 16733 --- [nio-8080-exec-2] c.e.j.demo.controller.PokemonController : Elapsed time: 162.0 ms
2019-02-23 21:41:51.138 INFO 16733 --- [nio-8080-exec-3] c.e.j.demo.controller.PokemonController : Elapsed time: 40.15 μs
2019-02-23 21:41:51.268 INFO 16733 --- [nio-8080-exec-4] c.e.j.demo.controller.PokemonController : Elapsed time: 122.7 ms
2019-02-23 21:41:51.379 INFO 16733 --- [nio-8080-exec-5] c.e.j.demo.controller.PokemonController : Elapsed time: 104.9 ms
キャッシュはJVMに保持される
なので調子に乗ってキャッシュ化しすぎると、メモリ容量を溢れる可能性があります。そういった場合はmaximumWeight
でキャッシュのデータサイズを制限できるみたい。単位はbyteかな。
APIドキュメントはここ
Interface LoadingCache<K,V>
https://google.github.io/guava/releases/27.0.1-jre/api/docs/com/google/common/cache/LoadingCache.html
Class CacheBuilder<K,V>
https://google.github.io/guava/releases/27.0.1-jre/api/docs/com/google/common/cache/CacheBuilder.html
参考
https://www.baeldung.com/guava-cacheloader
https://github.com/google/guava/wiki/CachesExplained
http://www.codingpedia.org/ama/java-cache-example-with-guava