先日書かれた、こちらの記事を見まして。
クラスタ化された Payara Server のアップグレード - notepad
ちょっと気になったのは、こちらの部分。
Case 1 : Shoal を使用していない場合
http://www.coppermine.jp/docs/notepad/2017/12/how-to-update-clustered-payara-server.html
Payara は GlassFish から引き継いだ Shoal を使用しなくても、Hazelcast のみでクラスタリングが可能です。その場合はおそらく、すべてのノードが DAS となっているはずで、通常のアップグレードをすべてのノードに対して実施すればよいことになります。ノードの接続・切断は Hazelcast が自動的に行うため考慮する必要はありません。
確かにそのとおりだと思うのですが、個人的にはもうちょっと気になるところがあります。
特に、このあたり。
- Hazelcast Nodeを接続・切断してよいタイミング
- Hazelcast自体のバージョン間の互換性
というわけで、この視点でHazelcast側を見ていこうかなぁと思います。
ClusterSafeとClusterStatus
Hazelcastは分散KVS的な側面を持っていますが、Nodeの追加、削除が起こるとデータのリバランシングが発生します。
特にNodeが失われると一時的にデータが一部なくなりますが、他のNodeから復元することが可能です。ただ、デフォルトではデータのバックアップ数は
1なので、2つのNodeが同時にダウンするとデータが欠損する可能性があります。
例えばPayaraの場合は
- Distributed Map
- Cache
- Topic
あたりのデータ構造を使用していますが、MapとCacheについてはこの理屈が当てはまります。
※Topicについては分散配置ができないので、2つのNodeが同時にダウンするとすべてのデータを失う可能性があります
そんなわけなので、リバランスが終わって安全な状態になったかどうか、確認したりしたいですよね?
このあたりを見るのに、ClusterSafeがあります。
ClusterSafeについては、こちら。
Safety Checking Cluster Members
PartitionServiceが持つisClusterSafeメソッドでは、Hazelcastクラスタが安全な状態にあるかどうかをチェックします。データのリバランスがなく
(Partitionの移行が発生していない)、すべてのバックアップが各Partitionで同期されている場合にtrueを返します。
isMemberSafeやisLocalMemberは、指定したMember、もしくは自分自身が持つPartitionがプライマリーとの同期が済んでいればtrueを返します。
trueを返す場合は、該当のMemberをシャットダウンすることができます。
使用するAPIとしては、こちら。
PartitionService (Hazelcast Root 3.9.1 API)
PartitionServiceMBean (Hazelcast Root 3.9.1 API)
続いて、クラスタの状態を確認するものとしてClusterStateがあります。
Managing Cluster and Member States
ClusterStateでは、クラスタ操作の許可/制限の状態を確認することができます。例えば、ClusterStatusがACTIVEであれば特に制限はありませんが、FROZEMや
PASSIVEの場合はPartitionが固定され、新規Memberを受け付けなくなったりします。
ClusterStateに関するAPIは、こちら。
Cluster (Hazelcast Root 3.9.1 API)
ClusterState (Hazelcast Root 3.9.1 API)
また、API以外にも、JMXやHealth Check(REST API)でも確認することができます。
Monitoring with JMX
Health Check and Monitoring
JMXは「hazelcast.jmx」を「true」に、Health Checkは「hazelcast.http.healthcheck.enabled」を「true」にすることで、それぞれ有効化することができます。
システムプロパティか、Hazelcastの設定ファイル/Configのproperties/propertyで設定します。
Hazelcastのバージョン間の互換性は?
HazelcastのRolling Upgrade(クラスタ内のMemberを順次アップグレードしていくこと)について、ドキュメントに以下の記載があります。
ところでこれ、Enterprise版のHazelcastの話です。
ここで、まずHazelcastのバージョンの定義を見てみます。
・Minor version: A version change after the decimal point, e.g., 3.8 and 3.9.
http://docs.hazelcast.org/docs/3.9.1/manual/html-single/index.html#terminology
・Patch version: A version change after the second decimal point, e.g., 3.8.1 and 3.8.2.
・Member codebase version: The major.minor.patch version of the Hazelcast binary on which the member executes. For example, when running on hazelcast-3.8.jar, your member's codebase version is 3.8.0.
・Cluster version: The major.minor version at which the cluster operates. This ensures that cluster members are able to communicate using the same cluster protocol and determines the feature set exposed by the cluster.
Member codebase versionが「major.minor.patch」バージョンを、Cluster versionが「major.minor」バージョンを指すみたいですね。
で、ここを読むとHazelcastはCluster version間で互換性がないことがわかります。
Hazelcast Members Compatibility Guarantees
Enterprise版のHazelcast 3.8以降であれば、ひとつ前のMinor Versionからの互換性があるようです(Hazelcast IMDG Enterprise 3.8から3.9へはRolling Upgradeが可能)。
ポイントかと思いますので、押さえておきましょう。
試してみる
と、ドキュメントベースに説明だけだとなんなので、試してみましょう。
簡単にGroovyスクリプトで、次のことを試してみたいと思います。
用意したスクリプトは、こんな感じ。
hazelcast391-runner.groovy
@Grab('com.hazelcast:hazelcast:3.9.1') import com.hazelcast.core.Hazelcast import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import java.util.stream.IntStream import java.time.LocalDateTime System.setProperty('hazelcast.jmx', 'true') System.setProperty('hazelcast.http.healthcheck.enabled', 'true') def log = { message -> println("[${LocalDateTime.now()}] ${message}") } log('starting Hazelcast Instance...') def hazelcast = Hazelcast.newHazelcastInstance() def running = new AtomicBoolean(true) new Thread({ -> while (running.get()) { def clusterState = hazelcast.cluster.clusterState def partitionService = hazelcast.partitionService log("Cluster#clusterState = ${clusterState}") log("PartitionService#clusterSafe = ${partitionService.clusterSafe}") // Reflection!! log("InternalPartitionServicem#migrationQueueSize = ${hazelcast.original.node.partitionService.migrationQueueSize}") TimeUnit.SECONDS.sleep(1L) } }).start() log('Hazelcast Instance started.') System.console().readLine('> Enter input data') def map = hazelcast.getMap('default') IntStream.rangeClosed(1, 100000).forEach { i -> map.put("${hazelcast.cluster.localMember.uuid}-key${i}", "value${i}") } log("putted data, size = ${map.size()}") System.console().readLine('> Enter stop Hazelcast Instance') running.set(false) hazelcast.shutdown() log('Hazelcast Instance shutdown.')
この構成を基本にして、あとでGrapeで指定したバージョンを変えたスクリプトを作成していきます。
@Grab('com.hazelcast:hazelcast:3.9.1') import com.hazelcast.core.Hazelcast
JMXとHealth Checkは、今回はシステムプロパティで有効化しました。
System.setProperty('hazelcast.jmx', 'true') System.setProperty('hazelcast.http.healthcheck.enabled', 'true')
ところどころEnterを押すと進んでいくのですが、途中でClusterStateやClusterSafeなどを観測したり、データを登録したりする処理を付けたり
しています。
JMX & Health Check
とりあえず、この状態で起動してみましょう。
$ groovy hazelcast391-runner.groovy
JMXの方は、起動時に有効化されていることがログ出力されます。
12 15, 2017 11:41:58 午後 com.hazelcast.internal.jmx.ManagementService 情報: [172.20.0.1]:5701 [dev] [3.9.1] Hazelcast JMX agent enabled.
PartitionServiceMBeanのAttributeを見ると、ClusterSafeや自分自身の状態を確認できます。
続いて、Health Checkを確認してみましょう。Health Checkは、「http://[IPアドレス]:[ポート]/hazelcast/health」で確認することができます。
$ curl http://localhost:5701/hazelcast/health Hazelcast::NodeState=ACTIVE Hazelcast::ClusterState=ACTIVE Hazelcast::ClusterSafe=TRUE Hazelcast::MigrationQueueSize=0 Hazelcast::ClusterSize=1
表示できましたね。
その他、cluster.shで確認する方法やREST APIで確認する方法もあるようですが、今回は割愛。
Using the Script cluster.sh
Using REST API for Cluster Management
Enterprise版だと、Management Centerがもっと豪華に…。
Clustered REST via Management Center
とりあえず、JMXとHealth Checkはこんなところで。
クラスタ内のMemberを増減させてみる
new Thread({ -> while (running.get()) { def clusterState = hazelcast.cluster.clusterState def partitionService = hazelcast.partitionService log("Cluster#clusterState = ${clusterState}") log("PartitionService#clusterSafe = ${partitionService.clusterSafe}") // Reflection!! log("InternalPartitionServicem#migrationQueueSize = ${hazelcast.original.node.partitionService.migrationQueueSize}") TimeUnit.SECONDS.sleep(1L) } }).start()
10万件ほどデータを放り込む処理を付けてあります。
def map = hazelcast.getMap('default') IntStream.rangeClosed(1, 100000).forEach { i -> map.put("${hazelcast.cluster.localMember.uuid}-key${i}", "value${i}") } log("putted data, size = ${map.size()}")
クラスタの状態確認は、それぞれClusterおよびPartitionServiceからClusterStateとClusterSafeを確認しています。
def clusterState = hazelcast.cluster.clusterState def partitionService = hazelcast.partitionService log("Cluster#clusterState = ${clusterState}") log("PartitionService#clusterSafe = ${partitionService.clusterSafe}")
あと、ちょっと強引にアクセスしているのがMigrationQueueで、これでMemberの増減に伴うデータのマイグレーション(リバランシング)の状態を確認できます。
// Reflection!! log("InternalPartitionServicem#migrationQueueSize = ${hazelcast.original.node.partitionService.migrationQueueSize}")
これはMember単位のサイズになります。
実は、先ほどのHealth Check時にも出力されています。
$ curl http://localhost:5701/hazelcast/health Hazelcast::NodeState=ACTIVE Hazelcast::ClusterState=ACTIVE Hazelcast::ClusterSafe=TRUE Hazelcast::MigrationQueueSize=0 Hazelcast::ClusterSize=1
これをちょっとズルして取得している感じです。
では、まずはひとつ起動。
$ groovy hazelcast391-runner.groovy
データを放り込みます。
[2017-12-16T00:03:51.329] putted data, size = 100000
ちなみに、この時はこんな感じでクラスタの状態が出力されます。
[2017-12-16T00:04:33.622] Cluster#clusterState = ACTIVE [2017-12-16T00:04:33.623] PartitionService#clusterSafe = true [2017-12-16T00:04:33.624] InternalPartitionServicem#migrationQueueSize = 0 [2017-12-16T00:04:34.624] Cluster#clusterState = ACTIVE [2017-12-16T00:04:34.626] PartitionService#clusterSafe = true [2017-12-16T00:04:34.626] InternalPartitionServicem#migrationQueueSize = 0 [2017-12-16T00:04:35.627] Cluster#clusterState = ACTIVE [2017-12-16T00:04:35.629] PartitionService#clusterSafe = true [2017-12-16T00:04:35.630] InternalPartitionServicem#migrationQueueSize = 0
ここで1 Nodeなので、次のNodeを起動してみましょう。
$ groovy hazelcast391-runner.groovy
クラスタにMemberが参加するとともに、ClusterSafeがfalseとなり、MigrationQueueのサイズも増えます。
12 16, 2017 12:05:20 午前 com.hazelcast.internal.cluster.ClusterService 情報: [172.20.0.1]:5701 [dev] [3.9.1] Members {size:2, ver:2} [ Member [172.20.0.1]:5701 - 87b87aa5-4da8-40e9-924e-adb661d81b38 this Member [172.20.0.1]:5702 - c957669a-27c4-4483-917c-8d37afc54a6e ] 12 16, 2017 12:05:20 午前 com.hazelcast.internal.partition.impl.MigrationManager 情報: [172.20.0.1]:5701 [dev] [3.9.1] Re-partitioning cluster data... Migration queue size: 271 [2017-12-16T00:05:20.749] Cluster#clusterState = ACTIVE [2017-12-16T00:05:20.753] PartitionService#clusterSafe = false [2017-12-16T00:05:20.753] InternalPartitionServicem#migrationQueueSize = 253 [2017-12-16T00:05:21.754] Cluster#clusterState = ACTIVE [2017-12-16T00:05:21.756] PartitionService#clusterSafe = false [2017-12-16T00:05:21.757] InternalPartitionServicem#migrationQueueSize = 134 [2017-12-16T00:05:22.758] Cluster#clusterState = ACTIVE [2017-12-16T00:05:23.061] PartitionService#clusterSafe = false [2017-12-16T00:05:23.063] InternalPartitionServicem#migrationQueueSize = 34 [2017-12-16T00:05:24.064] Cluster#clusterState = ACTIVE [2017-12-16T00:05:24.079] PartitionService#clusterSafe = true [2017-12-16T00:05:24.080] InternalPartitionServicem#migrationQueueSize = 0 12 16, 2017 12:05:24 午前 com.hazelcast.internal.partition.impl.MigrationThread 情報: [172.20.0.1]:5701 [dev] [3.9.1] All migration tasks have been completed, queues are empty.
最後に、MigrationQueueのサイズが0になるとClusterSafeがtrueとなるようです。
※このあたりは、あとでもう少し
3つ目のNodeを足しても同じ。
12 16, 2017 12:07:32 午前 com.hazelcast.internal.cluster.ClusterService 情報: [172.20.0.1]:5701 [dev] [3.9.1] Members {size:3, ver:3} [ Member [172.20.0.1]:5701 - 87b87aa5-4da8-40e9-924e-adb661d81b38 this Member [172.20.0.1]:5702 - c957669a-27c4-4483-917c-8d37afc54a6e Member [172.20.0.1]:5703 - 967fff3e-94e7-4b40-8c97-604bdb4a00ec ] [2017-12-16T00:07:32.719] Cluster#clusterState = ACTIVE [2017-12-16T00:07:32.722] PartitionService#clusterSafe = false [2017-12-16T00:07:32.723] InternalPartitionServicem#migrationQueueSize = 1 12 16, 2017 12:07:32 午前 com.hazelcast.internal.partition.impl.MigrationManager 情報: [172.20.0.1]:5701 [dev] [3.9.1] Re-partitioning cluster data... Migration queue size: 271 [2017-12-16T00:07:33.724] Cluster#clusterState = ACTIVE [2017-12-16T00:07:33.725] PartitionService#clusterSafe = false [2017-12-16T00:07:33.726] InternalPartitionServicem#migrationQueueSize = 228 [2017-12-16T00:07:34.727] Cluster#clusterState = ACTIVE [2017-12-16T00:07:34.992] PartitionService#clusterSafe = false [2017-12-16T00:07:34.993] InternalPartitionServicem#migrationQueueSize = 101 [2017-12-16T00:07:35.995] Cluster#clusterState = ACTIVE [2017-12-16T00:07:36.065] PartitionService#clusterSafe = true [2017-12-16T00:07:36.066] InternalPartitionServicem#migrationQueueSize = 0 12 16, 2017 12:07:36 午前 com.hazelcast.internal.partition.impl.MigrationThread 情報: [172.20.0.1]:5701 [dev] [3.9.1] All migration tasks have been completed, queues are empty.
なお、念のためですが、データを追加したりしても別にデータのリバランシングが発生するとかいうわけではありませんので。
異なるバージョンのHazelcastでクラスタを構成してみる
最後に、異なるバージョンのHazelcastを使用してクラスタを構成してみましょう。
最初に、Hazelcast 3.9.1(現時点での最新)のNodeを起動してみます。
$ groovy hazelcast391-runner.groovy
ここで、Grapeでのバージョン指定だけ変えたスクリプトを用意してみます。
hazelcast39-runner.groovy
@Grab('com.hazelcast:hazelcast:3.9') import com.hazelcast.core.Hazelcast
起動。
$ groovy hazelcast39-runner.groovy
無事、クラスタが構成されました。
12 16, 2017 12:12:37 午前 com.hazelcast.internal.cluster.ClusterService 情報: [172.20.0.1]:5701 [dev] [3.9.1] Members {size:2, ver:2} [ Member [172.20.0.1]:5701 - 690a44e3-11b5-4728-93fb-2a1cbf76562a this Member [172.20.0.1]:5702 - 6233fe6c-f3e9-4b32-9efd-46a57a80f564 ]
それでは、今度は3.9.1と3.8系の最新である3.8.8で試してみましょう。
hazelcast388-runner.groovy
@Grab('com.hazelcast:hazelcast:3.8.8') import com.hazelcast.core.Hazelcast
起動。先に3.9.1の方を起動してみました。
## 3.9.1 $ groovy hazelcast391-runner.groovy ## 3.8.8 $ groovy hazelcast388-runner.groovy
すると、3.9.1側にはこういうログが出力され、クラスタに入ることを拒否されます。
12 16, 2017 12:15:09 午前 com.hazelcast.internal.cluster.impl.ClusterJoinManager 警告: [172.20.0.1]:5701 [dev] [3.9.1] Joining node's version 3.8.8 is not compatible with cluster version 3.9 (Rolling Member Upgrades are only supported for the next minor version) (Rolling Member Upgrades are only supported in Hazelcast Enterprise) 12 16, 2017 12:15:09 午前 com.hazelcast.nio.tcp.TcpIpConnection 情報: [172.20.0.1]:5701 [dev] [3.9.1] Connection[id=1, /172.20.0.1:5701->/192.168.254.128:34082, endpoint=[172.20.0.1]:5702, alive=false, type=MEMBER] closed. Reason: Connection closed by the other side
互換性ないよ、Rolling UpgradeしたかったらHazelcast Enterprise使ってね、って言われてますね。
警告: [172.20.0.1]:5701 [dev] [3.9.1] Joining node's version 3.8.8 is not compatible with cluster version 3.9 (Rolling Member Upgrades are only supported for the next minor version) (Rolling Member Upgrades are only supported in Hazelcast Enterprise)
なお、3.8.8の方は起動に失敗してシャットダウンしてしまいます。
12 16, 2017 12:15:09 午前 com.hazelcast.security 重大: [172.20.0.1]:5702 [dev] [3.8.8] Node could not join cluster. Before join check failed node is going to shutdown now! 12 16, 2017 12:15:09 午前 com.hazelcast.security 重大: [172.20.0.1]:5702 [dev] [3.8.8] Reason of failure for node join : Joining node's version 3.8.8 is not compatible with cluster version 3.9 (Rolling Member Upgrades are only supported for the next minor version) (Rolling Member Upgrades are only supported in Hazelcast Enterprise) 12 16, 2017 12:15:09 午前 com.hazelcast.instance.Node 警告: [172.20.0.1]:5702 [dev] [3.8.8] Terminating forcefully... 12 16, 2017 12:15:09 午前 com.hazelcast.instance.Node 情報: [172.20.0.1]:5702 [dev] [3.8.8] Shutting down multicast service... 12 16, 2017 12:15:09 午前 com.hazelcast.instance.Node 情報: [172.20.0.1]:5702 [dev] [3.8.8] Shutting down connection manager... 12 16, 2017 12:15:09 午前 com.hazelcast.nio.tcp.TcpIpConnection 情報: [172.20.0.1]:5702 [dev] [3.8.8] Connection[id=1, /172.20.0.1:34082->/172.20.0.1:5701, endpoint=[172.20.0.1]:5701, alive=false, type=MEMBER] closed. Reason: TcpIpConnectionManager is stopping 12 16, 2017 12:15:09 午前 com.hazelcast.instance.Node 情報: [172.20.0.1]:5702 [dev] [3.8.8] Shutting down node engine... 12 16, 2017 12:15:09 午前 com.hazelcast.instance.NodeExtension 情報: [172.20.0.1]:5702 [dev] [3.8.8] Destroying node NodeExtension. 12 16, 2017 12:15:09 午前 com.hazelcast.instance.Node 情報: [172.20.0.1]:5702 [dev] [3.8.8] Hazelcast Shutdown is completed in 59 ms. 12 16, 2017 12:15:10 午前 com.hazelcast.instance.Node 重大: [172.20.0.1]:5702 [dev] [3.8.8] Could not join cluster in 300000 ms. Shutting down now! 12 16, 2017 12:15:10 午前 com.hazelcast.core.LifecycleService 情報: [172.20.0.1]:5702 [dev] [3.8.8] [172.20.0.1]:5702 is SHUTTING_DOWN 12 16, 2017 12:15:10 午前 com.hazelcast.instance.Node 情報: [172.20.0.1]:5702 [dev] [3.8.8] Node is already shutting down... Waiting for shutdown process to complete... 12 16, 2017 12:15:10 午前 com.hazelcast.core.LifecycleService 情報: [172.20.0.1]:5702 [dev] [3.8.8] [172.20.0.1]:5702 is SHUTDOWN 12 16, 2017 12:15:10 午前 com.hazelcast.instance.Node 警告: [172.20.0.1]:5702 [dev] [3.8.8] Config seed port is 5701 and cluster size is 1. Some of the ports seem occupied! 12 16, 2017 12:15:10 午前 com.hazelcast.util.PhoneHome 警告: [172.20.0.1]:5702 [dev] [3.8.8] Could not schedule phone home! Most probably Hazelcast is failed to start. 12 16, 2017 12:15:10 午前 com.hazelcast.instance.Node 情報: [172.20.0.1]:5702 [dev] [3.8.8] Node is already shutting down... Waiting for shutdown process to complete... Caught: java.lang.IllegalStateException: Node failed to start! java.lang.IllegalStateException: Node failed to start! at com.hazelcast.instance.HazelcastInstanceImpl.<init>(HazelcastInstanceImpl.java:135) at com.hazelcast.instance.HazelcastInstanceFactory.constructHazelcastInstance(HazelcastInstanceFactory.java:218) at com.hazelcast.instance.HazelcastInstanceFactory.newHazelcastInstance(HazelcastInstanceFactory.java:176) at com.hazelcast.instance.HazelcastInstanceFactory.newHazelcastInstance(HazelcastInstanceFactory.java:126) at com.hazelcast.core.Hazelcast.newHazelcastInstance(Hazelcast.java:87) at com.hazelcast.core.Hazelcast$newHazelcastInstance.call(Unknown Source) at hazelcast388-runner.run(hazelcast388-runner.groovy:17)
起動順序を逆にすると、3.9.1側が停止することになります。
では、Hazelcast 3.8で。
hazelcast38-runner.groovy
@Grab('com.hazelcast:hazelcast:3.8') import com.hazelcast.core.Hazelcast
## 3.8.8 $ groovy hazelcast388-runner.groovy ## 3.8 $ groovy hazelcast38-runner.groovy
こちらは、問題なくクラスタが構成できます。
12 16, 2017 12:19:53 午前 com.hazelcast.internal.cluster.ClusterService 情報: [172.20.0.1]:5701 [dev] [3.8.8] Members [2] { Member [172.20.0.1]:5701 - df07e168-d992-4bba-b7c0-c6fae1f35d30 this Member [172.20.0.1]:5702 - 86c92361-94e4-4260-ab71-9df691b458a7 }
くどくなりますが、今度は3.8.8と3.7.8。
## 3.8.8 $ groovy hazelcast388-runner.groovy ## 3.7.8 $ groovy hazelcast378-runner.groovy
3.7系が相手の場合は、Rolling Upgadeのサポートが3.8以降だからか、両方のMemberが独立して生き残ります。
3.8.8の方。互換性がないと怒られていますね。
12 16, 2017 12:21:11 午前 com.hazelcast.internal.cluster.impl.MulticastService 警告: [172.20.0.1]:5701 [dev] [3.8.8] Received a JoinRequest with an incompatible binary-format. An old version of Hazelcast may be using the same multicast discovery port. Are you running multiple Hazelcast clusters on this host? (This message will be suppressed for 60 seconds).
3.7.8の方は、単純に認識できないだけになるようです。
12 16, 2017 12:21:14 午前 com.hazelcast.internal.cluster.impl.MulticastJoiner 情報: [172.20.0.1]:5702 [dev] [3.7.8] Members [1] { Member [172.20.0.1]:5702 - b90571a9-3f82-4613-8207-aa27445b4dcf this } 12 16, 2017 12:21:14 午前 com.hazelcast.instance.Node 警告: [172.20.0.1]:5702 [dev] [3.7.8] Config seed port is 5701 and cluster size is 1. Some of the ports seem occupied!
今回は同じホストでクラスタを構成しようとしたので、TCPポートがかぶってずれましたが…。
というわけで、やっぱりMinor Versionで互換性がありません、と。Minor Versionを越えてアップデートしたければ、Enterprise版が必要だということですね。
少しだけ中身を
ClusterSafeとClusterStateがどういう実装になっているか、ちょっと確認してみましょう。
ClusterSafeは、全MemberにSafeStateCheckOperationを投げることで確認します。
https://github.com/hazelcast/hazelcast/blob/v3.9.1/hazelcast/src/main/java/com/hazelcast/internal/partition/PartitionServiceProxy.java#L117-L146
実体はInternalPartitionServiceImpl#isMemberStateSafeで、これがひとつでもfalseを返すとClusterSafeとしてはfalseとなります。全Memberがtrueに
なって、はじめてClusterSafeがtrueになりますと。
https://github.com/hazelcast/hazelcast/blob/v3.9.1/hazelcast/src/main/java/com/hazelcast/internal/partition/impl/InternalPartitionServiceImpl.java#L912-L914
InternalPartitionServiceImpl#isMemberStateSafeがtrueになるというのは、PartitionReplicaStateChecker#getPartitionServiceStateが
PartitionServiceState.SAFEを返すということで、この確認はバックアップの有無やデータのリバランス中でないかどうか、Masterが
マイグレーション中でないかどうかなどを元に確認します。
https://github.com/hazelcast/hazelcast/blob/v3.9.1/hazelcast/src/main/java/com/hazelcast/internal/partition/impl/PartitionReplicaStateChecker.java#L76-L94
ClusterStateの方は、ClusterStateManager#getSafeの結果がそのまま使われます。
https://github.com/hazelcast/hazelcast/blob/v3.9.1/hazelcast/src/main/java/com/hazelcast/internal/cluster/impl/ClusterServiceImpl.java#L955-L957
ClusterStateManagerがクラスタの状態を管理しています、と。
https://github.com/hazelcast/hazelcast/blob/v3.9.1/hazelcast/src/main/java/com/hazelcast/internal/cluster/impl/ClusterStateManager.java
まとめ
Hazelcastのクラスタの状態を確認する方法を調べたり、バージョン間の互換性についてまとめてみました。
このあたりはなんとなく把握はしていたのですが、実際に確認してみたのは初めてなので、いろいろ参考になりました。
今回作成したソースコードは、こちらに置いています。
https://github.com/kazuhira-r/hazelcast-examples/tree/master/hazelcast-cluster-safe-and-vesion-compatibility