前回は、EmbeddedなローカルモードでInfinispanを使ってみましたが、今回は単体でサーバとして動作するInfinispan Serverを使ってみます。
こちらのページの「Server」と書かれたボタンを選び、Infinispan Serverをダウンロードします。
http://infinispan.org/download/
自分が取得したファイルは、「infinispan-server-7.0.0.Final-bin.zip」でした。
Infinispan Serverとは?
前回はEmbeddedなキャッシュライブラリみたいな形で使いましたが、こちらはサーバとして独立して起動させる、外部プログラムからアクセス可能なタイプになります。
アクセス方法は、以下の4つがあります。
Javaから使うならHot Rod、他の言語から使うのならMemcachedかRESTといったところでしょう。WebSocketの方は、未だ触ったことがありません…。
Hot Rodについては、一応Java以外の言語向けのクライアントライブラリも存在するようです。
Hot Rod Clients
http://infinispan.org/hotrod-clients/
やっぱり試したことないですけど…。
インストール&起動
では、まずはインストールから。
ダウンロードしたZIPファイルを、適当なディレクトリに展開します。
$ unzip infinispan-server-7.0.0.Final-bin.zip
起動。
$ ./infinispan-server-7.0.0.Final/bin/standalone.sh
起動スクリプトはクラスタリングを意識したものもあるのですが、今回は単体でいきます。
起動中には、こんなログや
16:07:04,122 INFO [org.wildfly.extension.undertow] (MSC service thread 1-3) JBAS017519: Undertow HTTP listener default listening on /127.0.0.1:8080 〜省略〜 16:07:10,323 INFO [org.infinispan.server.endpoint] (MSC service thread 1-2) JDGS010002: REST mapped to /rest
こんなログが出力されるので
16:07:10,829 INFO [org.infinispan.server.endpoint] (MSC service thread 1-2) JDGS010000: WebSocketServer starting 16:07:10,830 INFO [org.infinispan.server.endpoint] (MSC service thread 1-2) JDGS010001: WebSocketServer listening on 127.0.0.1:8181 16:07:10,835 INFO [org.infinispan.server.endpoint] (MSC service thread 1-4) JDGS010000: MemcachedServer starting 16:07:10,837 INFO [org.infinispan.server.endpoint] (MSC service thread 1-4) JDGS010001: MemcachedServer listening on 127.0.0.1:11211 16:07:10,836 INFO [org.infinispan.server.endpoint] (MSC service thread 1-1) JDGS010000: HotRodServer starting 16:07:10,845 INFO [org.infinispan.server.endpoint] (MSC service thread 1-1) JDGS010001: HotRodServer listening on 127.0.0.1:11222
プロトコルとポートはなんとなくわかります。まあ、設定ファイルを見てもいいですけどね。
ちなみに、Infinispan Server 7.0.0.FinalはWildFly 8.1.0.Finalがベースになっているようです。
16:06:46,000 INFO [org.jboss.modules] (main) JBoss Modules version 1.3.3.Final 16:06:47,200 INFO [org.jboss.msc] (main) JBoss MSC version 1.2.2.Final 16:06:47,477 INFO [org.jboss.as] (MSC service thread 1-1) JBAS015899: JBoss Infinispan Server 7.0.0.Final (WildFly 8.1.0.Final) starting
起動完了時。
16:07:12,365 INFO [org.jboss.as] (Controller Boot Thread) JBAS015874: JBoss Infinispan Server 7.0.0.Final (WildFly 8.1.0.Final) started in 27983ms - Started 146 of 156 services (48 services are lazy, passive or on-demand)
では、このInfinispan Serverを使ってキャッシュにアクセスするプログラムを書いていきます。
準備
ここで作成するプログラムで必要な依存関係を含むbuild.sbtを、以下のように定義しました。
build.sbt
name := "remote-starter" version := "0.0.1-SNAPSHOT" scalaVersion := "2.11.4" organization := "org.littlewings" scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature") incOptions := incOptions.value.withNameHashing(true) fork in Test := true parallelExecution in Test := false libraryDependencies ++= Seq( // for Hot Rod "org.infinispan" % "infinispan-client-hotrod" % "7.0.0.Final", "net.jcip" % "jcip-annotations" % "1.0" % "provided", // for Memcached "net.spy" % "spymemcached" % "2.11.4", // for REST "net.databinder.dispatch" %% "dispatch-json4s-jackson" % "0.11.2", "org.scalatest" %% "scalatest" % "2.2.2" % "test" )
また、テストコードの雛形は以下のように用意しておきます。使うクライアントによってimport文が変わるので、その点にはご注意を。
src/test/scala/org/littlewings/infinispan/remote/RemoteCacheSpec.scala
package org.littlewings.infinspan.remote import java.util.concurrent.TimeUnit // for Hot Rod Client import org.infinispan.client.hotrod.RemoteCacheManager import org.infinispan.client.hotrod.configuration.ConfigurationBuilder // for Memcached Client import java.net.InetSocketAddress import net.spy.memcached.MemcachedClient // for REST Client import dispatch._ import dispatch.Defaults._ import org.json4s._ import org.json4s.jackson.Serialization import org.scalatest.FunSpec import org.scalatest.Matchers._ class RemoteCacheSpec extends FunSpec { describe("Infinispan RemoteCache Spec") { // ここに、テストを書く } }
Hot Rod
では、まずはHot Rodから。
Java Hot Rod client
http://infinispan.org/docs/7.0.x/user_guide/user_guide.html#_java_hot_rod_client
いきなりですが、今回用にCache定義をServerに足してみます。以下のように定義されたcache-container-の定義ですが、
*infinispan-server-7.0.0.Final/standalone/configuration/standalone.xmlより抜粋
<subsystem xmlns="urn:infinispan:server:core:7.0" 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>
このようにします。
<subsystem xmlns="urn:infinispan:server:core:7.0"> <cache-container name="local" default-cache="default" statistics="true"> <local-cache name="default" start="EAGER"> <locking striping="false" acquire-timeout="30000" concurrency-level="1000"/> <transaction mode="NONE"/> </local-cache> <local-cache name="memcachedCache" start="EAGER"> <locking striping="false" acquire-timeout="30000" concurrency-level="1000"/> <transaction mode="NONE"/> </local-cache> <local-cache name="namedCache" start="EAGER"/> <local-cache name="hotrodCache"/> <local-cache name="hotrodWithExpiredCache"> <expiration max-idle="3000" lifespan="5000"/> </local-cache> </cache-container> <cache-container name="security"/> </subsystem>
この2つを足しました。
<local-cache name="hotrodCache"/> <local-cache name="hotrodWithExpiredCache"> <expiration max-idle="3000" lifespan="5000"/> </local-cache>
このCacheを使って、確認していきます。
まずは簡単に。
it("Hot Rod Client") { val manager = new RemoteCacheManager(new ConfigurationBuilder() .addServer .host("localhost") .port(11222) .build) val cache = manager.getCache[String, String]("hotrodCache") cache.put("key1", "value1") cache.put("key2", "value2") cache.get("key1") should be ("value1") cache.get("key2") should be ("value2") cache.get("non-exists-key") should be (null) cache.remove("key1") cache.remove("key2") cache.get("key1") should be (null) cache.get("key2") should be (null) cache.stop() manager.stop() }
この範囲だと、ほとんどMap、もしくは組み込み時で使ったCacheと変わりません。
ちなみに、組み込み時と異なり、事前に定義されていない名前のCacheを取得しようとすると、nullになります。
it("Hot Rod Client, using non exists Cache") { val manager = new RemoteCacheManager(new ConfigurationBuilder() .addServer .host("localhost") .port(11222) .build) manager.getCache[String, String]("nonExistsCache") should be (null) manager.stop() }
この時、コンソール上ではこんな感じにメッセージが出力され(例外が飛ぶわけではないです)
WARN: ISPN004005: Error received from the server: org.infinispan.server.hotrod.CacheNotFoundException: Cache with name 'nonExistsCache' not found amongst the configured caches
サーバ側でも例外が出力されます。
16:21:14,120 ERROR [org.infinispan.server.hotrod.HotRodDecoder] (HotRodServerWorker-13-3) ISPN005003: Exception reported: org.infinispan.server.hotrod.CacheNotFoundException: Cache with name 'nonExistsCache' not found amongst the configured caches
Embedded Cacheの時も同じだと思いますが、ちゃんとCacheの定義はしましょうね、と。
有効期限付きCacheの例。
it("Hot Rod Client, with expiration") { val manager = new RemoteCacheManager(new ConfigurationBuilder() .addServer .host("localhost") .port(11222) .build) val cache = manager.getCache[String, String]("hotrodWithExpiredCache") cache.put("key1", "value1") cache.put("key2", "value2") TimeUnit.SECONDS.sleep(2) cache.get("key1") should be ("value1") TimeUnit.SECONDS.sleep(2) // maxIdle cache.get("key1") should be ("value1") cache.get("key2") should be (null) TimeUnit.SECONDS.sleep(2) // lifespan cache.get("key1") should be (null) cache.get("key2") should be (null) cache.stop() manager.stop() }
こちらも、Embeddedな場合とそう変わらず。
Memcached
続いて、Memcachedプロトコルでアクセスしてみます。ただし、Infinispan Serverがサポートしているのは、テキストプロトコルのみです。
Using Infinispan Memcached Server
http://infinispan.org/docs/7.0.x/user_guide/user_guide.html#_using_infinispan_memcached_server
Memcachedクライアントライブラリとしては、spymemcachedを使いました。
spymemcached
https://code.google.com/p/spymemcached/
API Document
http://www.couchbase.com/autodocs/
Memcachedの方は、Infinispan Serverに事前定義済みのものをそのまま使いました。
<local-cache name="memcachedCache" start="EAGER"> <locking striping="false" acquire-timeout="30000" concurrency-level="1000"/> <transaction mode="NONE"/> </local-cache>
テストコードは、こちら。
it("Memcached Client") { val memcachedClient = new MemcachedClient(new InetSocketAddress("localhost", 11211)) memcachedClient.set("key1", 3, "value1") // expire 3 sec memcachedClient.set("key2", 3, "value2") // expire 3sec memcachedClient.get("key1") should be ("value1") memcachedClient.get("key2") should be ("value2") TimeUnit.SECONDS.sleep(3) memcachedClient.get("key1") should be (null) memcachedClient.get("key2") should be (null) }
REST
最後は、HTTPでアクセスできる、REST形式です。
Infinispan REST Server
http://infinispan.org/docs/7.0.x/user_guide/user_guide.html#_infinispan_rest_server
URLとしては、「http://[ホスト名]:[ポート番号]/rest/[Cache名]」を起点に、GETやPUT、DELETEでキャッシュ操作ができます。詳しくは、ドキュメントへ…。
REST用のCacheも、専用のものを追加しました。
<local-cache name="restCache"/>
…実は、REST Serverとして使うパターンが1番ハマりました。
デフォルトでBASIC認証がかかっているので、認証するために以下のスクリプトでユーザを追加したのですが
$ ./infinispan-server-7.0.0.Final/bin/add-user.sh
こういうエラーが出てどうにもうまくいきませんでした…。
Caused by: java.lang.IllegalStateException: JBAS017329: No security context found at org.wildfly.extension.undertow.security.JAASIdentityManagerImpl.verifyCredential(JAASIdentityManagerImpl.java:115) [wildfly-undertow-8.1.0.Final.jar:8.1.0.Final]
というか、アレですね、Security Subsystemがわかってない。
かなり詰まったので、もういいやと思ってBASIC認証を外す方向に転換しました。以下の2箇所のXML定義を
<rest-connector cache-container="local" auth-method="BASIC" security-domain="other" virtual-server="default-host"/> <http-connector name="http-remoting-connector" connector-ref="default" security-realm="ApplicationRealm"/>
このように変更。
<rest-connector cache-container="local" virtual-server="default-host"/> <http-interface security-realm="ManagementRealm" http-upgrade-enabled="true">
ご参考)
管理CLIでのコマンド。
[standalone@localhost:9990 /] /subsystem=endpoint/rest-connector=rest-connector:undefine-attribute(name=security-domain) [standalone@localhost:9990 /] /subsystem=endpoint/rest-connector=rest-connector:undefine-attribute(name=auth-method) [standalone@localhost:9990 /] /subsystem=remoting/http-connector=http-remoting-connector:undefine-attribute(name=security-realm) [standalone@localhost:9990 /] reload
はい。
で、クライアントの方についてはなんでもいいのですが、今回はJSONでやることにしました。
使ったのは、Dispatchのjson4sjacksonモジュールを使いました。
Dispatch
http://dispatch.databinder.net/Dispatch.html
json4s
https://github.com/json4s/json4s
作ったコードは、こんな感じです。
Case Classを使ったので、トップレベルのクラスとして定義して
case class Pair(key: String, value: String)
テストコードはこのように。
it("REST Client") { val http = Http() val baseUrl = host("localhost", 8080) / "rest" / "restCache" val baseReq = baseUrl .setContentType("application/json", "UTF-8") val p1 = Pair("key1", "value1") val p2 = Pair("key2", "value2") implicit val formats = Serialization.formats(NoTypeHints) // PUT val p1RegisterReq = (baseReq / p1.key).PUT << Serialization.write(p1) http(p1RegisterReq).apply().getStatusCode should be (200) val p2RegisterReq = (baseReq / p2.key).PUT << Serialization.write(p2) http(p2RegisterReq).apply().getStatusCode should be (200) // GET val p1GetReq = baseReq / p1.key val p1GetRes = http(p1GetReq OK as.json4s.Json) p1GetRes.apply().extract[Pair] should be (p1) val p2GetReq = baseReq / p2.key val p2GetRes = http(p2GetReq OK as.json4s.Json) p2GetRes.apply().extract[Pair] should be (p2) // DELETE val p1DeleteReq = (baseReq / p1.key).DELETE << Serialization.write(p1) http(p1DeleteReq).apply().getStatusCode should be (200) val p2DeleteReq = (baseReq / p2.key).DELETE << Serialization.write(p2) http(p2DeleteReq).apply().getStatusCode should be (200) // GET http(p1GetReq).apply().getStatusCode should be (404) http(p2GetReq).apply().getStatusCode should be (404) http.shutdown() }
今回は地味にDispatchの勉強になった気がします。
最終的に使いませんでしたが、
val baseReq = baseUrl .as("username", "password") // または .as_! .setContentType("application/json", "UTF-8")
でBASIC認証が使えることもわかりました…。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-starter