Infinispan 7.2の新機能のうちのひとつで、Hot RodでJCacheが使えるようになったという話。
Infinispan 7.2 Release Notes
http://infinispan.org/release-notes/
今まではEmbedded ModeでのJCacheサポートはありましたが、こちらが追加されたので試してみました。
ドキュメントはなさそうだったので、実装とテストコードから追っています。
準備
まずは、ビルド定義。
build.sbt
name := "remote-jcache" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.6" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature") updateOptions := updateOptions.value.withCachedResolution(true) parallelExecution in Test := false libraryDependencies ++= Seq( "javax.cache" % "cache-api" % "1.0.0", "org.infinispan" % "infinispan-jcache-remote" % "7.2.1.Final", "net.jcip" % "jcip-annotations" % "1.0" % "provided", "org.scalatest" %% "scalatest" % "2.2.4" % "test" )
Hot Rod用のJCacheモジュールは、「infinispan-jcache-remote」らしいです。
また、今回はInfinispan Serverが必要なので、ダウンロードページよりServerモジュールを取得しておきます。
http://infinispan.org/download/
この時点では、Infinispan 7.2.1ですね。ダウンロードしたら、解凍・展開。
$ unzip infinispan-server-7.2.1.Final-bin.zip
サーバーは起動しておきます。
$ infinispan-server-7.2.1.Final/bin/standalone.sh
今回は、スタンドアロンでいきます。
利用するCacheは、事前定義済みで特に設定の入っていない、「namedCache」を使うことにします。
(infinispan-server-7.2.1.Final/standalone/configuration/standalone.xml より)
<subsystem xmlns="urn:infinispan:server:core:7.2" default-cache-container="local"> <cache-container name="local" default-cache="default" statistics="true"> <local-cache name="default" start="EAGER"> <locking acquire-timeout="30000" concurrency-level="1000" striping="false"/> <transaction mode="NONE"/> </local-cache> <local-cache name="memcachedCache" start="EAGER"> <locking acquire-timeout="30000" concurrency-level="1000" striping="false"/> <transaction mode="NONE"/> </local-cache> <local-cache name="namedCache" start="EAGER"/> </cache-container> <cache-container name="security"/> </subsystem>
とりあえず、事前準備はここまで。
JCacheを使ったコードを書く
それでは、JCacheを利用したコードを書いていきます。
動作確認自体は、ScalaTestを使ったテストコードで行うので、雛形を用意。
src/test/scala/org/littlewings/infinispan/jcache/JCacheRemoteSpec.scala
package org.littlewings.infinispan.jcache import java.util import java.util.Properties import java.util.concurrent.TimeUnit import javax.cache.configuration.{Configuration, MutableConfiguration} import javax.cache.expiry.{AccessedExpiryPolicy, Duration} import javax.cache.processor.EntryProcessorResult import javax.cache.{CacheException, Cache, Caching} import scala.collection.JavaConverters._ import org.scalatest.FunSpec import org.scalatest.Matchers._ class JCacheRemoteSpec extends FunSpec { describe("Infinispan JCache Remote Spec") { // ここに、テストを書く! } }
Hello World的な
まずはJCacheの導入的なコードから。
it("simple usage") { val provider = Caching.getCachingProvider val manager = provider.getCacheManager val configuration = new MutableConfiguration[String, String]() .setTypes(classOf[String], classOf[String]) val cache: Cache[String, String] = manager.createCache("namedCache", configuration) cache.put("key1", "value1") cache.get("key1") should be("value1") cache.remove("key1") cache.close() manager.close() provider.close() }
当たり前かもですが、普通にJCacheを使ったコードで動作します。
有効期限を設定する
一定時間アクセスしなかった場合は、エントリが有効期限切れするCacheを作成・利用。
it("with expiry") { val provider = Caching.getCachingProvider val manager = provider.getCacheManager val configuration = new MutableConfiguration() .setTypes(classOf[String], classOf[String]) .setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, 5))) val cache: Cache[String, String] = manager.createCache("namedCache", configuration) cache.put("key1", "value1") cache.put("key2", "value2") TimeUnit.SECONDS.sleep(3L) cache.get("key1") TimeUnit.SECONDS.sleep(3L) cache.get("key1") should be("value1") cache.get("key2") should be(null) cache.close() manager.close() provider.close() }
こちらもOK。
CacheManager#createCacheを行わなかった場合は?
個人的に見てきたJCacheの実装では、CacheManagerを構築する際に実装依存の定義ファイルを読めるようにしたりすると、CacheManager#createCacheを呼び出さなくても定義済みのCacheをいきなりgetCacheできたのですが、これをHot Rodで試してみると…
it("predefined?") { val provider = Caching.getCachingProvider val manager = provider.getCacheManager manager.getCache("namedCache", classOf[String], classOf[String]) should be(null) manager.close() provider.close() }
nullになりました。
というか、Hot Rodを素で使っても、Cacheを定義できるAPIってないですからね。こうなってもおかしくないなぁという感じ。
Infinispan Serverに未定義のCacheをcreateCacheしようとした場合
これは、Hot Rodで使う場合はエラーになっていたので、JCache over Hot Rodでもエラーになりました。
it("not found cache") { val provider = Caching.getCachingProvider val manager = provider.getCacheManager val configuration = new MutableConfiguration[String, String]() .setTypes(classOf[String], classOf[String]) a[CacheException] should be thrownBy { manager.createCache[String, String, Configuration[String, String]]("testCache", configuration) } manager.close() provider.close() }
これは予想できた挙動です。
Propertiesを渡して設定を行う
Hot Rodを素で使う場合はプロパティファイルで設定したり、ConfigurationBuilderで設定ができたりしたのですが、JCache over Hot RodだとCachingProvider#getCacheManagerを呼び出す際に、PropertiesにHot Rodで利用可能な項目を設定して呼び出すことで行うようです。
it("with Properties") { val properties = new Properties properties.setProperty("infinispan.client.hotrod.server_list", "localhost:11222") // 以下でも可 // org.infinispan.client.hotrod.impl.ConfigurationProperties に定義 //properties.setProperty(ConfigurationProperties.SERVER_LIST, "localhost:11222") properties.setProperty("infinispan.client.hotrod.protocol_version", "2.1") val provider = Caching.getCachingProvider val manager = provider.getCacheManager(provider.getDefaultURI, provider.getDefaultClassLoader, properties) val configuration = new MutableConfiguration[String, String]() .setTypes(classOf[String], classOf[String]) val cache: Cache[String, String] = manager.createCache("namedCache", configuration) cache.put("key1", "value1") cache.get("key1") should be("value1") cache.remove("key1") cache.close() manager.close() provider.close() }
なお、自分はあまりHot Rodの設定をしたことがないのですが、設定項目はこのあたりを見るのがよいようです。
http://docs.jboss.org/infinispan/7.2/apidocs/org/infinispan/client/hotrod/RemoteCacheManager.html
https://github.com/infinispan/infinispan/blob/7.2.1.Final/client/hotrod-client/src/main/java/org/infinispan/client/hotrod/impl/ConfigurationProperties.java
自分で定義したクラスをCacheに登録する
今まで、StringをCacheに放り込んでいましたが、自分で定義したクラスも普通に使えますよ、と。
@SerialVersionUID(1L) class Person(val firstName: String, val lastName: String, val age: Int) extends Serializable
ただし、シリアライズ可能としておくこと。
it("user defined class") { val provider = Caching.getCachingProvider val manager = provider.getCacheManager val configuration = new MutableConfiguration[String, Person]() .setTypes(classOf[String], classOf[Person]) val cache: Cache[String, Person] = manager.createCache("namedCache", configuration) cache.put("1", new Person("カツオ", "磯野", 12)) cache.get("1").firstName should be("カツオ") cache.remove("key1") cache.close() manager.close() provider.close() }
EntryProcessorを使う
最後に、EntryProcessorを使ってみます。
今までScalaで書いていましたが、EntryProcessorはJavaで書かないとうまく動かせませんでした…。
src/test/java/org/littlewings/infinispan/jcache/DoublingEntryProcessor.java
package org.littlewings.infinispan.jcache; import java.io.Serializable; import javax.cache.processor.EntryProcessor; import javax.cache.processor.MutableEntry; public class DoublingEntryProcessor implements EntryProcessor<String, Integer, Integer> { @Override public Integer process(MutableEntry<String, Integer> entry, Object... arguments) { return entry.getValue() * 2; } }
EntryProcessorは、ここではSerializableにしなくても動きました。
※とはいえ、普通はやった方がいいと思います
EntryProcessor利用コード。
it("entry processor") { val provider = Caching.getCachingProvider val manager = provider.getCacheManager val configuration = new MutableConfiguration[String, Integer]() .setTypes(classOf[String], classOf[Integer]) val cache: Cache[String, Integer] = manager.createCache("namedCache", configuration) (1 to 10).foreach(i => cache.put(s"key$i", i)) val keys = (1 to 10).map(i => s"key$i").toSet.asJava val result: util.Map[String, EntryProcessorResult[Integer]] = cache.invokeAll(keys, new DoublingEntryProcessor) result.asScala.values.map(_.get).foldLeft(0)(_ + _) should be(110) cache.clear() cache.close() manager.close() provider.close() }
このEntryProcessor、どこで動いているかですが、どうもクライアント側で動いているようです。
https://github.com/infinispan/infinispan/blob/7.2.1.Final/jcache/commons/src/main/java/org/infinispan/jcache/AbstractJCache.java#L172
なので、Serializableでなくてもよかった、と。上記のAbstractJCacheは、Embedded Cache、Hot Rod共通の親クラスなので、Embedded ModeのEntryProcessorもCache#invokeやinvokeAllを呼び出したNodeで動きそうですね。
そんな感じなので、自前で定義したクラスを対象にしたEntryProcessorを作ってみて
src/test/java/org/littlewings/infinispan/jcache/FirstNameEntryProcessor.java
package org.littlewings.infinispan.jcache; import javax.cache.processor.EntryProcessor; import javax.cache.processor.MutableEntry; public class FirstNameEntryProcessor implements EntryProcessor<String, Person, String> { @Override public String process(MutableEntry<String, Person> entry, Object... arguments) { return entry.getValue().firstName(); } }
これを利用するコードを書いてみましたが、こちらもOKでした。
it("entry processor, with user defined class") { val provider = Caching.getCachingProvider val manager = provider.getCacheManager val configuration = new MutableConfiguration[String, Person]() .setTypes(classOf[String], classOf[Person]) val cache: Cache[String, Person] = manager.createCache("namedCache", configuration) cache.put("1", new Person("カツオ", "磯野", 12)) cache.invoke("1", new FirstNameEntryProcessor) should be("カツオ") cache.clear() cache.close() manager.close() provider.close() }
使い方についてはだいぶ分かった気がするので、OKでしょう。
今回書いたコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-jcache