うちのブログ、けっこうHazelcastについて書いていますが、Server側ばかりでほとんどClient側を使っていないので、たまには試してみようかなということで。
ClientにはJava、C++、.NETがあり、それ以外の形態としてREST、Memcachedがあります。
Clients
http://docs.hazelcast.org/docs/3.4/manual/html-single/hazelcast-documentation.html#clients
今回は、Java Clientを使用します。
Java Client
http://docs.hazelcast.org/docs/3.4/manual/html-single/hazelcast-documentation.html#java-client
Java Clientは、Hazelcastの機能がほぼ使えるClient Modeで、Serverとの主な違いはClient自身はデータを持たないことにあります。よって、Clientは接続先のServerがひとつもない状態では起動することができません。先に、接続先のServer(というかHazelcastクラスタ)が存在しておく必要があります。
なので、今回はServer側を用意し、それからそのHazelcastクラスタにアクセスするClientを書いていきます。
準備
ちょっとした意図があって、Mavenのマルチプロジェクト構成で用意します。
pom.xml
<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>hazelcast-client-routing</artifactId> <version>0.0.1-SNAPSHOT</version> <modules> <module>hazelcast-client-routing-server</module> <module>hazelcast-client-routing-client</module> </modules> <packaging>pom</packaging> <dependencyManagement> <dependencies> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-client</artifactId> <version>3.4.2</version> </dependency> </dependencies> </dependencyManagement> <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> </properties> </project>
利用するHazelcastは、3.4.2です。このpomを親として、ServerとClientを書いていきます。
Server側
まずはServer側から。pomは、このように。
hazelcast-client-routing-server/pom.xml
<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"> <parent> <artifactId>hazelcast-client-routing</artifactId> <groupId>org.littlewings</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>hazelcast-client-routing-server</artifactId> <packaging>jar</packaging> <dependencies> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast</artifactId> </dependency> </dependencies> </project>
非常に簡単ですが、設定ファイルも用意しました。
hazelcast-client-routing-server/src/main/resources/hazelcast.xml
<?xml version="1.0" encoding="UTF-8"?> <hazelcast xsi:schemaLocation="http://www.hazelcast.com/schema/config hazelcast-config-3.4.xsd" xmlns="http://www.hazelcast.com/schema/config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <group> <name>my-cluster</name> <password>my-password</password> </group> <network> <port auto-increment="true" port-count="100">5701</port> <outbound-ports> <ports>0</ports> </outbound-ports> <join> <multicast enabled="true"> <multicast-group>224.2.2.3</multicast-group> <multicast-port>54327</multicast-port> </multicast> <tcp-ip enabled="false"/> </join> </network> </hazelcast>
クラスタ名とパスワードをちょっと変えた程度です。
Server側のプログラムも、非常に簡単です。
hazelcast-client-routing-server/src/main/java/org/littlewings/hazelcast/Server.java
package org.littlewings.hazelcast; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import com.hazelcast.config.ClasspathXmlConfig; import com.hazelcast.core.Hazelcast; import com.hazelcast.core.HazelcastInstance; public class Server { public static void main(String... args) { HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(new ClasspathXmlConfig("hazelcast.xml")); try { System .console() .readLine("[%s] Hazelcast Server Startup.", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))); } finally { hazelcast.getLifecycleService().shutdown(); Hazelcast.shutdownAll(); } } }
ほぼ何もしていません。プロセスとして浮いていてもらうだけです。
Enterを入力したら、終了するプログラムになっています。
これでおしまい。
Client側
そして、Client側。
hazelcast-client-routing-client/pom.xml
<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"> <parent> <artifactId>hazelcast-client-routing</artifactId> <groupId>org.littlewings</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>hazelcast-client-routing-client</artifactId> <packaging>jar</packaging> <dependencies> <dependency> <groupId>com.hazelcast</groupId> <artifactId>hazelcast-client</artifactId> </dependency> </dependencies> </project>
設定ファイルも用意。
hazelcast-client-routing-client/src/main/resources/hazelcast-client.xml
<?xml version="1.0" encoding="UTF-8"?> <hazelcast-client xsi:schemaLocation="http://www.hazelcast.com/schema/client-config hazelcast-client-config-3.4.xsd" xmlns="http://www.hazelcast.com/schema/client-config" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <group> <name>my-cluster</name> <password>my-password</password> </group> <network> <smart-routing>true</smart-routing> <redo-operation>true</redo-operation> </network> <load-balancer type="random"/> </hazelcast-client>
Server側でクラスタ名とパスワードを変えているので、Client側も合わせて変えておきます。
プログラム。putとgetが、対話形式でできるようにしました。
hazelcast-client-routing-client/src/main/java/org/littlewings/hazelcast/Client.java
package org.littlewings.hazelcast; import java.io.IOException; import java.util.Objects; import java.util.stream.Stream; import com.hazelcast.client.HazelcastClient; import com.hazelcast.client.config.XmlClientConfigBuilder; import com.hazelcast.core.HazelcastInstance; import com.hazelcast.core.IMap; public class Client { public static void main(String... args) throws IOException { HazelcastInstance client = HazelcastClient.newHazelcastClient(new XmlClientConfigBuilder("src/main/resources/hazelcast-client.xml").build()); try { IMap<String, Language> map = client.getMap("language"); Stream .generate(() -> System.console().readLine("Command> ")) .filter(Objects::nonNull) .forEach(line -> { String[] tokens = line.split("\\s+"); String command = tokens[0]; switch (command) { case "put": String key = tokens[1]; Language language = new Language(tokens[2]); map.put(key, language); System.out.printf("putted %s/%s%n", key, language); break; case "get": System.out.printf("get => %s%n", map.get(tokens[1])); break; default: System.out.printf("Unknown command => %s%n", command); break; } }); } finally { client.getLifecycleService().shutdown(); HazelcastClient.shutdownAll(); } } }
HazelcastのDistributed Mapに登録するクラス。こちらは、Client側にしかありません。
hazelcast-client-routing-client/src/main/java/org/littlewings/hazelcast/Language.java
package org.littlewings.hazelcast; import java.io.Serializable; public class Language implements Serializable { private String name; public Language(String name) { this.name = name; } public String getName() { return name; } @Override public String toString() { return name; } }
動作確認
親プロジェクトでコンパイルして
$ mvn compile
まずはServerを起動して、クラスタを構成します。
$ cd hazelcast-client-routing-server ## Node 1 $ mvn exec:java -Dexec.mainClass=org.littlewings.hazelcast.Server ## Node 2 $ mvn exec:java -Dexec.mainClass=org.littlewings.hazelcast.Server
クラスタが構成されます。
Members [2] { Member [192.168.254.129]:5702 this Member [192.168.254.129]:5701 }
そして、Clientを起動。
$ cd ../hazelcast-client-routing-client $ mvn exec:java -Dexec.mainClass=org.littlewinghazelcast.Client
現在のクラスタメンバーが表示されますが、Client自身はこの中のメンバーではありません。
Members [2] { Member [192.168.254.129]:5702 Member [192.168.254.129]:5701 }
いくつかコマンドを実行してみます。
Command> put 1 java putted 1/java Command> put 2 groovy putted 2/groovy Command> put 3 perl putted 3/perl Command> get 2 get => groovy
ここで、いったんClientをCtrl-Cで終了して、再度起動してみます。
Clientが起動したら、getしてみます。
Command> get 1 get => java
Clientが終了しても、データは保存されたままです。ところで、ここまでが普通に動いていることからわかりますが、Server側はClientが保存するデータのClassを認識する必要がないということですね。Server側には、Languageクラスを置いていないので。
Serverを途中で1 Node落としてみる
ここで、Clientのネットワーク設定をちょっと変更します。
<network> <cluster-members> <address>localhost:5701</address> </cluster-members> <smart-routing>true</smart-routing> <redo-operation>true</redo-operation> </network>
クラスタの初期メンバーをlocalhostの5701としました。この設定にすると、最初にlocalhost:7001のメンバーにアクセスしにいきます。
ここで、Server側のNode 1を落とします。
情報: [192.168.254.129]:5701 [my-cluster] [3.4.2] Shutting down node engine... 6 06, 2015 7:56:36 午後 com.hazelcast.instance.NodeExtension 情報: [192.168.254.129]:5701 [my-cluster] [3.4.2] Destroying node NodeExtension. 6 06, 2015 7:56:36 午後 com.hazelcast.instance.Node 情報: [192.168.254.129]:5701 [my-cluster] [3.4.2] Hazelcast Shutdown is completed in 866 ms. 6 06, 2015 7:56:36 午後 com.hazelcast.core.LifecycleService 情報: [192.168.254.129]:5701 [my-cluster] [3.4.2] Address[192.168.254.129]:5701 is SHUTDOWN
Members [1] { Member [192.168.254.129]:5702 this }
だけです。
この状態でClientを起動しようとすると、
$ mvn exec:java -Dexec.mainClass=org.lhazelcast.Client
5701ポートでリッスンしているメンバーがいなくなってしまったので、エラーになります。
6 06, 2015 7:57:39 午後 com.hazelcast.client.spi.impl.ClusterListenerThread 重大: Error while connecting to cluster! java.lang.IllegalStateException: Unable to connect to any address in the config! The following addresses were tried:[/127.0.0.1:5701] at com.hazelcast.client.spi.impl.ClusterListenerThread.connectToOne(ClusterListenerThread.java:286) at com.hazelcast.client.spi.impl.ClusterListenerThread.run(ClusterListenerThread.java:89) Caused by: java.net.ConnectException: 接続を拒否されました at sun.nio.ch.SocketChannelImpl.checkConnect(Native Method) at sun.nio.ch.SocketChannelImpl.finishConnect(SocketChannelImpl.java:717)
ちなみに、Clientのネットワーク設定の初期値は
<cluster-members> <address>localhost</address> </cluster-members>
で、この状態だと5701ポートでリッスンしているNodeがでなくても、5702などの他のポートでリッスンしているメンバーがいれば見つけてくれるようです。
それでは、1度Hazelcastクラスタを起動しなおします。
## Node 1 $ mvn exec:java -Dexec.mainClass=org.littlewings.hazelcast.Server ## Node 2 $ mvn exec:java -Dexec.mainClass=org.littlewings.hazelcast.Server
クラスタが構成されます。
Members [2] { Member [192.168.254.129]:5702 this Member [192.168.254.129]:5701 }
そして、クライアントを起動。
$ mvn exec:java -Dexec.mainClass=org.littlewings.hazelcast.Client
Members [2] { Member [192.168.254.129]:5702 Member [192.168.254.129]:5701 }
適当にデータ登録。
Command> put 1 java putted 1/java Command> put 2 groovy putted 2/groovy Command> put 3 clojure putted 3/clojure Command> put 4 perl putted 4/perl
ここで、Node 1を落とします。
情報: [192.168.254.129]:5701 [my-cluster] [3.4.2] Shutting down node engine... 6 06, 2015 8:07:48 午後 com.hazelcast.instance.NodeExtension 情報: [192.168.254.129]:5701 [my-cluster] [3.4.2] Destroying node NodeExtension. 6 06, 2015 8:07:48 午後 com.hazelcast.instance.Node 情報: [192.168.254.129]:5701 [my-cluster] [3.4.2] Hazelcast Shutdown is completed in 757 ms. 6 06, 2015 8:07:48 午後 com.hazelcast.core.LifecycleService 情報: [192.168.254.129]:5701 [my-cluster] [3.4.2] Address[192.168.254.129]:5701 is SHUTDOWN
残ったのはNode 2だけで、Clientもそれを認識します。
Members [1] { Member [192.168.254.129]:5702 }
このまま、操作を継続することができます。
Command> get 1 get => java Command> get 3 get => clojure
初期メンバーに定義していたNode以外もクラスタメンバーとして認識するし、途中でクラスタメンバーに変化があってもそれを認識してClientも追従してくれるということですね。
まとめ
HazelcastのClientで遊んでみましたが、クラスタにさえ接続できれば、あとでクラスタのNodeに増減があってもそれに追従してくれることがわかりました。また、ClientはServerがいないと接続できないことの再確認や、Server側はClientが保存するデータの型(Class)を知らなくてもよいことが確認できました、と。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/hazelcast-examples/tree/master/hazelcast-client-routing