インメモリ・データグリッドといえば、このブログではInfinispanをよく扱っていますが、以前から気になっていたHazelcastも取り上げてみたいと思います。
Hazelcast
http://www.hazelcast.com/index.jsp
Hazelcastも、Infinispanと同じくオープンソースのインメモリ・データグリッドです。とはいえ、Community EditionとEnterprise Editionがあり、使える機能に差があるようになっています。
ここでは、もちろんCommunity Editionを使用します。
Hazelcastを使うのは初めてなのですが、興味があってドキュメントを眺めていたり、GitHubのソースを見ていたりしているので全く知らないわけでもないです。実際にコードを書くのは初めてだ、ということですね。
Infinispanと比べると、機能的にはちょっと少ない感じでしょうか?代わりに、軽量なのが売りなのかなぁ?Map以外にもListやSet、Queue、Semaphoreなどいろいろデータ構造を提供しているところは面白いです。Topicまであります。とはいえ、以前に性能を計測した時には、InfinispanとEhcacheよりもけっこう遅かった気がしますけど…。
まあ、いいや。まずは普通に始めてみましょう。
Getting Startedをマネる
手始めに、Getting Startedをマネしてプログラムを書いてみましょう。
あ、そうそう、プログラムはClojureで書きます。実行も、lein-execプラグインを使用するので、悪しからず。
で、Getting Started。
Getting Started Tutorial
http://www.hazelcast.com/screencast.jsp
ここで書くのは、Embeddedな形態でもあり、サーバでもある形態なのでしょうかね。Hazelcastのライブラリは、Maven Central Repositoryにあるので、普通にMavenなどで依存関係に追加できます。
getting_started_server.clj
(require '[leiningen.exec :as exec]) (exec/deps '[[com.hazelcast/hazelcast "3.0.2"]]) (ns hazelcast.getting-started-server (:import (com.hazelcast.config Config) (com.hazelcast.core Hazelcast HazelcastInstance) (java.util Map Queue))) (try (let [^Config config (Config.) ^HazelcastInstance instance (Hazelcast/newHazelcastInstance config)] ;; Mapとして使う (let [^Map map-customers (.getMap instance "customers")] (doto map-customers (.put 1 "Joe") (.put 2 "Ali") (.put 3 "Avi")) (println (str "Customer with key 1: " (.get map-customers 1))) (println (str "Map size: " (count map-customers)))) ;; Queueとして使う (let [^Queue queue-customers (.getQueue instance "customers")] (doto queue-customers (.offer "Tom") (.offer "Mary") (.offer "Jane")) (println (str "First customer: " (.poll queue-customers))) (println (str "Second customer: " (.peek queue-customers))) (println (str "Queue size: " (count queue-customers)))) ;; HazelcastInstanceをシャットダウンする (.. instance getLifecycleService shutdown)) ;; 全Hazelcastのインスタンスをシャットダウンする (finally (Hazelcast/shutdownAll)))
最初なので、型パラメータを付与しておきました。
Mavenのアーティファクトを指定しているのは、ここになります。
(exec/deps '[[com.hazelcast/hazelcast "3.0.2"]])
ConfigとHazelcastから、HazelcastInstanceインターフェースを実装したクラスのインスタンスを取得できます。
(let [^Config config (Config.) ^HazelcastInstance instance (Hazelcast/newHazelcastInstance config)]
なんか、不思議なネーミングですね。
あとは、HazelcastInstance#getMapやgetQueueなどで、分散MapやQueueなどのコレクションを取得することができます。
;; Mapとして使う (let [^Map map-customers (.getMap instance "customers")] ;; Queueとして使う (let [^Queue queue-customers (.getQueue instance "customers")]
これらは、Javaのコレクション系のインターフェースを実装しているので、通常のコレクションの感覚で扱うことができます。
シャットダウンもお忘れなく。
;; HazelcastInstanceをシャットダウンする (.. instance getLifecycleService shutdown)) ;; 全Hazelcastのインスタンスをシャットダウンする (finally (Hazelcast/shutdownAll)))
では、実行してみましょう。
$ lein exec getting_started_server.clj 10 06, 2013 1:35:58 午前 com.hazelcast.instance.DefaultAddressPicker 情報: Prefer IPv4 stack is true. 10 06, 2013 1:35:58 午前 com.hazelcast.instance.DefaultAddressPicker 情報: Picked Address[192.168.129.128]:5701, using socket ServerSocket[addr=/0:0:0:0:0:0:0:0,localport=5701], bind any local is true 10 06, 2013 1:35:58 午前 com.hazelcast.system 情報: [192.168.129.128]:5701 [dev] Hazelcast Community Edition 3.0.2 (20130906) starting at Address[192.168.129.128]:5701 10 06, 2013 1:35:58 午前 com.hazelcast.system 情報: [192.168.129.128]:5701 [dev] Copyright (C) 2008-2013 Hazelcast.com 10 06, 2013 1:35:59 午前 com.hazelcast.instance.Node 情報: [192.168.129.128]:5701 [dev] Creating MulticastJoiner 10 06, 2013 1:35:59 午前 com.hazelcast.core.LifecycleService 情報: [192.168.129.128]:5701 [dev] Address[192.168.129.128]:5701 is STARTING 10 06, 2013 1:36:04 午前 com.hazelcast.cluster.MulticastJoiner 情報: [192.168.129.128]:5701 [dev] Members [1] { Member [192.168.129.128]:5701 this } 10 06, 2013 1:36:04 午前 com.hazelcast.core.LifecycleService 情報: [192.168.129.128]:5701 [dev] Address[192.168.129.128]:5701 is STARTED 10 06, 2013 1:36:04 午前 com.hazelcast.partition.PartitionService 情報: [192.168.129.128]:5701 [dev] Initializing cluster partition table first arrangement... Customer with key 1: Joe Map size: 3 First customer: Tom Second customer: Mary Queue size: 2 10 06, 2013 1:36:04 午前 com.hazelcast.core.LifecycleService 情報: [192.168.129.128]:5701 [dev] Address[192.168.129.128]:5701 is SHUTTING_DOWN 10 06, 2013 1:36:04 午前 com.hazelcast.initializer 情報: [192.168.129.128]:5701 [dev] Destroying node initializer. 10 06, 2013 1:36:04 午前 com.hazelcast.instance.Node 情報: [192.168.129.128]:5701 [dev] Hazelcast Shutdown is completed in 86 ms. 10 06, 2013 1:36:04 午前 com.hazelcast.core.LifecycleService 情報: [192.168.129.128]:5701 [dev] Address[192.168.129.128]:5701 is SHUTDOWN
いきなり、こんなクラスタっぽい表示が出ましたね。
10 06, 2013 1:35:59 午前 com.hazelcast.instance.Node 情報: [192.168.129.128]:5701 [dev] Creating MulticastJoiner 10 06, 2013 1:35:59 午前 com.hazelcast.core.LifecycleService 情報: [192.168.129.128]:5701 [dev] Address[192.168.129.128]:5701 is STARTING 10 06, 2013 1:36:04 午前 com.hazelcast.cluster.MulticastJoiner 情報: [192.168.129.128]:5701 [dev] Members [1] { Member [192.168.129.128]:5701 this }
MulticastJoinerとか言ってますし、Node Discoveryをやりそうな雰囲気ですね。
なお、printlnした部分の出力はこうなっています。
Customer with key 1: Joe Map size: 3 First customer: Tom Second customer: Mary Queue size: 2
それにしても、ちょっと重ため?な気がします…。
で、これだと終わってしまうので、shutdownの前にとりあえず無限ループを追加します。
(loop [] (println "Waiting...") (Thread/sleep 3000) (recur)) ;; HazelcastInstanceをシャットダウンする (.. instance getLifecycleService shutdown))
これで実行すると、プログラムが終了しなくなります。で、この間に同じプログラムをもうひとつ起動します。
$ lein exec getting_started_server.clj 10 06, 2013 1:39:14 午前 com.hazelcast.instance.DefaultAddressPicker 情報: Prefer IPv4 stack is true. 10 06, 2013 1:39:14 午前 com.hazelcast.instance.DefaultAddressPicker 情報: Picked Address[192.168.129.128]:5702, using socket ServerSocket[addr=/0:0:0:0:0:0:0:0,localport=5702], bind any local is true 10 06, 2013 1:39:14 午前 com.hazelcast.system 情報: [192.168.129.128]:5702 [dev] Hazelcast Community Edition 3.0.2 (20130906) starting at Address[192.168.129.128]:5702 10 06, 2013 1:39:14 午前 com.hazelcast.system 情報: [192.168.129.128]:5702 [dev] Copyright (C) 2008-2013 Hazelcast.com 10 06, 2013 1:39:14 午前 com.hazelcast.instance.Node 情報: [192.168.129.128]:5702 [dev] Creating MulticastJoiner 10 06, 2013 1:39:14 午前 com.hazelcast.core.LifecycleService 情報: [192.168.129.128]:5702 [dev] Address[192.168.129.128]:5702 is STARTING 10 06, 2013 1:39:15 午前 com.hazelcast.nio.SocketConnector 情報: [192.168.129.128]:5702 [dev] Connecting to /192.168.129.128:5701, timeout: 0, bind-any: true 10 06, 2013 1:39:15 午前 com.hazelcast.nio.TcpIpConnectionManager 情報: [192.168.129.128]:5702 [dev] 49935 accepted socket connection from /192.168.129.128:5701 10 06, 2013 1:39:20 午前 com.hazelcast.cluster.ClusterService 情報: [192.168.129.128]:5702 [dev] Members [2] { Member [192.168.129.128]:5701 Member [192.168.129.128]:5702 this } 10 06, 2013 1:39:22 午前 com.hazelcast.core.LifecycleService 情報: [192.168.129.128]:5702 [dev] Address[192.168.129.128]:5702 is STARTED Customer with key 1: Joe Map size: 3 First customer: Mary Second customer: Jane Queue size: 4
思いっきりクラスタに入りましたね。
Members [2] { Member [192.168.129.128]:5701 Member [192.168.129.128]:5702 this }
また、Queueの数も増えているので、分散Queueを共有していることも確認できます。
Customer with key 1: Joe Map size: 3 First customer: Mary Second customer: Jane Queue size: 4
というか、何も設定してないんですけどね…。
クライアントプログラムを書く
で、ここまででEmbeded Mode的なプログラムを作ったわけですが、この浮いているプログラムをサーバとして、外部からクライアントでアクセスすることができます。
こちらが、そのプログラムです。
getting_started_client.clj
(require '[leiningen.exec :as exec]) (exec/deps '[[com.hazelcast/hazelcast-client "3.0.2"]]) (ns hazelcast.getting-started-client (:import (com.hazelcast.client HazelcastClient) (com.hazelcast.client.config ClientConfig) (com.hazelcast.core HazelcastInstance IMap IQueue))) (try (let [^ClientConfig clientConfig (-> (ClientConfig.) ;; デフォルトのIPアドレス/ポート (.addAddress (into-array ["127.0.0.1:5701"]))) ^HazelcastInstance client (HazelcastClient/newHazelcastClient clientConfig)] ;; Mapを使う (let [^IMap map (.getMap client "customers")] (println (str "Map Size: " (count map))) (println (str "Customer with key 1: " (.get map 1))) (println (str "Customer with key 3: " (.get map 3)))) ;; Queueを使う (let [^IQueue queue (.getQueue client "customers")] (println (str "First customer: " (.poll queue))) (println (str "Queue size: " (count queue)))) ;; Clientをシャットダウンする (.. client getLifecycleService shutdown)) ;; 全Hazelcastのインスタンスをシャットダウンする (finally (HazelcastClient/shutdownAll)))
クライアントを使う場合は、Mavenのアーティファクトはhazelcast-clientになります。
(exec/deps '[[com.hazelcast/hazelcast-client "3.0.2"]])
で、今度はClientConfigとHazelcastClientから、サーバへの接続設定を作成します。
(let [^ClientConfig clientConfig (-> (ClientConfig.) ;; デフォルトのIPアドレス/ポート (.addAddress (into-array ["127.0.0.1:5701"]))) ^HazelcastInstance client (HazelcastClient/newHazelcastClient clientConfig)]
ここでは、チュートリアルに習って「127.0.0.1:5701」(最初のサーバ)を指定しています。HazelcastClient.newHazelcastClientから接続を作成するのですが、結局こちらも使うのはHazelcastInstanceだったりします…。
そして、取得したHazeelcastInstanceを使って、サーバの時と同様getMapやgetQueueで分散Map、Queueを取得します。
;; Mapを使う (let [^IMap map (.getMap client "customers")] ;; Queueを使う (let [^IQueue queue (.getQueue client "customers")]
そういえば、どうしてチュートリアルはサーバ側はJavaのコレクション型で扱っていたのに、クライアント側ではIMapとかIQueueとかにしてるんでしょうね?
こちらもシャットダウンをお忘れなく。
;; Clientをシャットダウンする (.. client getLifecycleService shutdown)) ;; 全Hazelcastのインスタンスをシャットダウンする (finally (HazelcastClient/shutdownAll)))
実行してみます。
$ lein exec getting_started_client.clj 10 06, 2013 1:49:47 午前 com.hazelcast.core.LifecycleService 情報: HazelcastClient[hz.client_0_dev] is STARTING 10 06, 2013 1:49:47 午前 com.hazelcast.core.LifecycleService 情報: HazelcastClient[hz.client_0_dev] is STARTED 10 06, 2013 1:49:47 午前 com.hazelcast.client.spi.ClientClusterService 情報: Members [2] { Member [192.168.129.128]:5701 Member [192.168.129.128]:5702 } Map Size: 3 Customer with key 1: Joe Customer with key 3: Avi First customer: Jane Queue size: 3 10 06, 2013 1:49:47 午前 com.hazelcast.core.LifecycleService 情報: HazelcastClient[hz.client_0_dev] is SHUTTING_DOWN 10 06, 2013 1:49:47 午前 com.hazelcast.client.connection.ClientConnectionManager 情報: Closing connection -> Connection [Address[192.168.129.128]:5701 -> /192.168.129.128:52906] 10 06, 2013 1:49:47 午前 com.hazelcast.client.connection.ClientConnectionManager 情報: Closing connection -> Connection [Address[192.168.129.128]:5702 -> /192.168.129.128:53676] 10 06, 2013 1:49:47 午前 com.hazelcast.core.LifecycleService 情報: HazelcastClient[hz.client_0_dev] is SHUTDOWN
クラスタのメンバーが表示されると共に、サーバ側でMapやQueueに登録した値が取得できました。
導入は簡単ですね。デフォルト設定は全然分かっていませんけど。
これから、ちょこちょこ見ていきたいと思います。