CLOVER🍀

That was when it all began.

HazelcastのReplicated Mapを試してみる - その1

Hazelcast 3.3のリリースから、Replicated Mapというデータ構造が追加されました。

What's New in Hazelcast 3.3 - New Features
http://docs.hazelcast.org/docs/3.3/manual/html-single/hazelcast-documentation.html#new-features

Replicated Map - BETA
http://docs.hazelcast.org/docs/3.3/manual/html-single/hazelcast-documentation.html#replicated-map-beta

まだベータ版的な扱い?

Hazelcastといえば、MapやList、Queueといった多様なデータ構造を提供していますが、すべて分散(Distributed)なもので、各Nodeに分散してデータを持ちます。よって、バックアップ数以上のNodeが同時にダウンすると、データが失われます。

で、今回のReplicated Mapは、どのNodeも同じデータを持つようなMapになります。よって、1番メモリの少ないNodeに保持可能なデータ量が合わせられる形になるでしょうね。

このReplicated Mapですが、他のインメモリ・データグリッドのレプリケーションを行うものと比べて、ちょっと性格が異なるようです。

HazelcastのReplicated Mapの特徴は、

  • 弱い一貫性
  • 全Nodeが同じデータを持ち、メモリ消費量は大きいが読み取りは高速
  • java.util.Mapインターフェースを実装しているが、ConcurrentMapではない(アトミックな読み書きはできない)
  • 書き込み順は保証していない。2つのNodeから同時に書き込まれた場合は、Vector Clockアルゴリズムによりひとつの値を使用するように決定する
  • Map#clearは非同期に実行される

結果、HTMLや商品データのような、あまり変わらないデータが格納されることを想定しているそうな。

使ってみる

まあ、概要はこれくらいにして、まずは使ってみましょう。

Maven依存関係は、このように定義。

    <dependency>
      <groupId>com.hazelcast</groupId>
      <artifactId>hazelcast</artifactId>
      <version>3.3.1</version>
    </dependency>

ごく単純なサンプルを書いてみます。
src/main/java/org/littlewings/hazelcast/replicatedmap/ReplicatedMapExample.java

package org.littlewings.hazelcast.replicatedmap;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import com.hazelcast.config.Config;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;

public class ReplicatedMapExample {
    public static void main(String[] args) {
        Config config = new Config();
        HazelcastInstance hazelcast =
            Hazelcast.newHazelcastInstance(config);

        Map<String, String> map = hazelcast.getReplicatedMap("default");
        // Map<String, String> map = hazelcast.getMap("default");

        int entrySize = 20;

        IntStream
            .rangeClosed(1, entrySize)
            .forEach(i -> map.put("key" + i, "value" + i));

        System.out.printf("[%s] Hazelcast Node, startup, putted [%d]entries.%n",
                          LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), entrySize);
        System.console().readLine("Enter, shutdown...");

        IntStream
            .rangeClosed(1, entrySize)
            .forEach(i -> {
                String key = "key" + i;
                String value = map.get(key);
                System.out.printf("Key = [%s], Value = [%s]%n", key, value);
            });

        hazelcast.getLifecycleService();
        Hazelcast.shutdownAll();
    }
}

HazelcastInstance#getReplicatedMapで、Replicated Mapを取得することができます。

        Map<String, String> map = hazelcast.getReplicatedMap("default");

今回はMapインターフェースで受けていますが、com.hazelcast.core.ReplicatedMapインターフェースで受けてもOKです。

このプログラム、20個エントリを放り込んで、Enterを打たれるまで待機します。

        int entrySize = 20;

        IntStream
            .rangeClosed(1, entrySize)
            .forEach(i -> map.put("key" + i, "value" + i));

        System.out.printf("[%s] Hazelcast Node, startup, putted [%d]entries.%n",
                          LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), entrySize);
        System.console().readLine("Enter, shutdown...");

その後、データをダンプしてシャットダウンします。

        IntStream
            .rangeClosed(1, entrySize)
            .forEach(i -> {
                String key = "key" + i;
                String value = map.get(key);
                System.out.printf("Key = [%s], Value = [%s]%n", key, value);
            });

        hazelcast.getLifecycleService();
        Hazelcast.shutdownAll();

これを使って、単一のNodeがすべてのデータを持っていることを確認してみましょう。

このプログラムを4つ起動して、Hazelcast 4Nodeのクラスタを構成してみます。

# Node1
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.hazelcast.replicatedmap.ReplicatedMapExample

# Node2
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.hazelcast.replicatedmap.ReplicatedMapExample

# Node3
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.hazelcast.replicatedmap.ReplicatedMapExample

# Node4
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.hazelcast.replicatedmap.ReplicatedMapExample

起動すると、各Nodeがこんなところで止まります。

[2014-10-12 16:21:35] Hazelcast Node, startup, putted [20]entries.
Enter, shutdown...

全Nodeが起動し終わると、最初に起動したNodeのコンソールにはこんな表示が出ていると思います。

Members [4] {
	Member [192.168.129.129]:5701 this
	Member [192.168.129.129]:5702
	Member [192.168.129.129]:5703
	Member [192.168.129.129]:5704
}

ここから、各Nodeを落としてひとつだけ残します。

作ったプログラムの関係上、Enterを押していってもシャットダウンしていくのですが、それだとクラスタ再構成の猶予ができるので、ここは一気にkillします。

$ kill [Node2のPID] [Node3のPID] [Node4のPID]

Node1は、こういう状態になります。

Members [1] {
	Member [192.168.129.129]:5701 this
}

あとは、Enterを押してシャットダウン。

Key = [key1], Value = [value1]
Key = [key2], Value = [value2]
Key = [key3], Value = [value3]
Key = [key4], Value = [value4]
Key = [key5], Value = [value5]
Key = [key6], Value = [value6]
Key = [key7], Value = [value7]
Key = [key8], Value = [value8]
Key = [key9], Value = [value9]
Key = [key10], Value = [value10]
Key = [key11], Value = [value11]
Key = [key12], Value = [value12]
Key = [key13], Value = [value13]
Key = [key14], Value = [value14]
Key = [key15], Value = [value15]
Key = [key16], Value = [value16]
Key = [key17], Value = [value17]
Key = [key18], Value = [value18]
Key = [key19], Value = [value19]
Key = [key20], Value = [value20]

ダンプした値に、すべてのエントリが格納されていることが確認できます。

次に、HazelcastInstance#getReplicatedMapをHazelcastInstance#getMapに書き換え、Replicated MapからDistributed Mapに替えて同じことをしてみます。

        // Map<String, String> map = hazelcast.getReplicatedMap("default");
        Map<String, String> map = hazelcast.getMap("default");

各Nodeを起動して、同じようにkillします。

$ kill [Node2のPID] [Node3のPID] [Node4のPID]

残ったNodeで、Enterを押します。

Key = [key1], Value = [null]
Key = [key2], Value = [null]
Key = [key3], Value = [value3]
Key = [key4], Value = [null]
Key = [key5], Value = [null]
Key = [key6], Value = [value6]
Key = [key7], Value = [value7]
Key = [key8], Value = [value8]
Key = [key9], Value = [value9]
Key = [key10], Value = [null]
Key = [key11], Value = [value11]
Key = [key12], Value = [value12]
Key = [key13], Value = [null]
Key = [key14], Value = [null]
Key = [key15], Value = [null]
Key = [key16], Value = [null]
Key = [key17], Value = [value17]
Key = [key18], Value = [null]
Key = [key19], Value = [value19]
Key = [key20], Value = [null]

こちらでは、エントリが欠けていますね。

Replicated Mapを使うと、各Nodeが全データを持つことが確認できたと思います。次は、設定などについて見ていこうと思います。

続きは、こちら。

HazelcastのReplicated Mapを試してみる - その2
http://d.hatena.ne.jp/Kazuhira/20141012/1413105032

作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/hazelcast-examples/tree/master/hazelcast-replicated-map