CLOVER🍀

That was when it all began.

HazelcastとClojureで始める、インメモリ・データグリッド

インメモリ・データグリッドといえば、このブログでは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に登録した値が取得できました。

導入は簡単ですね。デフォルト設定は全然分かっていませんけど。

これから、ちょこちょこ見ていきたいと思います。