JGroupsによる信頼できるメッセージング
1. 概要
JGroupsは、信頼性の高いメッセージ交換のためのJavaAPIです。 以下を提供するシンプルなインターフェースを備えています。
-
TCPおよびUDPを含む柔軟なプロトコルスタック
-
大きなメッセージの断片化と再構築
-
信頼できるユニキャストとマルチキャスト
-
故障検出
-
フロー制御
他の多くの機能と同様。
このチュートリアルでは、アプリケーション間でStringメッセージを交換し、新しいアプリケーションがネットワークに参加するときに共有状態を提供するための簡単なアプリケーションを作成します。
2. セットアップ
2.1. メーベン依存
pom.xmlに単一の依存関係を追加する必要があります。
org.jgroups
jgroups
4.0.10.Final
ライブラリの最新バージョンはMaven Central.で確認できます
2.2. ネットワーキング
JGroupsは、デフォルトでIPV6を使用しようとします。 システム構成によっては、これによりアプリケーションが通信できなくなる可能性があります。
これを回避するために、ここでアプリケーションを実行するときに、java.net.preferIPv4Stackをtrueプロパティに設定します。
java -Djava.net.preferIPv4Stack=true com.example.jgroups.JGroupsMessenger
3. JChannels
JGroupsネットワークへの接続はJChannel.です。チャネルはクラスターに参加し、メッセージとネットワークの状態に関する情報を送受信します。
3.1. チャネルの作成
構成ファイルへのパスを使用してJChannelを作成します。 ファイル名を省略すると、現在の作業ディレクトリでudp.xmlが検索されます。
明示的に名前が付けられた構成ファイルを使用してチャネルを作成します。
JChannel channel = new JChannel("src/main/resources/udp.xml");
JGroupsの構成は非常に複雑になる可能性がありますが、ほとんどのアプリケーションではデフォルトのUDPおよびTCP構成で十分です。 コードにUDPのファイルを含め、このチュートリアルで使用します。
トランスポートの構成の詳細については、JGroupsのマニュアルhereを参照してください。
3.2. チャネルの接続
チャネルを作成したら、クラスターに参加する必要があります。 A cluster is a group of nodes that exchange messages.
クラスターに参加するには、クラスター名が必要です。
channel.connect("example");
クラスターに参加しようとする最初のノードは、クラスターが存在しない場合にクラスターを作成します。 このプロセスの実際の動作を以下に示します。
3.3. チャネルの命名
ノードは名前で識別されるため、ピアは指示されたメッセージを送信し、クラスターに出入りするユーザーに関する通知を受信できます。 JGroupsは自動的に名前を割り当てますが、独自に設定することもできます:
channel.name("user1");
以下では、これらの名前を使用して、ノードがクラスターに出入りするタイミングを追跡します。
3.4. チャネルを閉じる
終了したという通知をピアにタイムリーに受信させたい場合は、チャネルのクリーンアップが不可欠です。
closeメソッドでJChannelを閉じます。
channel.close()
4. クラスタービューの変更
JChannelが作成されたら、クラスター内のピアの状態を確認し、それらとメッセージを交換する準備が整いました。
JGroups maintains cluster state inside the View class.各チャネルにはネットワークのViewが1つあります。 ビューが変更されると、viewAccepted()コールバックを介して配信されます。
このチュートリアルでは、アプリケーションに必要なすべてのインターフェースメソッドを実装するReceiverAdaptorAPIクラスを拡張します。
コールバックを実装するための推奨される方法です。
アプリケーションにviewAcceptedを追加しましょう。
public void viewAccepted(View newView) {
private View lastView;
if (lastView == null) {
System.out.println("Received initial view:");
newView.forEach(System.out::println);
} else {
System.out.println("Received new view.");
List newMembers = View.newMembers(lastView, newView);
System.out.println("New members: ");
newMembers.forEach(System.out::println);
List exMembers = View.leftMembers(lastView, newView);
System.out.println("Exited members:");
exMembers.forEach(System.out::println);
}
lastView = newView;
}
各Viewには、クラスターの各メンバーを表すAddressオブジェクトのListが含まれています。 JGroupsは、あるビューを別のビューと比較する便利な方法を提供します。これを使用して、クラスターの新しいメンバーまたは終了したメンバーを検出します。
5. メッセージを送信する
JGroupsでのメッセージ処理は簡単です。 Messageには、送信者と受信者に対応するbyte配列とAddressオブジェクトが含まれます。
このチュートリアルでは、コマンドラインから読み取ったStringsを使用していますが、アプリケーションが他のデータ型を交換する方法を簡単に確認できます。
5.1. ブロードキャストメッセージ
Messageは、宛先とバイト配列を使用して作成されます。 JChannelは送信者を設定します。 If the target is null, __ the entire cluster will receive the message.
コマンドラインからテキストを受け取り、それをクラスターに送信します。
System.out.print("Enter a message: ");
String line = in.readLine().toLowerCase();
Message message = new Message(null, line.getBytes());
channel.send(message);
プログラムの複数のインスタンスを実行してこのメッセージを送信すると(以下のreceive()メソッドを実装した後)、すべてのインスタンスがそれを受信します、including the sender.
5.2. メッセージのブロック
メッセージを表示したくない場合は、そのためのプロパティを設定できます。
channel.setDiscardOwnMessages(true);
前のテストを実行すると、メッセージ送信者はブロードキャストメッセージを受信しません。
5.3. ダイレクトメッセージ
ダイレクトメッセージを送信するには、有効なAddressが必要です。 ノードを名前で参照している場合は、Addressを検索する方法が必要です。 幸い、そのためのViewがあります。
現在のViewは、常にJChannelから利用できます。
private Optional getAddress(String name) {
View view = channel.view();
return view.getMembers().stream()
.filter(address -> name.equals(address.toString()))
.findAny();
}
Addressの名前は、クラスtoString()メソッドを介して使用できるため、クラスターメンバーのListで必要な名前を検索するだけです。
そのため、コンソールから名前を受け入れて、関連する宛先を見つけ、ダイレクトメッセージを送信できます。
Address destination = null;
System.out.print("Enter a destination: ");
String destinationName = in.readLine().toLowerCase();
destination = getAddress(destinationName)
.orElseThrow(() -> new Exception("Destination not found");
Message message = new Message(destination, "Hi there!");
channel.send(message);
6. メッセージを受信する
メッセージを送信できるので、今すぐ受信してみましょう。
ReceiverAdaptor’sの空の受信メソッドをオーバーライドしましょう。
public void receive(Message message) {
String line = Message received from: "
+ message.getSrc()
+ " to: " + message.getDest()
+ " -> " + message.getObject();
System.out.println(line);
}
メッセージにStringが含まれていることがわかっているので、getObject()をSystem.outに安全に渡すことができます。
7. 州の交換
ノードがネットワークに入ると、クラスターに関する状態情報を取得する必要がある場合があります。 JGroupsは、このための状態転送メカニズムを提供します。
ノードがクラスターに参加すると、単にgetState()を呼び出します。 通常、クラスターは、グループ内の最も古いメンバー(コーディネーター)から状態を取得します。
アプリケーションにブロードキャストメッセージ数を追加しましょう。 新しいメンバー変数を追加し、receive()内でインクリメントします。
private Integer messageCount = 0;
public void receive(Message message) {
String line = "Message received from: "
+ message.getSrc()
+ " to: " + message.getDest()
+ " -> " + message.getObject();
System.out.println(line);
if (message.getDest() == null) {
messageCount++;
System.out.println("Message count: " + messageCount);
}
}
ダイレクトメッセージをカウントすると、各ノードの番号が異なるため、nullの宛先を確認します。
次に、ReceiverAdaptorのさらに2つのメソッドをオーバーライドします。
public void setState(InputStream input) {
try {
messageCount = Util.objectFromStream(new DataInputStream(input));
} catch (Exception e) {
System.out.println("Error deserialing state!");
}
System.out.println(messageCount + " is the current messagecount.");
}
public void getState(OutputStream output) throws Exception {
Util.objectToStream(messageCount, new DataOutputStream(output));
}
メッセージと同様に、JGroupsは状態をbytesの配列として転送します。
JGroupsは、状態を書き込むためのInputStreamをコーディネーターに提供し、新しいノードが読み取るためのOutputStreamを提供します。 APIは、データをシリアル化および逆シリアル化するための便利なクラスを提供します。
実稼働コードでは、状態情報へのアクセスはスレッドセーフでなければならないことに注意してください。
最後に、クラスターに接続した後、getState()への呼び出しをスタートアップに追加します。
channel.connect(clusterName);
channel.getState(null, 0);
getState()は、状態を要求する宛先とミリ秒単位のタイムアウトを受け入れます。 null宛先はコーディネーターを示し、0はタイムアウトしないことを意味します。
一対のノードでこのアプリを実行し、ブロードキャストメッセージを交換すると、メッセージカウントが増加します。
次に、3番目のクライアントを追加するか、そのうちの1つを停止して開始すると、新しく接続されたノードが正しいメッセージ数を出力するのがわかります。
8. 結論
このチュートリアルでは、JGroupsを使用して、メッセージを交換するためのアプリケーションを作成しました。 APIを使用して、どのノードがクラスターに接続し、クラスターから離脱したかを監視し、クラスターに参加したときにクラスター状態を新しいノードに転送しました。
いつものように、コードサンプルはover on GitHubで見つけることができます。