CLOVER🍀

That was when it all began.

Infinispan(Server)でクラスタを構成してみる

先ほど、InfinispanのEmbedded Modeでクラスタを構成してみましたが、今度はInfinispanのServer Moduleでクラスタを構成してみます。

Infinispan Serverでクラスタを組む際でも、選択するCacheのモードはEmbeddedと同じくReplicated Mode、Distribution Mode、Invalidation Mode、そしてLocal Modeになります。

ただ、それはInfinispan Server間で構成するクラスタの話であって、そのクラスタにアクセスする際のクライアントはまた別の話になります。

以前紹介した通り、Infinispan Serverへのアクセス方法はHot Rod、Memcached、REST、WebSocketがありますが、今回はHot Rodを使ってクラスタにアクセスしてみます。

Infinispan Serverのダウンロードと展開

Infinispan Serverは、こちらからダウンロードします。

http://infinispan.org/download/

今回は、Infinspan Server 7.0.2.Finalを使用します。

ダウンロードしたら、unzipしましょう。

$ unzip infinispan-server-7.0.2.Final-bin.zip

今回は、Infinispan Serverを3つ起動しようと思うので、展開されたInfinispan Serverをコピーして3つにします。

$ cp -Rp infinispan-server-7.0.2.Final infinispan-server-7.0.2.Final-2
$ cp -Rp infinispan-server-7.0.2.Final infinispan-server-7.0.2.Final-3

というわけで、今回はInfinispan Serverを3つのNodeでクラスタを構成します。

クラスタの構成自体はそれほど手を加えず、デフォルトで登録されているこちらのCacheを使用することにします。
*infinispan-server-7.0.2.Final/standalone/configuration/clustered.xmlより抜粋

                <distributed-cache name="namedCache" mode="SYNC" start="EAGER"/>

Distributed Cacheですね。

サーバの起動

展開したサーバを、それぞれ起動していきます。単純に起動してしまうと、Node名およびポートが衝突してしまうので、起動パラメータで調整します。

# Node 1
$ infinispan-server-7.0.2.Final/bin/clustered.sh -Djboss.node.name=node1

# Node 2
$ infinispan-server-7.0.2.Final/bin/clustered.sh -Djboss.node.name=node2 -Djboss.socket.binding.port-offset=1000

# Node 3
$ infinispan-server-7.0.2.Final/bin/clustered.sh -Djboss.node.name=node3 -Djboss.socket.binding.port-offset=2000

ポートは、2番目のNode以降1000ずつずらしています。

この結果、リッスンポートはそれぞれ11211、12211、13222となります。

クライアント側の準備

それでは、クライアント側のプログラムを作成するための準備をします。

Hot Rodを使うので、依存関係のinfinispan-client-hotrodを追加します。
build.sbt

name := "remote-clustered-starter"

version := "0.0.1-SNAPSHOT"

scalaVersion := "2.11.4"

organization := "org.littlewings"

scalacOptions ++= Seq("-Xlint", "-deprecation", "-unchecked", "-feature")

incOptions := incOptions.value.withNameHashing(true)

updateOptions := updateOptions.value.withCachedResolution(true)

libraryDependencies ++= Seq(
  "org.infinispan" % "infinispan-client-hotrod" % "7.0.2.Final",
  "net.jcip" % "jcip-annotations" % "1.0" % "provided"
)

クライアントプログラム

プログラム自体は、Embedded Modeの時と近いものになります。
src/main/scala/org/littlewings/infinispan/remote/RemoteCacheClusteringDemo.scala

package org.littlewings.infinispan.remote

import scala.collection.JavaConverters._
import scala.io.StdIn

import org.infinispan.client.hotrod.RemoteCacheManager
import org.infinispan.client.hotrod.configuration.ConfigurationBuilder

object RemoteCacheClusteringDemo {
  def main(args: Array[String]): Unit = {
    val manager =
      new RemoteCacheManager(new ConfigurationBuilder()
        .addServers("localhost:11222;localhost:12222;localhsot:13222")
        .build)
    val cache = manager.getCache[String, String]("namedCache")

    Iterator
      .continually(StdIn.readLine("> "))
      .withFilter(l => l != null && !l.isEmpty)
      .takeWhile(_ != "exit")
      .map(_.split(" +").toList)
      .foreach {
        case Nil =>
        case "put" :: key :: value :: Nil =>
          cache.put(key, value)
          println(s"Putted, $key : $value")
        case "get" :: key :: Nil =>
          println(s"Get, Key[$key] => ${cache.get(key)}")
        case "size" :: Nil =>
          println(s"Size = ${cache.size}")
        case "keys" :: Nil =>
          println(s"Keys:")
          cache.keySet.asScala.foreach(k => println(s" $k"))
        case command =>
          println(s"Unknown command, [${command(0)}]")
      }

    cache.stop()
    manager.stop()
  }
}

少々違う点として、RemoteCacheManagerを使うというところはありますが、接続先をあらかじめ与えておくところが特徴です。

    val manager =
      new RemoteCacheManager(new ConfigurationBuilder()
        .addServers("localhost:11222;localhost:12222;localhsot:13222")
        .build)

ひとつひとつホストとポートを追加するメソッドもあるのですが、このように「;」で区切って一括で指定することも可能です。

インタラクティブにコマンドを実行する部分については、Embedded Modeと大差ありませんが「locate」については削除しています。通常のAPIの範囲では、わからない感じがしたので。
*内部APIを引きずり出せば可能っぽいですが…

では、実行してみます。

> run

起動時に、3つ接続先があることがわかっているようです。

INFO: ISPN004006: localhost/127.0.0.1:11222 sent new topology view (id=4) containing 3 addresses: [/127.0.0.1:11222, /127.0.0.1:13222, /127.0.0.1:12222]

データの登録。

> put key1 value1
Putted, key1 : value1
> put key2 value2
Putted, key2 : value2
> put key3 value3
Putted, key3 : value3  
> put key4 value4
Putted, key4 : value4
> put key5 value5
Putted, key5 : value5

取得。

> get key3
Get, Key[key3] => value3

エントリ数の取得。

> size
Size = 5

キーの取得。

> keys
Keys:
 key1
 key2
 key5
 key3
 key4

Nodeを落としてみる

では、ここでサーバ側のNodeを落としてみましょう。

とりあえず、Ctr-cでNode 1、Node 2の順に停止してみます。この時、各Nodeを停止する間隔を詰めすぎると、リバランスが間に合わずにロストするデータが出てきます。

Node 3側では、自Nodeだけになっていることがわかります。

23:10:33,012 INFO  [org.infinispan.remoting.transport.jgroups.JGroupsTransport] (OOB-72,shared=udp) ISPN000094: Received new cluster view for channel clustered: [node2/clustered|3] (2) [node2/clustered, node3/clustered]
23:10:59,397 INFO  [org.infinispan.remoting.transport.jgroups.JGroupsTransport] (Incoming-5,shared=udp) ISPN000094: Received new cluster view for channel clustered: [node3/clustered|4] (1) [node3/clustered]

クライアント側で、キーを取得してみます。

> keys
Keys:
12 05, 2014 11:11:47 午後 org.infinispan.client.hotrod.impl.protocol.Codec20 readNewTopologyAndHash
INFO: ISPN004006: /127.0.0.1:13222 sent new topology view (id=10) containing 1 addresses: [/127.0.0.1:13222]
12 05, 2014 11:11:47 午後 org.infinispan.client.hotrod.impl.transport.tcp.TcpTransportFactory updateServers
INFO: ISPN004016: Server not in cluster anymore(/127.0.0.1:11222), removing from the pool.
12 05, 2014 11:11:47 午後 org.infinispan.client.hotrod.impl.transport.tcp.TcpTransportFactory updateServers
INFO: ISPN004016: Server not in cluster anymore(/127.0.0.1:12222), removing from the pool.
 key1
 key2
 key5
 key3
 key4

この時、クラスタの2つのNodeがいなくなったことを検知して、接続先から削除されるようです。また、エントリがなくなっていないこともわかります。

さすがに、全部落としちゃうとクライアント側もエラーになりますが。

> keys

〜省略〜

ERROR: ISPN004007: Exception encountered. Retry 10 out of 10
org.infinispan.client.hotrod.exceptions.TransportException:: Could not fetch transport

接続先は、あらかじめ列挙する必要がある?

最初のプログラムでは、このようにあらかじめ接続先をすべて列挙していました。

      new RemoteCacheManager(new ConfigurationBuilder()
        .addServers("localhost:11222;localhost:12222;localhsot:13222")
        .build)

ですが、実際のところは先ほどNodeをダウンさせた時に検出されたことからもわかるように、すべてを列挙する必要はなかったりします。

      new RemoteCacheManager(new ConfigurationBuilder()
        // .addServers("localhost:11222;localhost:12222;localhsot:13222")
        .addServers("localhost:11222")  // これでもOK
        .build)

起動時、アクセス時に、クラスタに参加しているNode群を検出してくれます。

INFO: ISPN004006: localhost/127.0.0.1:11222 sent new topology view (id=4) containing 3 addresses: [/127.0.0.1:13222, /127.0.0.1:11222, /127.0.0.1:12222]

あくまで初期値ということですね。無効なNodeが書かれていた場合は、即刻削除されますが。

このため、途中でNodeが増えたりしてもちゃんとクライアント側で認識してくれます。良いですね!

今回作成したソースコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-clustered-starter