CLOVER🍀

That was when it all began.

Infinispanを使ってみる

仕事で、ちょっとKey Value Storeについて調べていて、JBossファミリのInfinispanというものを知りました。

InfoQの以下の記事によると、
http://www.infoq.com/jp/news/2011/10/java-data-grid
以下のようなJSRの実装であるようです。

  • JSR-107(Javaプラットフォーム向けキャッシュ)
  • JSR-347(Javaプラットフォーム向けDataGrid)

それから、「JBoss Data Grid」という製品の中核になっているらしいです。

ぶっちゃけKVSって触ったことがないんですが、何か触ってみたいなとは思っていたので、ちょっとここでInfinispanに手を出してみることにしました。

Infinispan
https://www.jboss.org/infinispan
ドキュメント
https://docs.jboss.org/author/display/ISPN/Home

まあ、KVSというよりはData Gridという扱いのようですが…。

日本語資料の取っ掛かりとしては、以下がよさそうです。
http://www.slideshare.net/AdvancedTechNight/infinispan-14380723
http://www.slideshare.net/nekop/infinispan-open-source-data-grid

その他、英語ですが…。
http://www.slideshare.net/guest69f868/infinispan
http://www.slideshare.net/maniksurtani/infinispan-data-grids-nosql-cloud-storage-and-jsr-347
http://www.slideshare.net/galderz/infinispan-for-dummies
http://www.slideshare.net/tristantarrant/infinispan-jbug-milano
http://www.slideshare.net/ptjug/introducing-infinispan
http://www.slideshare.net/JBUG_London/infinispan-from-poc-to-production-13455333

Ehcacheみたいにアプリケーションが動作するJavaVMに埋め込んで使うEmbedded Cacheと、Infinispanを別のJavaVMで起動してクライアントから使用するClustering Cacheがあるんだそうな。
*Ehcacheの場合は、分散キャッシュをやろうとするとTerracottaがつきまとってくるみたいですが…

では、まずは使ってみましょう。

最初は、Embedded Cacheから

なんかオフィシャルのGetting Started
https://docs.jboss.org/author/display/ISPN/Getting+Started+Guide+-+Embedded+Cache+in+Java+SE
が見れないので、他を調べつつ書いてみました。

今回使用するInfinispanのバージョンは、5.2.0.Finalです。

build.sbt

name := "infinispan-embedded"

version := "0.0.1"

scalaVersion := "2.10.0"

organization := "littlewings"

fork in run := true

resolvers += "jboss repository" at "http://repository.jboss.org/nexus/content/groups/public-jboss/"

libraryDependencies += "org.infinispan" % "infinispan-core" % "5.2.0.Final"

forkの設定を入れておかないと、Embedded Cacheだとsbt上でrunコマンドを実行していると、2回目以降はJMXにMBeanを複数回登録しようとして失敗します…。

とりあえず、こんなのを書いてみます。コードがScalaなのは、気にしない方向で。
src/main/scala/EmbeddedModeClient.scala

import org.infinispan.Cache
import org.infinispan.manager.{DefaultCacheManager, EmbeddedCacheManager}

object EmbeddedModeClient {
  def main(args: Array[String]): Unit = {
    val manager: EmbeddedCacheManager = new DefaultCacheManager
    val cache: Cache[String, String] = manager.getCache()

    cache.put("key1", "value1")
    cache.put("key2", "value2")

    println(s"key1 => ${cache.get("key1")}")  // => key1 => value1
    println(s"key2 => ${cache.get("key2")}")  // => key2 => value2

    cache.remove("key1")
    cache.remove("key2")

    println(s"key1 => ${cache.get("key1")}")  // => key1 => null
    println(s"key2 => ${cache.get("key2")}")  // => key2 => null
  }
}

実行結果はコメントに書いていますが、だいたいMapのような使い方をします。というか、Cacheインターフェースがjava.util.concurrent.ConcurrentMapインターフェースを実装してますので。

Embedded Cacheで使う場合は、DefaultCacheManagerを使います。

    val manager: EmbeddedCacheManager = new DefaultCacheManager

で、作成したManagerから、Cacheインターフェースのインスタンスを取得するといった感じですね。

    val cache: Cache[String, String] = manager.getCache()

キャッシュのエントリの有効期限を設定する

Cache#putする時に、有効期限を設定できます。設定しないと、デフォルトは有効期限なしのようです。
src/main/scala/EmbeddedModeClient.scala

import java.util.concurrent.TimeUnit

import org.infinispan.Cache
import org.infinispan.manager.{DefaultCacheManager, EmbeddedCacheManager}

object EmbeddedModeClient {
  def main(args: Array[String]): Unit = {
    val manager: EmbeddedCacheManager = new DefaultCacheManager
    val cache: Cache[String, String] = manager.getCache()

    cache.put("key1", "value1")
    cache.put("key2", "value2", 1, TimeUnit.SECONDS)

    println("Sleep...")
    Thread.sleep(3000L)

    println(s"key1 => ${cache.get("key1")}")  // => key1 => value1
    println(s"key2 => ${cache.get("key2")}")  // => key2 => null
  }
}

時間の指定には、java.util.concurrent.TimeUnitを使います。「key2」の方は、有効期限を1秒にしているので、3秒スリープした後はキャッシュがなくなっていますね。

名前付きキャッシュを使う

EmbeddedCacheManager#getCacheする時に、引数にキャッシュの名前を与えることで、別のキャッシュを作成することができます。
src/main/scala/EmbeddedModeClient.scala

import java.util.concurrent.TimeUnit

import org.infinispan.Cache
import org.infinispan.manager.{DefaultCacheManager, EmbeddedCacheManager}

object EmbeddedModeClient {
  def main(args: Array[String]): Unit = {
    val manager: EmbeddedCacheManager = new DefaultCacheManager
    val cache: Cache[String, String] = manager.getCache()

    cache.put("key1", "value1")
    cache.put("key2", "value2")

    println(s"key1 => ${cache.get("key1")}")  // => key1 => value1
    println(s"key2 => ${cache.get("key2")}")  // => key2 => value2

    cache.remove("key1")
    cache.remove("key2")

    println(s"key1 => ${cache.get("key1")}")  // => key1 => null
    println(s"key2 => ${cache.get("key2")}")  // => key2 => null

    val namedCache1: Cache[String, String] = manager.getCache("namedCache1")
    println(s"namedCache1: key1 => ${namedCache1.get("key1")}")  // => namedCache1: key1 => null
    println(s"namedCache1: key2 => ${namedCache1.get("key2")}")  // => namedCache1: key2 => null
  }
}

名前付きキャッシュの事前定義は不要ですが、キャッシュの設定はデフォルトのものが使用されるようです。

それぞれのキャッシュは、もちろん別々に管理されます。

名前付きキャッシュの設定をする(プログラム編)

org.infinispan.configuration.cache.ConfigurationBuilderおよびorg.infinispan.configuration.cache.Configurationを使用することで、名前付きキャッシュの設定をすることができます。
src/main/scala/EmbeddedModeClient.scala

import java.util.concurrent.TimeUnit

import org.infinispan.Cache
import org.infinispan.configuration.cache.ConfigurationBuilder
import org.infinispan.eviction.EvictionStrategy
import org.infinispan.manager.{DefaultCacheManager, EmbeddedCacheManager}

object EmbeddedModeClient {
  def main(args: Array[String]): Unit = {
    val manager: EmbeddedCacheManager = new DefaultCacheManager
    manager.defineConfiguration(
      "namedCache2",
      new ConfigurationBuilder()
        .eviction
        .strategy(EvictionStrategy.LIRS)
        .maxEntries(10)
        .build
    )

    val namedCache2: Cache[String, String] = manager.getCache("namedCache2")
    println(s"namedCache2: key1 => ${namedCache2.get("key1")}")  // => namedCache2: key1 => null
    println(s"namedCache2: key2 => ${namedCache2.get("key2")}")  // => namedCache2: key2 => null
  }
}

EmbeddedCacheManager#defineConfigurationの第1引数がキャッシュの名前で、第2引数にConfigurationクラスのインスタンスを設定することで、名前付きキャッシュの設定を行います。Configurationクラスのインスタンスは、ConfigurationBuilderクラスを使用して作成するようです。
ここでは、

で定義しています。

名前付きキャッシュの設定をする(XMLファイル編)

プログラムでキャッシュの設定をする他に、XMLファイルで設定をすることもできます。
src/main/scala/XmlConfigurationEmbeddedClient.scala

import java.util.concurrent.TimeUnit

import org.infinispan.Cache
import org.infinispan.manager.{DefaultCacheManager, EmbeddedCacheManager}

object XmlConfigurationEmbeddedClient {
  def main(args: Array[String]): Unit = {
    val manager: EmbeddedCacheManager = new DefaultCacheManager("infinispan.xml")
    val cache: Cache[String, String] = manager.getCache("xml-configured-cache")

    ...
  }
}

今回は、DefaultCacheManagerのコンストラクタ引数に設定ファイル名を与えています。

    val manager: EmbeddedCacheManager = new DefaultCacheManager("infinispan.xml")

設定ファイルは、最初にクラスパスから検索され、見つからなければパスを絶対パスとして解釈して探すんだそうな。

今回用意した設定ファイルは、こちらです。
src/main/resources/infinispan.xml

<?xml version="1.0" encoding="UTF-8"?>
<infinispan
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="urn:infinispan:config:5.2 http://www.infinispan.org/schemas/infinispan-config-5.2.xsd"
    xmlns="urn:infinispan:config:5.2">

  <namedCache name="xml-configured-cache">
    <eviction strategy="LIRS" maxEntries="2" />
    <expiration lifespan="10000" maxIdle="1000" />
  </namedCache>
</infinispan>
  • キャッシュの名前は「xml-configured-cache」
  • キャッシュアルゴリズムは「LIRS」、最大保存数は2
  • 有効期限は10秒だが、アイドル状態が1秒続くと有効期限切れ

というわけで、こんな感じで試してみます。
src/main/scala/XmlConfigurationEmbeddedClient.scala

import java.util.concurrent.TimeUnit

import org.infinispan.Cache
import org.infinispan.manager.{DefaultCacheManager, EmbeddedCacheManager}

object XmlConfigurationEmbeddedClient {
  def main(args: Array[String]): Unit = {
    val manager: EmbeddedCacheManager = new DefaultCacheManager("infinispan.xml")
    val cache: Cache[String, String] = manager.getCache("xml-configured-cache")

    cache.put("key1", "value1")
    cache.get("key1")
    cache.get("key1")
    cache.put("key2", "value2")
    cache.put("key3", "value3")

    println(s"key1 => ${cache.get("key1")}")  // => key1 => value1
    println(s"key2 => ${cache.get("key2")}")  // => key2 => null
    println(s"key3 => ${cache.get("key3")}")  // => key3 => value3

    println("Sleep...")
    Thread.sleep(3000L)

    println(s"key1 => ${cache.get("key1")}")  // => key1 => null
    println(s"key2 => ${cache.get("key2")}")  // => key2 => null
    println(s"key3 => ${cache.get("key3")}")  // => key3 => null
  }
}

実行結果はコメント参照ですが、キャッシュに3つのエントリを入れていますが、先に「key1」に対して参照をかけているので、「key2」のエントリがなくなっています。
続いて、3秒スリープをかけた後は、全てのエントリがなくなっていますね。


とりあえず、Embedded Cacheはこんな感じで。

公式のドキュメントがなんかわかりにくいので、以下のリソースを参照した方がいいような気がします。

infinispan-quickstart
https://github.com/infinispan/infinispan-quickstart
JBoss Data Grid スタートガイド
https://access.redhat.com/knowledge/docs/ja-JP/JBoss_Data_Grid/6/html-single/Getting_Started_Guide/index.html
JBoss Data Grid 管理設定ガイド
https://access.redhat.com/knowledge/docs/ja-JP/JBoss_Data_Grid/6/html-single/Administration_and_Configuration_Guide/index.html
JBoss Data Grid ドキュメント
https://access.redhat.com/knowledge/docs/JBoss_Data_Grid/?locale=ja-JP

あと、Embedded Cacheで最初に書いていたプログラムの完全な形を載せておきます。
src/main/scala/EmbeddedModeClient.scala

import java.util.concurrent.TimeUnit

import org.infinispan.Cache
import org.infinispan.manager.{DefaultCacheManager, EmbeddedCacheManager}

object EmbeddedModeClient {
  def main(args: Array[String]): Unit = {
    val manager: EmbeddedCacheManager = new DefaultCacheManager
    val cache: Cache[String, String] = manager.getCache()

    cache.put("key1", "value1")
    cache.put("key2", "value2")

    println(s"key1 => ${cache.get("key1")}")  // => key1 => value1
    println(s"key2 => ${cache.get("key2")}")  // => key2 => value2

    cache.remove("key1")
    cache.remove("key2")

    println(s"key1 => ${cache.get("key1")}")  // => key1 => null
    println(s"key2 => ${cache.get("key2")}")  // => key2 => null

    cache.put("key1", "value1")
    cache.put("key2", "value2", 1, TimeUnit.SECONDS)

    println("Sleep...")
    Thread.sleep(3000L)

    println(s"key1 => ${cache.get("key1")}")  // => key1 => value1
    println(s"key2 => ${cache.get("key2")}")  // => key2 => null

    val namedCache1: Cache[String, String] = manager.getCache("namedCache1")
    println(s"namedCache1: key1 => ${namedCache1.get("key1")}")  // => namedCache1: key1 => null
    println(s"namedCache1: key2 => ${namedCache1.get("key2")}")  // => namedCache1: key2 => null

    import org.infinispan.configuration.cache.ConfigurationBuilder
    import org.infinispan.eviction.EvictionStrategy
    manager.defineConfiguration(
      "namedCache2",
      new ConfigurationBuilder()
        .eviction
        .strategy(EvictionStrategy.LIRS)
        .maxEntries(10)
        .build
    )

    val namedCache2: Cache[String, String] = manager.getCache("namedCache2")
    println(s"namedCache2: key1 => ${namedCache2.get("key1")}")  // => namedCache2: key1 => null
    println(s"namedCache2: key2 => ${namedCache2.get("key2")}")  // => namedCache2: key2 => null
  }
}