LoginSignup
8
7

More than 5 years have passed since last update.

GuavaのLoadingCacheの実装サンプル

Posted at

業務で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にアクセスしたデータをキャッシュしておきます。キャッシュの保持期間やデータ数はexpireAfterAccessmaximumSizeで設定することができます。下記は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を使って、本当にキャッシュが効いているか確認します。

  1. id=151にアクセス(遅い。初回のアクセスのため)
  2. id=151にアクセス(速い。151はキャッシュされている)
  3. id=150にアクセス(遅い。150は初回アクセスのため)
  4. 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

8
7
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
8
7