Zuverlässiges Messaging mit JGroups

Zuverlässiges Messaging mit JGroups

1. Überblick

JGroups ist eine Java-API für den zuverlässigen Nachrichtenaustausch. Es verfügt über eine einfache Oberfläche, die Folgendes bietet:

  • ein flexibler Protokollstapel, einschließlich TCP und UDP

  • Fragmentierung und Zusammenstellung großer Nachrichten

  • zuverlässiges Unicast und Multicast

  • Fehlererkennung

  • Ablaufsteuerung

Sowie viele andere Funktionen.

In diesem Lernprogramm erstellen wir eine einfache Anwendung zum Austauschen vonString-Nachrichten zwischen Anwendungen und zum Bereitstellen des gemeinsamen Status für neue Anwendungen, wenn diese dem Netzwerk beitreten.

2. Konfiguration

2.1. Maven-Abhängigkeit

Wir müssen unserenpom.xml eine einzige Abhängigkeit hinzufügen:


    org.jgroups
    jgroups
    4.0.10.Final

Die neueste Version der Bibliothek kann aufMaven Central. überprüft werden

2.2. Vernetzung

JGroups versucht standardmäßig,IPV6 zu verwenden. Abhängig von unserer Systemkonfiguration kann dies dazu führen, dass Anwendungen nicht kommunizieren können.

Um dies zu vermeiden, setzen wir die Eigenschaftjava.net.preferIPv4Stack auftrue, wenn wir unsere Anwendungen hier ausführen:

java -Djava.net.preferIPv4Stack=true com.example.jgroups.JGroupsMessenger

3. JChannels

Unsere Verbindung zu einem JGroups-Netzwerk beträgtJChannel. Der Kanal tritt einem Cluster auf und sendet und empfängt Nachrichten sowie Informationen über den Zustand des Netzwerks.

3.1. Einen Kanal erstellen

Wir erstellen einJChannel mit einem Pfad zu einer Konfigurationsdatei. Wenn wir den Dateinamen weglassen, wird im aktuellen Arbeitsverzeichnis nachudp.xml gesucht.

Wir erstellen einen Kanal mit einer explizit benannten Konfigurationsdatei:

JChannel channel = new JChannel("src/main/resources/udp.xml");

Die Konfiguration von JGroups kann sehr kompliziert sein, aber die Standard-UDP- und TCP-Konfigurationen sind für die meisten Anwendungen ausreichend. Wir haben die Datei für UDP in unseren Code aufgenommen und werden sie für dieses Tutorial verwenden.

Weitere Informationen zum Konfigurieren des Transports finden Sie im JGroups-Handbuchhere.

3.2. Kanal anschließen

Nachdem wir unseren Kanal erstellt haben, müssen wir einem Cluster beitreten. A cluster is a group of nodes that exchange messages.

Der Beitritt zu einem Cluster erfordert einen Clusternamen:

channel.connect("example");

Der erste Knoten, der versucht, einem Cluster beizutreten, erstellt ihn, wenn er nicht vorhanden ist. Wir werden diesen Prozess unten in Aktion sehen.

3.3. Einen Kanal benennen

Knoten werden durch einen Namen identifiziert, sodass Peers gerichtete Nachrichten senden und Benachrichtigungen darüber erhalten können, wer den Cluster betritt und verlässt. JGroups vergibt automatisch einen Namen oder wir können unseren eigenen vergeben:

channel.name("user1");

Wir werden diese Namen unten verwenden, um zu verfolgen, wann Knoten den Cluster betreten und verlassen.

3.4. Kanal schließen

Die Bereinigung von Kanälen ist wichtig, wenn Peers rechtzeitig benachrichtigt werden sollen, dass wir beendet wurden.

Wir schließen einJChannel mit seiner Schließmethode:

channel.close()

4. Änderungen an der Clusteransicht

Nachdem ein JChannel erstellt wurde, können wir jetzt den Status der Peers im Cluster anzeigen und Nachrichten mit ihnen austauschen.

JGroups maintains cluster state inside the View class. Jeder Kanal hat ein einzelnesView des Netzwerks. Wenn sich die Ansicht ändert, wird sie über den Rückruf vonviewAccepted()übermittelt.

In diesem Lernprogramm erweitern wir die API-KlasseReceiverAdaptor, die alle für eine Anwendung erforderlichen Schnittstellenmethoden implementiert.

Dies ist die empfohlene Methode zum Implementieren von Rückrufen.

Fügen wir unserer AnwendungviewAccepted hinzu:

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; }

JedesView enthältList vonAddress Objekten, die jedes Mitglied des Clusters darstellen. JGroups bietet praktische Methoden zum Vergleichen einer Ansicht mit einer anderen, mit denen wir neue oder aus dem Cluster ausgetretene Mitglieder erkennen.

5. Nachrichten senden

Die Verarbeitung von Nachrichten in JGroups ist unkompliziert. EinMessage enthält einbyte Array undAddress Objekte, die dem Sender und dem Empfänger entsprechen.

In diesem Lernprogramm verwenden wirStrings, die über die Befehlszeile gelesen werden. Es ist jedoch leicht zu erkennen, wie eine Anwendung andere Datentypen austauschen kann.

5.1. Broadcast-Nachrichten

EinMessage wird mit einem Ziel und einem Bytearray erstellt. JChannel legt den Absender für uns fest. If the target is null, __ the entire cluster will receive the message.

Wir akzeptieren Text von der Befehlszeile und senden ihn an den Cluster:

System.out.print("Enter a message: ");
String line = in.readLine().toLowerCase();
Message message = new Message(null, line.getBytes());
channel.send(message);

Wenn wir mehrere Instanzen unseres Programms ausführen und diese Nachricht senden (nachdem wir die unten stehende Methodereceive() implementiert haben), erhalten sie alleincluding the sender.

5.2. Blockieren unserer Nachrichten

Wenn wir unsere Nachrichten nicht sehen möchten, können wir eine Eigenschaft dafür festlegen:

channel.setDiscardOwnMessages(true);

Wenn wir den vorherigen Test ausführen, erhält der Absender der Nachricht seine Broadcast-Nachricht nicht.

5.3. Direktnachrichten

Das Senden einer Direktnachricht erfordert ein gültigesAddress. Wenn wir uns namentlich auf Knoten beziehen, benötigen wir eine Möglichkeit,Address nachzuschlagen. Zum Glück haben wir dieView dafür.

Das aktuelleView ist immer aus demJChannel verfügbar:

private Optional
getAddress(String name) { View view = channel.view(); return view.getMembers().stream() .filter(address -> name.equals(address.toString())) .findAny(); }

Die Namen vonAddressind über die Methode der KlassetoString()verfügbar, daher durchsuchen wir lediglich dieList der Clustermitglieder nach dem gewünschten Namen.

So können wir einen Namen von der Konsole annehmen, das zugehörige Ziel finden und eine direkte Nachricht senden:

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. Nachrichten empfangen

Wir können Nachrichten senden. Versuchen wir nun, sie jetzt zu empfangen.

Überschreiben wir die leere Empfangsmethode vonReceiverAdaptor’s:

public void receive(Message message) {
    String line = Message received from: "
      + message.getSrc()
      + " to: " + message.getDest()
      + " -> " + message.getObject();
    System.out.println(line);
}

Da wir wissen, dass die NachrichtString enthält, können wirgetObject() sicher anSystem.out übergeben.

7. State Exchange

Wenn ein Knoten in das Netzwerk eintritt, muss er möglicherweise Statusinformationen zum Cluster abrufen. JGroups stellt hierfür einen Zustandsübertragungsmechanismus zur Verfügung.

Wenn ein Knoten dem Cluster beitritt, ruft er einfachgetState() auf. Der Cluster ruft normalerweise den Status des ältesten Mitglieds der Gruppe ab - des Koordinators.

Fügen wir unserer Anwendung eine Anzahl von Broadcast-Nachrichten hinzu. Wir fügen eine neue Mitgliedsvariable hinzu und erhöhen sie innerhalb vonreceive():

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);
    }
}

Wir suchen nach einemnull-Ziel, denn wenn wir Direktnachrichten zählen, hat jeder Knoten eine andere Nummer.

Als nächstes überschreiben wir zwei weitere Methoden inReceiverAdaptor:

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));
}

Ähnlich wie bei Nachrichten überträgt JGroups den Status als Array vonbytes.

JGroups liefert dem Koordinator einInputStream zum Schreiben des Status und einOutputStream zum Lesen des neuen Knotens. Die API bietet Komfortklassen zum Serialisieren und Deserialisieren der Daten.

Beachten Sie, dass der Zugriff auf Statusinformationen im Produktionscode threadsicher sein muss.

Schließlich fügen wir den Aufruf vongetState() zu unserem Start hinzu, nachdem wir eine Verbindung zum Cluster hergestellt haben:

channel.connect(clusterName);
channel.getState(null, 0);

getState() akzeptiert ein Ziel, von dem aus der Status angefordert werden soll, und eine Zeitüberschreitung in Millisekunden. Das Ziel vonnullgibt den Koordinator an und 0 bedeutet, dass keine Zeitüberschreitung auftritt.

Wenn wir diese App mit einem Knotenpaar ausführen und Broadcast-Nachrichten austauschen, sehen wir, dass die Anzahl der Nachrichten zunimmt.

Wenn wir dann einen dritten Client hinzufügen oder einen von ihnen stoppen und starten, wird der neu verbundene Knoten die richtige Anzahl von Nachrichten drucken.

8. Fazit

In diesem Tutorial haben wir mit JGroups eine Anwendung zum Austausch von Nachrichten erstellt. Wir haben die API verwendet, um zu überwachen, welche Knoten mit dem Cluster verbunden sind und diesen verlassen haben, und um den Clusterstatus auf einen neuen Knoten zu übertragen, wenn dieser dem Cluster beigetreten ist.

Codebeispiele finden sich wie immer inover on GitHub.