仕事で、ちょっとKey Value Storeについて調べていて、JBossファミリのInfinispanというものを知りました。
InfoQの以下の記事によると、
http://www.infoq.com/jp/news/2011/10/java-data-grid
以下のようなJSRの実装であるようです。
それから、「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クラスを使用して作成するようです。
ここでは、
- キャッシュアルゴリズムにLIRS
- キャッシュ保存の最大数10
で定義しています。
名前付きキャッシュの設定をする(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>
というわけで、こんな感じで試してみます。
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 } }