インメモリデータグリッド Apache IgniteをJavaのクライアントからアクセスする

# はじめに

インメモリデータグリッドを勉強しようと、Apache Igniteを始めてみました。
まずは、インストールして1サーバ/2サーバで起動し、Javaプログラムからキャッシュサーバとして利用するところまでやってみます。

以下にApache Igniteに関するドキュメントへのリンク集も作成しましたので、参考にしていただければ幸いです。

- [Apache Igniteに関するドキュメントへのリンク集](https://qiita.com/mkyz08/items/0064c7ac102a0f62735a)

# Apache Igniteとは

Apache Igniteは、インメモリデータグリッドを実現するミドルウェアです。
インメモリデータグリッドとは、大量のデータを複数のサーバ上のメモリで分散化する技術です。インメモリでデータを扱うことで高速化できることが大きな特徴です。
利用用途としては、大量データを格納したデータベースの分散キャッシュがあげられます。

Apache Igniteではインメモリデータグリッドを中心として、以下の役割も担うことができます。

* インメモリデータベース/分散データベース

　インメモリデータグリッドをデータベースとして扱います。Read/Writeは分散化され、スケールアウトが容易で、OracleなどのRDBより高速なことが特徴になります。現在のバージョンではトランザクションが扱えませんが、将来のバージョンではサポートされるらしいです。
　また、メモリにデータを保持しますがファイルシステム（HDFS）に永続化することも可能です。メモリにもHDFSと同様の構造で保存されており、HDFS上のファイルのサブセットとしての位置づけになります。
　
* KVS

　KVSのための簡単なAPIが準備されており、分散KVSとして利用できます。

* データベースキャッシング

　OracleやCassandraなどのデータベース/NoSQLをキャッシングとして利用できます。

* etc

詳しくは、[Apache Igniteのユースケース](https://ignite.apache.org/usecases.html)を参照してみてください。

また、色々な情報のリンク集を以下に作成しているのでご参考まで。

[Apache Igniteに関するドキュメントへのリンク集](https://qiita.com/mkyz08/items/0064c7ac102a0f62735a)


# 実行環境

Vagrantで、CentOS 7の環境を準備してApache Igniteを動かしました。CentOS 7の構築までの手順はわかりやすいサイトが他にあるので省略しています。

シングル構成とクラスタ構成（2台）を試してみます。サーバは以下の2台です。

・192.168.20.71 igniteserver1
・192.168.20.72 igniteserver2


# OpenJDKのインストール

Apache IgniteではJDKが必要で、Oracle JDKとOpen JDKに対応しています。
今回はOpen JDKを利用します。

```terminal
# yum -y install java-1.8.0-openjdk-devel

# java -version
openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)
```

JAVA_HOMEを設定し、PATHにJavaのパスを追加しています。

```terminal
# echo "export JAVA_HOME=$(readlink -e $(which java)|sed 's:/bin/java::')" > /etc/profile.d/java.sh
# echo "PATH=\$PATH:\$JAVA_HOME/bin" >> /etc/profile.d/java.sh
# source /etc/profile
```

# Apache Igniteのインストール

[Apache Igniteのサイト](https://ignite.apache.org/)からモジュールをダウンロードします。以下の「Binary Releases」を選択します。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/179607/93d3bf48-3ff0-1028-6d35-34b032f15c00.png)

今回は最新版である、以下のv2.6.0をダウンロードしました。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/179607/71c4daf6-e33a-1107-da59-33797fb1a684.png)

先ほどダウンロードした「apache-ignite-fabric-2.6.0-bin.zip」をサーバにアップロードし、以下のコマンドで「/opt」以下に展開します。

```
# cd /opt
# unzip /tmp/apache-ignite-fabric-2.6.0-bin.zip
# ln -s /opt/apache-ignite-fabric-2.6.0-bin/ /opt/apache-ignite
```

Apache Igniteを起動するため、以下のコマンドを実行します。

```
cd /opt/apache-ignite/bin
./ignite.sh
```
引数なしだと、「/opt/apache-ignite/config/default-config.xml」のデフォルトの設定ファイルが読み込まれて起動します。

起動に成功すると以下のようにコンソールへ出力されます。servers=1となって、サーバが1台起動していることが確認できます。

```
[22:42:07] Ignite node started OK (id=9ae050e2)
[22:42:07] Topology snapshot [ver=1, servers=1, clients=0, CPUs=1, offheap=0.74GB, heap=1.0GB]
```

デフォルトの設定ファイルは以下のようになっています。
「\<bean id="grid.cfg"」要素以下に必要な設定を書いていくのですが、デフォルトで空の設定になっています。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
        Alter configuration below as needed.
    -->
    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration"/>
</beans>
```

# JavaからApache Igniteのキャッシュにアクセス（1台構成）

先ほど起動したApache Igniteを、Javaアプリケーションからキャッシュサーバとして利用してみます。
JavaアプリケーションはMavenプロジェクトを利用し、pom.xmlに以下の依存関係を追加します。

```xml
		<dependency>
			<groupId>org.apache.ignite</groupId>
			<artifactId>ignite-core</artifactId>
			<version>2.6.0</version>
		</dependency>
```

Javaクライアントは、Apache Igniteのサンプルプログラムを参考に以下のように作成しました。
Apache Ignite上の「"put-get-example"」をいうキャッシュに対して、データをputし、そのまま同じデータをgetして表示するプログラムになっています。

```SimpleCacheApp.java
import org.apache.ignite.Ignition;
import org.apache.ignite.client.ClientCache;
import org.apache.ignite.client.ClientException;
import org.apache.ignite.client.IgniteClient;
import org.apache.ignite.configuration.ClientConfiguration;

public class SimpleCacheApp
{
	public static void main(String[] args) {
		// 接続先のApache IgniteサーバのIPとポートを指定する。
	    ClientConfiguration cfg = new ClientConfiguration().setAddresses("192.168.20.71:10800");

	    try (IgniteClient igniteClient = Ignition.startClient(cfg)) {
	        final String CACHE_NAME = "put-get-example";

	        ClientCache<Integer, String> cache = igniteClient.getOrCreateCache(CACHE_NAME);

	        Integer key = 1;
	        String val = "put-get-test";

	        cache.put(key, val);

	        System.out.format("PUT [%s]\n", val);

	        String cachedVal = cache.get(key);

	        System.out.format("GET [%s]\n", cachedVal);
	    }
	    catch (ClientException e) {
	        e.printStackTrace();
	    }
	    catch (Exception e) {
	    	e.printStackTrace();
	    }
	}
}

```

実行結果は以下のとおりです。
「put-get-test」というデータをputし、その値をキャッシュからgetできていることが確認できます。

```
PUT [put-get-test]
GET [put-get-test]
```

サーバ側でキャッシュされていることを確認するために、Igniteのコマンドラインインターフェースである「Ignite Visor」を利用してみます。

```terminal
まず、Visorを起動する。
# /opt/apache-ignite/bin/ignitevisorcmd.sh

Visorのコンソールが表示されるので、「open」コマンドでクラスタに接続する。
visor> open
Local configuration files:
+==========================================================================================+
| #  |                                 Configuration File                                  |
+==========================================================================================+
| 0  | config/default-config.xml                                                           |
～～省略～～
設定ファイル一覧が表示されるので、現在使用しているデフォルトの「0 : config/default-config.xml」を選択します。
Choose configuration file number ('c' to cancel) [0]: 0

ここで「cache」コマンドを実行すると、キャッシュの状況が表示されます。

visor> cache
Time of the snapshot: 2018-08-18 03:18:21
+========================================================================================================================+
|       Name(@)        |    Mode     | Nodes | Entries (Heap / Off-heap) |   Hits    |  Misses   |   Reads   |  Writes   |
+========================================================================================================================+
| put-get-example(@c0) | PARTITIONED | 1     | min: 1 (0 / 1)            | min: 0    | min: 0    | min: 0    | min: 0    |
|                      |             |       | avg: 1.00 (0.00 / 1.00)   | avg: 0.00 | avg: 0.00 | avg: 0.00 | avg: 0.00 |
|                      |             |       | max: 1 (0 / 1)            | max: 0    | max: 0    | max: 0    | max: 0    |
+------------------------------------------------------------------------------------------------------------------------+
```

「put-get-example」のキャッシュに1つのキャッシュ（Entries参照）が格納されていることが分かります。

# Apache Igniteをクラスタ構成（2台）にする。

先ほどはサーバ1台構成でしたが、2台構成で設定してみます。
2台構成用の設定ファイル（default-config-cluster2server.xml）を作成します。

```
# cp -p /opt/apache-ignite/config/default-config.xml /opt/apache-ignite/config/default-config-cluster2server.xml
```

TCP / IPディスカバリー方式でクラスタ設定を実施します。
「default-config-cluster2server.xml」ファイルを修正し、「\<property name="discoverySpi">」を追加します。
multicastで設定するほうが簡単なようですが、今回はIP指定（192.168.10.71, 192.168.10.72）で設定します。
※追記：そもそも、何も設定しないとデフォルトでmulticastで動くようです。multicastが通る環境なら、何も設定しなくて良かったです。

サーバ2台で同じファイルを作成します。（もしくはscpでコピー）

```xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
        Alter configuration below as needed.
    -->
    <bean id="grid.cfg" class="org.apache.ignite.configuration.IgniteConfiguration">
        <property name="discoverySpi">
            <bean class="org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi">
                <property name="ipFinder">
                    <bean class="org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder">
                        <property name="addresses">
                            <list>
                                <value>igniteserver1:47500..47509</value>
                                <value>igniteserver2:47500..47509</value>
                            </list>
                        </property>
                    </bean>
                </property>
            </bean>
        </property>
    </bean>
</beans>
```

今回はVisorから2台のApache Igniteのノードを起動します。
Visorからリモートのノードを操作するために、sshで接続できる必要があります。
以下のコマンドで設定します。

```terminal
$ ssh-keygen -t dsa -P '' -f ~/.ssh/id_dsa
$ cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys
$ chmod 600 ~/.ssh/authorized_keys

パスフレーズなしで設定できることを確認します。
$ ssh igniteserver1
$ ssh igniteserver2
```

Visorのコンソールを起動し、以下のコマンドを実行します。

```
# /opt/apache-ignite/bin/ignitevisorcmd.sh
Visorのコンソールが表示されるので、「open」コマンドでクラスタに接続する。
visor> open
～省略～
| 6  | config/default-config-cluster2server.xml                                            
～省略～
Choose configuration file number ('c' to cancel) [0]: 6
+------------------------------------------------------------------------------------+
| Status               | Connected                                                   |
| Ignite instance name | <default>                                                   |
| Config path          | /opt/apache-ignite/config/default-config-cluster2server.xml |
| Uptime               | 00:00:00                                                    |
+------------------------------------------------------------------------------------+


visor> start -h=192.168.20.71~72 -u=root -k=/root/.ssh/id_rsa -g=/opt/apache-ignite -c=config/default-config-cluster2server.xml
+-------------------------------+
| Successful start attempts | 2 |
| Failed start attempts     | 0 |
+-------------------------------+

```
「Successful start attempts」が"2"となっており、2台のサーバの起動に成功したことが分かります。

全てのノードを終了する場合は以下のコマンドを実行します。

```
visor> kill -k
Are you sure you want to kill ALL nodes? (y/n) [n]: y
You are about to kill ALL nodes. Are you 100% sure? (y/n) [n]: y
```


# JavaプログラムからIgniteクラスタへ接続

Igniteクラスタのノードには、キャッシュを保存するサーバノードと、データは持たないクライアントノードがあります。
Javaプログラムから、クライアントノードとしてIgniteクラスタへ接続します。
「Ignition.setClientMode(true);」としてクライアントと明示するところがポイントです。

"default-config.xml"をサーバから持ってきて、以下のようにコーディングします。設定は"default-config.xml"を読み込んでいますが、設定ファイルを用いずプログラムでコンフィグすることも可能です。

1台構成のときに作成したプログラムでも複数台に接続できるのですが、1台ダウンしたときに原因不明でフェイルオーバーがうまくいかなかったため書き直しました。

```java
import org.apache.ignite.Ignite;
import org.apache.ignite.IgniteCache;
import org.apache.ignite.Ignition;
import org.apache.ignite.client.ClientException;

public class SimpleCacheApp2Server
{
	public static void main(String[] args) {

	    Ignition.setClientMode(true);

	    try (Ignite igniteClient = Ignition.start("default-config-cluster2server.xml")) {
	        final String CACHE_NAME = "put-get-example";

	        IgniteCache<Integer, String> cache = igniteClient.getOrCreateCache(CACHE_NAME);

	        Integer key = 1;
	        String val = "put-get-test";

	        cache.put(key, val);

	        System.out.format("PUT [%s]\n", val);

	        String cachedVal = cache.get(key);

	        System.out.format("GET [%s]\n", cachedVal);
	    }
	    catch (ClientException e) {
	    	e.printStackTrace();
	    }
	    catch (Exception e) {
	    	e.printStackTrace();
	    }
	}
}
```


# 参考

- [Getting Started - Ignite公式サイト](https://apacheignite.readme.io/docs/getting-started)
- [Apache IgniteをUbuntu Linux 16.04 LTSにインストールする](http://d.hatena.ne.jp/Kazuhira/20180528/1527494224)