この前、Payara Microを使ってJCache越しにHazelcastで遊んでみました。
JCache(Hazelcast) on Payara Microを試す
http://d.hatena.ne.jp/Kazuhira/20150523/1432366272
今度は、HazelcastのAPIを直に使ってみたいと思います。利用するHazelcastのインスタンスは、Payara Micro自身が管理しているものを前提にします。
準備
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.littlewings</groupId> <artifactId>payra-micro-rawapi</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>war</packaging> <build> <finalName>payara-micro-rawapi</finalName> </build> <dependencies> <dependency> <groupId>fish.payara.extras</groupId> <artifactId>payara-micro</artifactId> <version>4.1.152.1</version> <scope>provided</scope> </dependency> </dependencies> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <failOnMissingWebXml>false</failOnMissingWebXml> </properties> </project>
「mvn package」で生成されるWARファイルの名前は、「payra-micro-rawapi.war」になります。
Payara Micro自体も、以下のページからダウンロードしておいてください。
http://www.payara.co.uk/downloads
今回も、「payara-micro-4.1.152.1.jar」を使用します。
あと、JAX-RSは使用するので、有効化のためのクラスを用意。
src/main/java/org/littlewings/hazelcast/rest/JaxrsApplication.java
package org.littlewings.hazelcast.rest; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("rest") public class JaxrsApplication extends Application { }
HazelcastInstanceを取得する
それでは、Hazelcastを使うにあたり、中心となるHazelcastInstanceの取得方法から。
ぶっちゃけ、ここを見ればOKです。
5. Using Hazelcast in your Applications
https://github.com/payara/Payara/wiki/Hazelcast-%28Payara-4.1.151%29#5-using-hazelcast-in-your-applications
JNDIルックアップしてね、と。
※JCacheの各種クラスもルックアップ可能ですが、今回は端折ります
確認用に、こんなクラスを用意。
src/main/java/org/littlewings/hazelcast/rest/HazelcastLookupResource.java
package org.littlewings.hazelcast.rest; import javax.annotation.Resource; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import com.hazelcast.core.HazelcastInstance; import fish.payara.nucleus.hazelcast.HazelcastCore; @Path("lookup") @RequestScoped public class HazelcastLookupResource { // ここに、確認用のコードを書く }
では、いくつかバリエーションを。
JNDIルックアップ。ドキュメント上は、こちらが正面案。
@GET @Path("jndi") @Produces(MediaType.TEXT_PLAIN) public String jndi() throws NamingException { InitialContext context = new InitialContext(); try { HazelcastInstance instance = (HazelcastInstance) context.lookup("payara/Hazelcast"); return instance.getName(); } finally { context.close(); } }
JNDI名は、「payara/Hazelcast」だそうで。
@Resourceで注入。
@Resource(name = "payara/Hazelcast") private HazelcastInstance hazelcastInstanceByResourcd; @GET @Path("resource") @Produces(MediaType.TEXT_PLAIN) public String resource() { return hazelcastInstanceByResourcd.getName(); }
CDIで引き抜く。
@Inject private HazelcastInstance hazelcastInstanceByCdi; @GET @Path("cdi") @Produces(MediaType.TEXT_PLAIN) public String cdi() { return hazelcastInstanceByCdi.getName(); }
まあ、これや@Resourceで抜けるのは、このJAX-RSリソースクラスがCDI管理Beanだからかと…。
あと、fish.payara.nucleus.hazelcast.HazelcastCoreというクラスを使うと…もっと簡単に引っこ抜けるのですが…どう見ても内部APIなので割愛。興味があれば、後でGitHubのコードを。
ひとつくらいは、動作確認の結果を貼っておきましょう。
$ curl 'http://localhost:8180/payara-micro-rawapi/rest/lookup/jndi' glassfish-web.server
いずれのAPIも、HazelcastInstanceの名前を返します。結果は、すべて同じ名前です(同じインスタンスを見ているから、そりゃあそうだという話ですが)。インスタンス名は、「glassfish-web.server」です。
Distributed Mapを使ってみる
続いて、Hazelcastが提供する分散データ構造を使ってみましょう。今回は、Distributed Mapを使います。その他、ListやSet、Queueなどいろいろあるので、興味のある方はHazelcastのドキュメントを参照してください。
では、Distributed Mapを使うということで…CDIのProducerを定義。
※このコードには問題があるので、後ろの方で修正します。
src/main/java/org/littlewings/hazelcast/producer/DistributedMapProducer.java
package org.littlewings.hazelcast.producer; import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Produces; import javax.inject.Inject; import com.hazelcast.config.Config; import com.hazelcast.config.MapConfig; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; @Dependent public class DistributedMapProducer { @Inject private HazelcastInstance hazelcastInstance; @ApplicationScoped @Produces public IMap<String, String> createSimpleDistributedMap() { return hazelcastInstance.getMap("default"); } @ApplicationScoped @Produces IMap<String, Integer> createWithExpiryDistributedMap() { Config config = hazelcastInstance.getConfig(); MapConfig mapConfig = new MapConfig("withExpiryMap"); mapConfig.setTimeToLiveSeconds(10); config.addMapConfig(mapConfig); return hazelcastInstance.getMap("withExpiryMap"); } }
ひとつはデフォルトのDistributed Mapを定義、そしてもうひとつはエントリの有効期限が10秒のDistributed Mapを定義しました。Qualifierは使いたくなかったので、型パラメーターで分けるということに…。
Payara Microでは、Hazelcastの設定はこんな形でAPI経由でしかできなさそうな感じで、設定ファイルは使える雰囲気がありません。また、先にHazelcastのインスタンスが起動してしまうので、クラスタ構成回りにはPayara Microの起動パラメーターの範囲でしか変更できなさそうな感じです。また、Hazelcastクラスタの構成については、Server(Embedded?)構成、Client/Server構成のうち、Server構成しかとることができません。
この点は、注意ですね。
それでは、これらを利用するCDI管理Beanを用意。
まずは、デフォルトのDistributed Mapを使う方。
src/main/java/org/littlewings/hazelcast/service/MessageService.java
package org.littlewings.hazelcast.service; import java.util.concurrent.TimeUnit; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import com.hazelcast.core.IMap; @ApplicationScoped public class MessageService { @Inject private IMap<String, String> simpleMap; public String build(String key, String word) { if (simpleMap.containsKey(key)) { return simpleMap.get(key); } try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { } String message = String.format("Hello %s!!", word); simpleMap.put(key, message); return simpleMap.get(key); } }
キー、そして渡された単語に、「Hello 」をくっつけるだけの簡単なもの…。Distributed Mapを使っていることがわかりやすいように、3秒間のスリープを入れています。キーは、そのままDistributed Mapのキーになります。
有効期限付きの方。
src/main/java/org/littlewings/hazelcast/service/TripleService.java
package org.littlewings.hazelcast.service; import java.util.concurrent.TimeUnit; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; import com.hazelcast.core.IMap; @ApplicationScoped public class TripleService { @Inject private IMap<String, Integer> withExpiryMap; public int execute(String key, int seed) { if (withExpiryMap.containsKey(key)) { return withExpiryMap.get(key); } try { TimeUnit.SECONDS.sleep(3L); } catch (InterruptedException e) { } int tripled = seed * 3; withExpiryMap.put(key, tripled); return withExpiryMap.get(key); } }
こちらは、Distributed Mapの型パラメーターがString、Integerなので、キーともらった値を3倍して返すクラスに…。
いや、よくわからないサンプルですが、あまり良いものが思いつかずでして…。
で、これらを使用するJAX-RSリソースクラス。
src/main/java/org/littlewings/hazelcast/rest/DistributedMapResource.java
package org.littlewings.hazelcast.rest; import javax.enterprise.context.RequestScoped; import javax.inject.Inject; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import org.littlewings.hazelcast.service.MessageService; import org.littlewings.hazelcast.service.TripleService; @Path("dist") @RequestScoped public class DistributedMapResource { @Inject private MessageService messageService; @GET @Path("simple") @Produces(MediaType.TEXT_PLAIN) public String simple(@QueryParam("key") @DefaultValue("key") String key, @QueryParam("word") @DefaultValue("World") String word) { return messageService.build(key, word); } @Inject private TripleService tripleService; @GET @Path("expiry") @Produces(MediaType.TEXT_PLAIN) public int expiry(@QueryParam("key") @DefaultValue("key") String key, @QueryParam("value") @DefaultValue("0") int value) { return tripleService.execute(key, value); } }
動作確認
パッケージングして
$ mvn package
動かしてみます。
今回は、最初から2 Nodeでいきましょう。
## Node 1 $ java -jar payara-micro-4.1.152.1.jar --deploy target/payara-micro-rawapi.war ## Node 2 $ java -jar payara-micro-4.1.152.1.jar --deploy target/payara-micro-rawapi.war --port 8180
ひとつめのNodeのリッスンポートが8080、2つめが8180です。
起動中に、このような表示が出てクラスタが構成されたことが確認できます(こちらは、Node 2の表示です)。
Members [2] { Member [192.168.254.129]:5900 Member [192.168.254.129]:5901 this }
では、アクセスしてみます。まずは、デフォルトのDistributed Mapから。Node 1へアクセス。
$ time curl 'http://localhost:8080/payara-micro-rawapi/rest/dist/simple?key=key1&word=Hazelcast' Hello Hazelcast!! real 0m3.400s user 0m0.000s sys 0m0.009s
3秒少々(起動直後なので、若干遅い…)。続いて、ポートを変えてNode 2へ。
$ time curl 'http://localhost:8180/payara-micro-rawapi/rest/dist/simple?key=key1&word=Hazelcast' Hello Hazelcast!! real 0m0.503s user 0m0.000s sys 0m0.006s
こちらは、3秒待ったりしません。
キーを変えると、また3秒かかります。
$ time curl 'http://localhost:8180/payara-micro-rawapi/rest/dist/simple?key=key10000&word=Hazelcast' Hello Hazelcast!! real 0m3.033s user 0m0.006s sys 0m0.004s
Node 1へ。
$ time curl 'http://localhost:8080/payara-micro-rawapi/rest/dist/simple?key=key10000&word=Hazelcast' Hello Hazelcast!! real 0m0.025s user 0m0.008s sys 0m0.000s
OKそうですね。
続いて、有効期限付きの方へ。Node 1から。
$ time curl 'http://localhost:8080/payara-micro-rawapi/rest/dist/expiry?key=key1&value=3' 9 real 0m3.058s user 0m0.005s sys 0m0.003s
Node 2。
$ time curl 'http://localhost:8180/payara-micro-rawapi/rest/dist/expiry?key=key1&value=3' 9 real 0m0.073s user 0m0.007s sys 0m0.000s
データが共有されていることがわかります。
そしてしばらくすると、
$ time curl 'http://localhost:8180/payara-micro-rawapi/rest/dist/expiry?key=key1&value=3' 9 real 0m3.024s user 0m0.000s sys 0m0.007s
と時間がかかるようになり、10秒後にはまた時間がかかようになるのですが…。
なんか、先にデフォルトのDistributed Mapにアクセスすると有効期限が無視されるようになりました…。デフォルト設定が最初に見られたのがダメなんでしょうなぁ、と。
というわけで、Producerをこう修正。
@Dependent public class DistributedMapProducer { @Inject private HazelcastInstance hazelcastInstance; @ApplicationScoped @Produces public IMap<String, String> createSimpleDistributedMap() { return hazelcastInstance.getMap("default"); } @ApplicationScoped @Produces IMap<String, Integer> createWithExpiryDistributedMap() { /* Config config = hazelcastInstance.getConfig(); MapConfig mapConfig = new MapConfig("withExpiryMap"); mapConfig.setTimeToLiveSeconds(10); config.addMapConfig(mapConfig); */ return hazelcastInstance.getMap("withExpiryMap"); } @PostConstruct public void configuration() { Config config = hazelcastInstance.getConfig(); MapConfig mapConfig = new MapConfig("withExpiryMap"); mapConfig.setTimeToLiveSeconds(10); config.addMapConfig(mapConfig); } }
これなら、最初にデフォルトのDistributed Mapを参照しても大丈夫になりました。普段、XMLファイルで設定してるからですねぇ…ちょっと踏んだ気分ですが、できたので良しとしましょう。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/hazelcast-examples/tree/master/payara-micro-rawapi