CLOVER🍀

That was when it all began.

Apache GeodeのPeer-to-Peer構成でクラスタを組んでみる

Apache Geodeで、はじめてのクラスタリングにチャレンジ。

ドキュメントに沿っていくと、Client/Server構成が基本路線かとも思いましたが、いろいろあってPeer-to-Peerでやることにしました。

今回のとっかかりとして、参考にしたのはこのあたりのドキュメントです。

http://geode.docs.pivotal.io/docs/topologies_and_comm/topology_concepts/how_member_discovery_works.html

http://geode.docs.pivotal.io/docs/topologies_and_comm/p2p_configuration/setting_up_a_p2p_system.html

http://geode.docs.pivotal.io/docs/configuring/running/running_the_locator.html

http://geode.docs.pivotal.io/docs/reference/topics/gemfire_properties.html

とりあえず、各NodeにLocatorがあればなんとかクラスタ構成できそうだったので、その線で進めてみました。

今回のサンプルでとる構成は、簡単なCLIツールと、単に浮いているだけのサーバーの2種類とします。クラスタ構成後、CLIツールでちょっと動きを確認してみる、そんな感じでやります。

準備

まずはMaven依存関係から。使ったApache Geodeのバージョンは、1.0.0-incubating.M1です。

        <dependency>
            <groupId>org.apache.geode</groupId>
            <artifactId>gemfire-core</artifactId>
            <version>1.0.0-incubating.M1</version>
        </dependency>

Geodeの設定ファイルは、次の2つを用意。

Locatorとして、ポート10335、10336をListenしているものを初期メンバーとして想定。
src/main/resources/gemfire.properties

locators=localhost[10335],localhost[10336]

Nodeは今回3つ用意しますが、もうひとつは初期メンバーに対して動的に追加する感じで。

Cacheの設定は、PARTITIONとREPLICATEでひとつずつ。
src/main/resources/cache.xml

<?xml version="1.0" encoding="UTF-8"?>
<cache
        xmlns="http://schema.pivotal.io/gemfire/cache"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://schema.pivotal.io/gemfire/cache http://schema.pivotal.io/gemfire/cache/cache-8.1.xsd"
        version="8.1">
    <region name="partitionRegion" refid="PARTITION">
        <region-attributes>
            <partition-attributes redundant-copies="1"/>
        </region-attributes>
    </region>

    <region name="replicateRegion" refid="REPLICATE"/>
</cache>

PARTITIONの方は、バックアップをひとつ取るようにしました。
※もしくは、後述のPARTITION_REDUNDANTを使うとよいかも

gemfire.properties、cache.xmlはGeodeのデフォルトの設定ファイルの名前なので、特に指定せずとも読み込んでくれます。

この設定で、確認用のプログラムを書いてみます。

動作確認用のプログラム

単純に、Nodeとして浮いていてもらうようのプログラム。
src/main/java/org/littlewings/geode/Server.java

package org.littlewings.geode;

import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheFactory;

public class Server {
    public static void main(String... args) {
        Cache cache =
                new CacheFactory()
                        .create();

        System.console().readLine("> Enter, stop server.");

        cache.close();
    }
}

Geodeを起動して、Enterを押したら終了する簡単なものです。

続いて、CLIツール。
src/main/java/org/littlewings/geode/Cli.java

package org.littlewings.geode;

import java.io.Console;
import java.util.stream.IntStream;

import com.gemstone.gemfire.cache.Cache;
import com.gemstone.gemfire.cache.CacheFactory;
import com.gemstone.gemfire.cache.Region;
import com.gemstone.gemfire.distributed.DistributedSystem;

public class Cli {
    public static void main(String... args) {
        Cache cache =
                new CacheFactory()
                        .create();

        try {
            Console console = System.console();

            boolean cliContinue = true;

            while (cliContinue) {
                String line = console.readLine("> ");

                if (line == null || line.trim().isEmpty()) {
                    continue;
                }

                try {
                    Region<String, String> region;
                    String[] tokens = line.split("\\s+");

                    switch (tokens[0]) {
                        case "put":
                            region = cache.getRegion(tokens[1]);
                            region.put(tokens[2], tokens[3]);
                            System.out.printf("Region[%s] putted, key = %s, value = %s.%n", region.getName(), tokens[2], tokens[3]);
                            break;
                        case "get":
                            region = cache.getRegion(tokens[1]);
                            System.out.printf("Region[%s] get, key = %s, value = %s.%n", region.getName(), tokens[2], region.get(tokens[2]));
                            break;
                        case "members":
                            if (tokens.length == 1) {
                                DistributedSystem ds = cache.getDistributedSystem();
                                System.out.printf("Self = %s.%n", ds.getDistributedMember());
                                System.out.printf("Other Members = %s.%n", ds.getAllOtherMembers());
                                System.out.printf("Other Members, alias = %s.%n", cache.getMembers());
                            } else {
                                region = cache.getRegion(tokens[1]);
                                System.out.printf("Region[%s] Other Members = %s.%n", region.getName(), cache.getMembers(region));
                            }
                            break;
                        case "range-put":
                            region = cache.getRegion(tokens[1]);
                            int start = Integer.parseInt(tokens[2]);
                            int end = Integer.parseInt(tokens[3]);
                            IntStream.rangeClosed(start , end).forEach(i -> {
                                String key = "key" + i;
                                String value = "value" + i;
                                region.put(key, value);
                                System.out.printf("Region[%s] putted, key = %s, value = %s.%n", region.getName(), key, value);
                            });
                            break;
                        case "get-all":
                            region = cache.getRegion(tokens[1]);

                            region
                                    .entrySet()
                                    .stream()
                                    .forEach(entry -> System.out.printf("Region[%s] key = %s, value = %s%n", region.getName(), entry.getKey(), entry.getValue()));
                            break;
                        case "exit":
                            cliContinue = false;
                            break;
                        default:
                            System.out.printf("Unknown Command[%s].%n", line);
                            break;
                    }
                } catch (Exception e) {
                    System.out.printf("Unknown Command, or Parse Error, [%s].%n", line);
                }
            }
        } finally {
            cache.close();
        }
    }
}

put、get、範囲を指定して一括put(単純なルールでのKey/Value)、エントリすべてのget、クラスタに参加しているメンバーの出力、といったあたりのコマンドを用意。

確認

それでは、作ったプログラムで動作確認してみます。

今回は、Serverを2 Node、加えてCLIの計3 Nodeとします。

Serverを2 Node起動。

## Node 1
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.geode.Server -Dgemfire.start-locator=localhost[10335]

## Node 2
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.geode.Server -Dgemfire.start-locator=localhost[10336]

システムプロパティに「gemfire.」接頭辞を付与することで、gemfire.propertiesで設定できる内容を外部から指定できるようです。

また、ちゃんとNodeが起動しきってから、次のNodeを起動するのがよいみたいです…。

今回は、Locatorを開始するのと、ポートを被らせないようにしています。また、Locatorのデフォルトポートは10334ですが、クラスタが組めていることを確認するために、ずらしておきました。

続いて、CLIツールを起動。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.geode.Cli -Dgemfire.start-locator=localhost[10337]

こちらのLocatorは、10337ポートを利用。

起動時に、3 Nodeいるっぽいログが出力されます。

[info 2016/02/24 20:53:47.104 JST <unicast receiver,xxxxx-9503> tid=0x1b] Peer locator received new membership view: View[192.168.254.129(34250)<ec><v0>:1024|2] members: [192.168.254.129(34250)<ec><v0>:1024{lead}, 192.168.254.129(34318)<ec><v1>:1025, 192.168.254.129(34407)<ec><v2>:1026]

参加メンバー確認。

> members
Self = 192.168.254.129(34407)<ec><v2>:1026.
Other Members = [192.168.254.129(34250)<ec><v0>:1024, 192.168.254.129(34318)<ec><v1>:1025].
Other Members, alias = [192.168.254.129(34250)<ec><v0>:1024, 192.168.254.129(34318)<ec><v1>:1025].

自分以外に、2 Nodeいます。

Regionを指定してput。

> put partitionRegion key1 value1
Region[partitionRegion] putted, key = key1, value = value1.

get。

> get partitionRegion key1
Region[partitionRegion] get, key = key1, value = value1.
> get partitionRegion key2
[info 2016/02/24 20:55:12.005 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initializing region _B__partitionRegion_86

[info 2016/02/24 20:55:12.008 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initialization of region _B__partitionRegion_86 completed

Region[partitionRegion] get, key = key2, value = null.

2つめは、エントリを登録していないのでnull。

一括登録。

> range-put partitionRegion 1 10  
Region[partitionRegion] putted, key = key1, value = value1.
Region[partitionRegion] putted, key = key2, value = value2.
Region[partitionRegion] putted, key = key3, value = value3.
[info 2016/02/24 20:55:24.957 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initializing region _B__partitionRegion_88

[info 2016/02/24 20:55:24.959 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initialization of region _B__partitionRegion_88 completed

Region[partitionRegion] putted, key = key4, value = value4.
[info 2016/02/24 20:55:25.008 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initializing region _B__partitionRegion_89

[info 2016/02/24 20:55:25.015 JST <org.littlewings.geode.Cli.main()> tid=0xb] Region _B__partitionRegion_89 requesting initial image from 192.168.254.129(34250)<ec><v0>:1024

[info 2016/02/24 20:55:25.017 JST <org.littlewings.geode.Cli.main()> tid=0xb] _B__partitionRegion_89 is done getting image from 192.168.254.129(34250)<ec><v0>:1024. isDeltaGII is false

[info 2016/02/24 20:55:25.018 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initialization of region _B__partitionRegion_89 completed

Region[partitionRegion] putted, key = key5, value = value5.
[info 2016/02/24 20:55:25.049 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initializing region _B__partitionRegion_90

[info 2016/02/24 20:55:25.056 JST <org.littlewings.geode.Cli.main()> tid=0xb] Region _B__partitionRegion_90 requesting initial image from 192.168.254.129(34318)<ec><v1>:1025

[info 2016/02/24 20:55:25.058 JST <org.littlewings.geode.Cli.main()> tid=0xb] _B__partitionRegion_90 is done getting image from 192.168.254.129(34318)<ec><v1>:1025. isDeltaGII is false

[info 2016/02/24 20:55:25.058 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initialization of region _B__partitionRegion_90 completed

Region[partitionRegion] putted, key = key6, value = value6.
[info 2016/02/24 20:55:25.078 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initializing region _B__partitionRegion_91

[info 2016/02/24 20:55:25.080 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initialization of region _B__partitionRegion_91 completed

Region[partitionRegion] putted, key = key7, value = value7.
Region[partitionRegion] putted, key = key8, value = value8.
[info 2016/02/24 20:55:25.156 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initializing region _B__partitionRegion_93

[info 2016/02/24 20:55:25.161 JST <org.littlewings.geode.Cli.main()> tid=0xb] Region _B__partitionRegion_93 requesting initial image from 192.168.254.129(34318)<ec><v1>:1025

[info 2016/02/24 20:55:25.164 JST <org.littlewings.geode.Cli.main()> tid=0xb] _B__partitionRegion_93 is done getting image from 192.168.254.129(34318)<ec><v1>:1025. isDeltaGII is false

[info 2016/02/24 20:55:25.165 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initialization of region _B__partitionRegion_93 completed

Region[partitionRegion] putted, key = key9, value = value9.
[info 2016/02/24 20:55:25.178 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initializing region _B__partitionRegion_84

[info 2016/02/24 20:55:25.183 JST <org.littlewings.geode.Cli.main()> tid=0xb] Initialization of region _B__partitionRegion_84 completed

Region[partitionRegion] putted, key = key10, value = value10.

なんか、ちらちらとログが見えますね…。

全部取得。

> get-all partitionRegion
Region[partitionRegion] key = key10, value = value10
Region[partitionRegion] key = key1, value = value1
Region[partitionRegion] key = key2, value = value2
Region[partitionRegion] key = key3, value = value3
Region[partitionRegion] key = key4, value = value4
Region[partitionRegion] key = key5, value = value5
Region[partitionRegion] key = key6, value = value6
Region[partitionRegion] key = key7, value = value7
Region[partitionRegion] key = key8, value = value8
Region[partitionRegion] key = key9, value = value9

こちらもログが出るんですけど、端折りました。

1 Node落としてみる

ここで、Server Nodeをひとつ落としてみます。

[info 2016/02/24 20:56:01.008 JST <org.littlewings.geode.Server.main()> tid=0xb] GemFireCache[id = 1690853966; isClosing = true; isShutDownAll = false; created = Wed Feb 24 20:53:13 JST 2016; server = false; copyOnRead = false; lockLease = 120; lockTimeout = 60]: Now closing.

[info 2016/02/24 20:56:01.138 JST <org.littlewings.geode.Server.main()> tid=0xb] Shutting down DistributionManager 192.168.254.129(34250)<ec><v0>:1024. 

[info 2016/02/24 20:56:01.241 JST <org.littlewings.geode.Server.main()> tid=0xb] Now closing distribution for 192.168.254.129(34250)<ec><v0>:1024

[info 2016/02/24 20:56:01.242 JST <org.littlewings.geode.Server.main()> tid=0xb] Stopping membership services

[info 2016/02/24 20:56:02.244 JST <org.littlewings.geode.Server.main()> tid=0xb] GMSHealthMonitor server socket is closed in stopServices().

[info 2016/02/24 20:56:02.245 JST <Geode Failure Detection Server thread 0> tid=0x1f] GMSHealthMonitor server thread exiting

[info 2016/02/24 20:56:02.245 JST <org.littlewings.geode.Server.main()> tid=0xb] GMSHealthMonitor serverSocketExecutor is terminated

[info 2016/02/24 20:56:02.255 JST <org.littlewings.geode.Server.main()> tid=0xb] DistributionManager stopped in 1,117ms.

[info 2016/02/24 20:56:02.255 JST <org.littlewings.geode.Server.main()> tid=0xb] Marking DistributionManager 192.168.254.129(34250)<ec><v0>:1024 as closed.

[info 2016/02/24 20:56:02.256 JST <org.littlewings.geode.Server.main()> tid=0xb] Stopping Distribution Locator on localhost/127.0.0.1[10335]

[info 2016/02/24 20:56:02.257 JST <Distribution Locator on localhost/127.0.0.1[10335]> tid=0x10] locator shutting down

[info 2016/02/24 20:56:02.259 JST <org.littlewings.geode.Server.main()> tid=0xb] Distribution Locator on localhost/127.0.0.1[10335]  is stopped

参加メンバー確認。

> members
Self = 192.168.254.129(34407)<ec><v2>:1026.
Other Members = [192.168.254.129(34318)<ec><v1>:1025].
Other Members, alias = [192.168.254.129(34318)<ec><v1>:1025].

1 Node減りましたね。

データ確認。

> get-all partitionRegion
Region[partitionRegion] key = key10, value = value10
Region[partitionRegion] key = key1, value = value1
Region[partitionRegion] key = key2, value = value2
Region[partitionRegion] key = key3, value = value3
Region[partitionRegion] key = key4, value = value4
Region[partitionRegion] key = key5, value = value5
Region[partitionRegion] key = key6, value = value6
Region[partitionRegion] key = key7, value = value7
Region[partitionRegion] key = key8, value = value8
Region[partitionRegion] key = key9, value = value9

ちゃんと、全部残っていますね。

PARTITIONで設定していたredundant-copiesはデフォルト値が0なので、そのままだとNodeダウン時にデータが欠損します。
redundant-copiesを1以上にするか、もしくはRegion ShortcutsでPARTITIONではなくPARTITION_REDUNDANTを指定するとよいでしょう。
http://geode.docs.pivotal.io/docs/reference/topics/region_shortcuts_table.html

ここで、再度Node起動。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.geode.Server -Dgemfire.start-locator=localhost[10335]

Nodeが3つに戻りました。

> members
Self = 192.168.254.129(34407)<ec><v2>:1026.
Other Members = [192.168.254.129(34585)<ec><v13>:1024, 192.168.254.129(34318)<ec><v1>:1025].
Other Members, alias = [192.168.254.129(34585)<ec><v13>:1024, 192.168.254.129(34318)<ec><v1>:1025].

PARTITIONとREPLICATEの違いを見る

今度は、PARTITIONとREPLICATEの違いを見てみましょう。

それぞれのRegionに、データを登録します。

PARTITION。

> range-put partitionRegion 1 10
Region[partitionRegion] putted, key = key1, value = value1.
Region[partitionRegion] putted, key = key2, value = value2.
Region[partitionRegion] putted, key = key3, value = value3.
Region[partitionRegion] putted, key = key4, value = value4.
Region[partitionRegion] putted, key = key5, value = value5.
Region[partitionRegion] putted, key = key6, value = value6.
Region[partitionRegion] putted, key = key7, value = value7.
Region[partitionRegion] putted, key = key8, value = value8.
Region[partitionRegion] putted, key = key9, value = value9.
Region[partitionRegion] putted, key = key10, value = value10.

REPLICATE。

> range-put replicateRegion 1 10
Region[replicateRegion] putted, key = key1, value = value1.
Region[replicateRegion] putted, key = key2, value = value2.
Region[replicateRegion] putted, key = key3, value = value3.
Region[replicateRegion] putted, key = key4, value = value4.
Region[replicateRegion] putted, key = key5, value = value5.
Region[replicateRegion] putted, key = key6, value = value6.
Region[replicateRegion] putted, key = key7, value = value7.
Region[replicateRegion] putted, key = key8, value = value8.
Region[replicateRegion] putted, key = key9, value = value9.
Region[replicateRegion] putted, key = key10, value = value10.

ここで、Server Nodeを2つ同時にkillします。

$ kill 34585 34318

CLI側に残るのは、自分のみです。

> members
Self = 192.168.254.129(34407)<ec><v2>:1026.
Other Members = [].
Other Members, alias = [].

データの状態を見てみましょう。

PARTITION。

> get-all partitionRegion
Region[partitionRegion] key = key10, value = value10
Region[partitionRegion] key = key2, value = value2
Region[partitionRegion] key = key4, value = value4
Region[partitionRegion] key = key5, value = value5
Region[partitionRegion] key = key6, value = value6
Region[partitionRegion] key = key7, value = value7
Region[partitionRegion] key = key9, value = value9

こちらは、1 Nodeにバックアップを用意しているだけなので、2 Node同時に落とすと微妙に欠損します。

REPLICATE。

> get-all replicateRegion
Region[replicateRegion] key = key9, value = value9
Region[replicateRegion] key = key10, value = value10
Region[replicateRegion] key = key8, value = value8
Region[replicateRegion] key = key6, value = value6
Region[replicateRegion] key = key7, value = value7
Region[replicateRegion] key = key2, value = value2
Region[replicateRegion] key = key4, value = value4
Region[replicateRegion] key = key1, value = value1
Region[replicateRegion] key = key5, value = value5
Region[replicateRegion] key = key3, value = value3

こちらは、全データを全Nodeにコピーするタイプなので、データが残ったままですね。

まとめ

はじめてApache Geodeでクラスタを組んでみましたが、Nodeがクラスタに参加するところで若干てこずった感じです。

Node Discoveryについて、もうちょっと見た方がいいかな?

あと、Locatorはプログラム内で開始する方法も覚えたいなと思います。
http://geode.docs.pivotal.io/docs/configuring/running/running_the_locator.html

とりあえず、クラスタさえ構成できれば少しはなんとかなりそうです…。