これは、なにをしたくて書いたもの?
Infinispan Serverの、ヘルスチェックAPIを確認してみたいなと思いまして。
もっと言うと、クラスター内のノードが停止してデータのリバランスが発生した場合、現在リバランス中なのか?
もう安定したのか?を知る方法は?というのが動機です。
Cluster Health API
Infinispan ServerのCluster Health APIについては、こちらに記載があります。
Infinispan Server Guide / Retrieving Health Statistics
Serverのドキュメントと言いつつ、EmbeddedCacheManager
の情報も書かれていますが…。
Infinispanのクラスターのヘルスチェックを行う方法としては、以下の3種類があります。
- Programmatically with
EmbeddedCacheManager#getHealth
method calls. - JMX MBeans
- Infinispan REST Server
今回は、REST APIによる確認を中心に行います。
Using the Infinispan REST Server / Getting Cluster Health
Infinispan ServerのCluster Health APIを呼び出すには、以下のパスにアクセスします。
http://[Infinispan Serverが動作しているホスト]:11222/rest/v2/cache-managers/{cacheManagerName}/health
{cacheManagerName}
というのは、cache-container
の名前です。
デフォルトのinfinispan.xml
であれば、以下のように定義されていると思いますので
<cache-container name="default" statistics="true">
この場合、default
のcache-container
のCluster Health APIにアクセスするためには、以下のURLにアクセスすることに
なります。
http://[Infinispan Serverが動作しているホスト]:11222/rest/v2/cache-managers/default/health
ここで取得できる情報としては、クラスターレベルのHealthとキャッシュレベルのHealthの2種類があります。
それぞれ、以下の情報が確認できます。
Healthステータスには、以下の4種類があります。
- HEALTHY … 正常
- HEALTHY_REBALANCING … キャッシュは正常ではあるが、リバランス中
- DEGRADED … DEGRADED modeになっている
- FAILED … なんらかのエラーのため、キャッシュが開始できなかった
クラスターレベルのHealthステータスは、cache-container
内のキャッシュにひとつでもHEALTHY
以外のものがあった場合、
その中の最初に見つかったステータスを返します。
ステータスがHEALTHY_REBALANCING
の場合、まだデータのリバランスが終わっていないので安定していない状態、
ということになります。このような場合は、HEALTHY
になるまで待つことになるでしょう。
ちなみにDEGRADED modeというのは、Split Brainを起こした後に読み書きが制限された状態のことを指します。
Configuring Infinispan Caches / Split brain
また、Healthに関する情報は、メトリクスからは取得できません。クラスターを構成するノード数のみが取得可能です。
Infinispan Server Guide / Enabling and configuring Infinispan statistics and JMX monitoring
では、説明はこれくらいにして確認していってみましょう。今回は、安定したクラスターの状態と、ノードを失って
リバランス中の状態を確認しようと思います。
なお、JMXを使った確認は行いません。
環境
今回の環境は、こちら。
$ java --version openjdk 17.0.1 2021-10-19 OpenJDK Runtime Environment (build 17.0.1+12-Ubuntu-120.04) OpenJDK 64-Bit Server VM (build 17.0.1+12-Ubuntu-120.04, mixed mode, sharing) $ mvn --version Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 17.0.1, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.4.0-91-generic", arch: "amd64", family: "unix"
Infinispan Serverは、13.0.5.Finalを使用します。クラスターは3ノード構成として、各ノードのIPアドレスは172.19.0.2〜4の
範囲とします。
準備
まずはInfinispan Serverの準備をします。
infinispan.xml
は、エンドポイントの認証設定のみ行います。
<endpoints> <endpoint socket-binding="default" security-realm="default"> <hotrod-connector> <authentication> <sasl mechanisms="SCRAM-SHA-512 SCRAM-SHA-384 SCRAM-SHA-256 SCRAM-SHA-1 DIGEST-SHA-512 DIGEST-SHA-384 DIGEST-SHA-256 DIGEST-SHA DIGEST-MD5 PLAIN" server-name="infinispan" qop="auth"/> </authentication> </hotrod-connector> <rest-connector> <authentication mechanisms="DIGEST BASIC"/> </rest-connector> </endpoint> </endpoints>
少し前まで、endpoint
がendpoints
の子要素になることはドキュメントに書かれていなかったのですが、改善された
みたいです。
Infinispan Server Guide / Configuring Endpoint Authentication Mechanisms
各サーバーには、管理用のユーザーとアプリケーション用のユーザーを作成しておきます。
$ bin/cli.sh user create -g admin -p password admin-user $ bin/cli.sh user create -g application -p password app-user
起動。
$ bin/server.sh \ -b 0.0.0.0 \ -Djgroups.tcp.address=`hostname -i`
これで、Infinispan Serverの準備はおしまいです。
キャッシュは、プログラムで作成することにします。
Distributed Cacheを作成して、データを登録する
では、キャッシュを作成してデータを登録することにします。
Maven依存関係等は、こちら。
<dependencies> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-client-hotrod</artifactId> <version>13.0.5.Final</version> </dependency> <dependency> <groupId>org.infinispan</groupId> <artifactId>infinispan-core</artifactId> <version>13.0.5.Final</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>5.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.assertj</groupId> <artifactId>assertj-core</artifactId> <version>3.21.0</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <artifactId>maven-surefire-plugin</artifactId> <version>2.22.2</version> </plugin> </plugins> </build>
Hot Rod Clientでキャッシュ作成とデータ登録を行います。
テストコードはこちら。名前の割には、このテストコード内でCluster Health APIを呼び出しているわけではないのですが…。
src/test/java/org/littlewings/infinispan/remote/health/ClusterHealthApiTest.java
package org.littlewings.infinispan.remote.health; import java.time.LocalDateTime; import java.util.function.Consumer; import java.util.stream.IntStream; import org.infinispan.client.hotrod.RemoteCache; import org.infinispan.client.hotrod.RemoteCacheManager; import org.infinispan.client.hotrod.RemoteCacheManagerAdmin; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; public class ClusterHealthApiTest { static String createUri(String userName, String password) { return String.format("hotrod://%s:%s@172.19.0.2:11222,172.19.0.3:11222,172.19.0.4:11222", userName, password); } @BeforeAll public static void setupAll() { String uri = createUri("admin-user", "password"); try (RemoteCacheManager manager = new RemoteCacheManager(uri)) { RemoteCacheManagerAdmin admin = manager.administration(); org.infinispan.configuration.cache.Configuration distCacheConfiguration = new org.infinispan.configuration.cache.ConfigurationBuilder() .clustering() .cacheMode(org.infinispan.configuration.cache.CacheMode.DIST_SYNC) .encoding().key().mediaType("application/x-protostream") .encoding().value().mediaType("application/x-protostream") .build(); admin.getOrCreateCache("distCache", distCacheConfiguration); } } <K, V> void withCache(String cacheName, Consumer<RemoteCache<K, V>> func) { String uri = createUri("app-user", "password"); try (RemoteCacheManager manager = new RemoteCacheManager(uri)) { RemoteCache<K, V> cache = manager.getCache(cacheName); func.accept(cache); } } @Test public void putsData() { this.<String, String>withCache("distCache", cache -> { int targetEntries = 1000000; IntStream .rangeClosed(1, targetEntries) .parallel() .peek(i -> { if (i % 100000 == 0) { System.out.printf("[%s] putted, %d entries%n", LocalDateTime.now(), i); } }) .forEach(i -> cache.put("key" + i, "value" + i)); assertThat(cache.size()).isEqualTo(targetEntries); }); } }
とりあえず、Distributed Cacheを作成してデータを10万件登録してみました。
Cluster Health APIを使ってみる
では、この状態でCluster Health APIを呼び出してみます。
こんな結果になりました。
$ curl -s -u app-user:password 172.19.0.2:11222/rest/v2/cache-managers/default/health | jq { "cluster_health": { "cluster_name": "cluster", "health_status": "HEALTHY", "number_of_nodes": 3, "node_names": [ "infinispan-server-60394", "infinispan-server-61394", "infinispan-server-7187" ] }, "cache_health": [ { "status": "HEALTHY", "cache_name": "___protobuf_metadata" }, { "status": "HEALTHY", "cache_name": "___script_cache" }, { "status": "HEALTHY", "cache_name": "distCache" } ] }
クラスターは3ノードで、ステータスはすべてHEALTHY
なので、安定した状態です。
ここで、172.19.0.4のノードを停止してみます(kill
で止めてもいいですが)。
Infinispan Server Guide / Shutting down Infinispan Server
$ bin/cli.sh -c http://admin-user:password@172.19.0.4:11222 [infinispan-server-7187@cluster//containers/default]> shutdown server
停止して本当にすぐはまだ状態は変わりませんが
$ curl -s -u app-user:password 172.19.0.2:11222/rest/v2/cache-managers/default/health | jq { "cluster_health": { "cluster_name": "cluster", "health_status": "HEALTHY", "number_of_nodes": 3, "node_names": [ "infinispan-server-60394", "infinispan-server-61394", "infinispan-server-7187" ] }, "cache_health": [ { "status": "HEALTHY", "cache_name": "___protobuf_metadata" }, { "status": "HEALTHY", "cache_name": "___script_cache" }, { "status": "HEALTHY", "cache_name": "distCache" } ] }
クラスター内のノードが失われたことが検出されると
2021-12-22 14:42:54,773 INFO (jgroups-36,infinispan-server-28362) [org.infinispan.CLUSTER] [Context=___hotRodTopologyCache_hotrod-d efault]ISPN100008: Updating cache members list [infinispan-server-28362, infinispan-server-31674], topology id 10
リバランスが始まります。
2021-12-22 14:59:30,214 INFO (jgroups-7,infinispan-server-60394) [org.infinispan.CLUSTER] [Context=distCache]ISPN100008: Updating cache members list [infinispan-server-60394, infinispan-server-61394], topology id 10 2021-12-22 14:59:30,216 INFO (jgroups-7,infinispan-server-60394) [org.infinispan.CLUSTER] [Context=distCache]ISPN100002: Starting rebalance with members [infinispan-server-60394, infinispan-server-61394], phase READ_OLD_WRITE_ALL, topology id 11
今回は作成したDistributed Cacheに絞ってログ表示していますが、これ以外にもリバランスの情報は表示されます。
Cluster Health APIでの確認を繰り返していると、ステータスがHEALTHY_REBALANCING
に変化します。
$ curl -s -u app-user:password 172.19.0.2:11222/rest/v2/cache-managers/default/health | jq { "cluster_health": { "cluster_name": "cluster", "health_status": "HEALTHY_REBALANCING", "number_of_nodes": 3, "node_names": [ "infinispan-server-60394", "infinispan-server-61394", "infinispan-server-7187" ] }, "cache_health": [ { "status": "HEALTHY", "cache_name": "___protobuf_metadata" }, { "status": "HEALTHY_REBALANCING", "cache_name": "___script_cache" }, { "status": "HEALTHY_REBALANCING", "cache_name": "distCache" } ] }
作成したDistributed CacheがHEALTHY_REBALANCING
ですね。また、クラスター全体もHEALTHY_REBALANCING
です。
さらにノードもなくなり、2ノードでクラスターが構成されていることになります。
$ curl -s -u app-user:password 172.19.0.2:11222/rest/v2/cache-managers/default/health | jq { "cluster_health": { "cluster_name": "cluster", "health_status": "HEALTHY_REBALANCING", "number_of_nodes": 2, "node_names": [ "infinispan-server-60394", "infinispan-server-61394" ] }, "cache_health": [ { "status": "HEALTHY", "cache_name": "___protobuf_metadata" }, { "status": "HEALTHY", "cache_name": "___script_cache" }, { "status": "HEALTHY_REBALANCING", "cache_name": "distCache" } ] }
さらに待っていると、安定してHEALTHY
になります。
$ curl -s -u app-user:password 172.19.0.2:11222/rest/v2/cache-managers/default/health | jq { "cluster_health": { "cluster_name": "cluster", "health_status": "HEALTHY", "number_of_nodes": 2, "node_names": [ "infinispan-server-60394", "infinispan-server-61394" ] }, "cache_health": [ { "status": "HEALTHY", "cache_name": "___protobuf_metadata" }, { "status": "HEALTHY", "cache_name": "___script_cache" }, { "status": "HEALTHY", "cache_name": "distCache" } ] }
リバランスが終了したことも、ログ出力されています。
2021-12-22 14:59:33,613 INFO (jgroups-41,infinispan-server-60394) [org.infinispan.CLUSTER] [Context=distCache]ISPN100010: Finished rebalance with members [infinispan-server-60394, infinispan-server-61394], topology id 14
これで、Infinispan ServerでのCluster Health APIの主要な確認は終わりですね。
Embedded Cacheの場合は?
軽くですが、Embedded Cacheの場合も見ておきましょう。こんな感じでの確認になります。
src/test/java/org/littlewings/infinispan/remote/health/EmbeddedCacheManagerHealthTest.java
package org.littlewings.infinispan.remote.health; import java.io.IOException; import java.util.Comparator; import java.util.List; import org.infinispan.configuration.cache.CacheMode; import org.infinispan.configuration.cache.ConfigurationBuilder; import org.infinispan.configuration.global.GlobalConfiguration; import org.infinispan.configuration.global.GlobalConfigurationBuilder; import org.infinispan.health.CacheHealth; import org.infinispan.health.ClusterHealth; import org.infinispan.health.Health; import org.infinispan.health.HealthStatus; import org.infinispan.manager.DefaultCacheManager; import org.infinispan.manager.EmbeddedCacheManager; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; public class EmbeddedCacheManagerHealthTest { @Test public void health() throws IOException { GlobalConfiguration globalConfiguration = new GlobalConfigurationBuilder().clusteredDefault().transport().defaultTransport().build(); try (EmbeddedCacheManager cacheManager = new DefaultCacheManager(globalConfiguration)) { cacheManager .defineConfiguration( "myCache", new ConfigurationBuilder().clustering().cacheMode(CacheMode.DIST_SYNC).build() ); cacheManager .defineConfiguration( "myCache2", new ConfigurationBuilder().clustering().cacheMode(CacheMode.DIST_SYNC).build() ); Health health = cacheManager.getHealth(); ClusterHealth clusterHealth = health.getClusterHealth(); assertThat(clusterHealth.getHealthStatus()).isEqualTo(HealthStatus.HEALTHY); assertThat(clusterHealth.getNumberOfNodes()).isEqualTo(1); List<CacheHealth> cacheHealths = health.getCacheHealth().stream().sorted(Comparator.comparing(c -> c.getCacheName())).toList(); assertThat(cacheHealths).hasSize(2); assertThat(cacheHealths.get(0).getCacheName()).isEqualTo("myCache"); assertThat(cacheHealths.get(0).getStatus()).isEqualTo(HealthStatus.HEALTHY); assertThat(cacheHealths.get(1).getCacheName()).isEqualTo("myCache2"); assertThat(cacheHealths.get(1).getStatus()).isEqualTo(HealthStatus.HEALTHY); } } }
クラスターレベルのヘルスチェックはこのように行い、
Health health = cacheManager.getHealth();
ClusterHealth clusterHealth = health.getClusterHealth();
assertThat(clusterHealth.getHealthStatus()).isEqualTo(HealthStatus.HEALTHY);
assertThat(clusterHealth.getNumberOfNodes()).isEqualTo(1);
キャッシュレベルのヘルスチェックは、このようにして行えます。
List<CacheHealth> cacheHealths = health.getCacheHealth().stream().sorted(Comparator.comparing(c -> c.getCacheName())).toList(); assertThat(cacheHealths).hasSize(2); assertThat(cacheHealths.get(0).getCacheName()).isEqualTo("myCache"); assertThat(cacheHealths.get(0).getStatus()).isEqualTo(HealthStatus.HEALTHY); assertThat(cacheHealths.get(1).getCacheName()).isEqualTo("myCache2"); assertThat(cacheHealths.get(1).getStatus()).isEqualTo(HealthStatus.HEALTHY);
先ほどまで行っていた、Infinispan ServerのCluster Health APIで取得していた情報と同じですね。
HEALTHY_REBALANCINGは、どのような状態?
HEALTHY_REBALANCING
というステータスが気になるので、少しソースコードを追ってみましょう。
まず、おさらい的にクラスター全体のステータスは、cache-container
が保持するキャッシュのうち、ひとつでも
HEALTHY
以外のものがあればそれが反映されます。
キャッシュはどうかというと、こちら。
HEALTHY_REBALANCING
ステータスは、DistributionManager#isRehashInProgress
がtrue
を返す時になります。
この状態は、CacheTopology#getPendingCH
がnull
ではない時、となっています。
CacheTopology
が持つCH(ConsistentHash
)というのは、キーと所有者のマッピングを定義したものです。
Configuring Infinispan Caches / Key ownership
ConsistentHash (Infinispan JavaDoc 13.0.5.Final API)
CacheTopology
が持つCHには現在(current)とpendingがあります。Pending状態のConsistentHash
というのは、
未来になるべきConsistentHash
の状態のことを指します。
そして、CacheTopology#getPendingCH
がnull
ではない時にHEALTHY_REBALANCING
ステータスというでした。
なので、CacheTopology#getPendingCH
がnull
ではない時というのは、クラスター内のノードに増減などがあって
再調整した結果、算出されたConsistentHash
の状態になれていないことを表します。
リバランス開始の指示したりするのは、このあたりですね。
安定すると、このあたりが呼び出されるようです。
ちなみに、こう書いているとリバランスはDistributed Cacheのみの話に見えなくもないですが、リバランスがないのは
Local CacheとInvalidation Cacheだけみたいですね。
Replicated Cacheに関しては、pendingという状態がちょっとイメージできていないのですが…今回はここは
パスしておきます…。
最後に、REST APIでアクセスしているCluster Health APIはこちらになります。
中身は、EmbeddedCacheManagerで使っていたClusterHealth
、CacheHealth
の情報を扱っています。
まとめ
Infinispan Server、それからEmbedded CacheでCluster Health APIを試してみました。
データのリバランスが完了しているかどうかを確認する方法がわかったり、内部の様子も追えたので勉強になりました。
今回作成したソースコードは、こちらに置いています。
https://github.com/kazuhira-r/infinispan-getting-started/tree/master/remote-cluster-health-api