Messagerie fiable avec JGroups

Messagerie fiable avec JGroups

1. Vue d'ensemble

JGroups est une API Java pour un échange de messages fiable. Il présente une interface simple qui fournit:

  • une pile de protocoles flexible, incluant TCP et UDP

  • fragmentation et réassemblage de gros messages

  • monodiffusion et multidiffusion fiables

  • détection d'échec

  • contrôle de flux

Ainsi que de nombreuses autres fonctionnalités.

Dans ce didacticiel, nous allons créer une application simple pour échanger les messagesString entre les applications et fournir un état partagé aux nouvelles applications lorsqu'elles rejoignent le réseau.

2. Installer

2.1. Dépendance Maven

Nous devons ajouter une seule dépendance à nospom.xml:


    org.jgroups
    jgroups
    4.0.10.Final

La dernière version de la bibliothèque peut être vérifiée surMaven Central.

2.2. La mise en réseau

JGroups essaiera d'utiliserIPV6 par défaut. Selon la configuration de notre système, les applications pourraient ne pas être en mesure de communiquer.

Pour éviter cela, nous allons définir la propriétéjava.net.preferIPv4Stack surtrue lors de l'exécution de nos applications ici:

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

3. JChannels

Notre connexion à un réseau JGroups est unJChannel. Le canal rejoint un cluster et envoie et reçoit des messages, ainsi que des informations sur l'état du réseau.

3.1. Créer une chaîne

Nous créons unJChannel avec un chemin vers un fichier de configuration. Si nous omettons le nom du fichier, il rechercheraudp.xml dans le répertoire de travail actuel.

Nous allons créer une chaîne avec un fichier de configuration explicitement nommé:

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

La configuration de JGroups peut être très compliquée, mais les configurations UDP et TCP par défaut sont suffisantes pour la plupart des applications. Nous avons inclus le fichier pour UDP dans notre code et nous l'utiliserons pour ce didacticiel.

Pour plus d'informations sur la configuration du transport, consultez le manuel JGroupshere.

3.2. Connexion d'un canal

Une fois que nous avons créé notre chaîne, nous devons rejoindre un cluster. A cluster is a group of nodes that exchange messages.

Rejoindre un cluster nécessite un nom de cluster:

channel.connect("example");

Le premier nœud qui tente de rejoindre un cluster le crée s'il n'existe pas. Nous verrons ce processus en action ci-dessous.

3.3. Nommer une chaîne

Les nœuds sont identifiés par un nom afin que les homologues puissent envoyer des messages dirigés et recevoir des notifications sur les personnes entrant et sortant du cluster. JGroups attribuera un nom automatiquement, ou nous pouvons définir le nôtre:

channel.name("user1");

Nous utiliserons ces noms ci-dessous pour suivre le moment où les nœuds entrent et quittent le cluster.

3.4. Fermer une chaîne

Le nettoyage des canaux est essentiel si nous voulons que les pairs reçoivent une notification en temps opportun de notre sortie.

On ferme unJChannel avec sa méthode close:

channel.close()

4. Modifications de la vue du cluster

Avec un JChannel créé, nous sommes maintenant prêts à voir l'état des pairs dans le cluster et à échanger des messages avec eux.

JGroups maintains cluster state inside the View class. Chaque canal possède un seulView du réseau. Lorsque la vue change, elle est envoyée via le rappel deviewAccepted().

Pour ce tutoriel, nous allons étendre la classe APIReceiverAdaptor qui implémente toutes les méthodes d'interface requises pour une application.

C'est la méthode recommandée pour mettre en œuvre les rappels.

AjoutonsviewAccepted à notre application:

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

ChaqueView contient unList d'objetsAddress, représentant chaque membre du cluster. JGroups offre des méthodes pratiques pour comparer une vue à une autre, que nous utilisons pour détecter les membres nouveaux ou sortis du cluster.

5. Envoi de messages

La gestion des messages dans JGroups est simple. UnMessage contient un tableaubyte et des objetsAddress correspondant à l'expéditeur et au destinataire.

Pour ce didacticiel, nous utilisonsStrings read à partir de la ligne de commande, mais il est facile de voir comment une application peut échanger d'autres types de données.

5.1. Diffuser des messages

UnMessage est créé avec une destination et un tableau d'octets; JChannel définit l'expéditeur pour nous. If the target is null, __ the entire cluster will receive the message.

Nous accepterons le texte de la ligne de commande et l'envoyons au cluster:

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

Si nous exécutons plusieurs instances de notre programme et envoyons ce message (après avoir implémenté la méthodereceive() ci-dessous), elles le recevront toutes,including the sender.

5.2. Bloquer nos messages

Si nous ne voulons pas voir nos messages, nous pouvons définir une propriété pour cela:

channel.setDiscardOwnMessages(true);

Lorsque nous exécutons le test précédent, l'expéditeur du message ne reçoit pas son message de diffusion.

5.3. Messages directs

L'envoi d'un message privé nécessite unAddress valide. Si nous faisons référence aux nœuds par leur nom, nous avons besoin d'un moyen de rechercher unAddress. Heureusement, nous avons lesView pour cela.

LeView courant est toujours disponible à partir desJChannel:

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

Les noms deAddress sont disponibles via la méthode de classetoString(), donc nous cherchons simplement lesList des membres du cluster pour le nom que nous voulons.

Nous pouvons donc accepter un nom depuis la console, trouver la destination associée et envoyer un message direct:

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. Recevoir des messages

Nous pouvons envoyer des messages, ajoutons maintenant pour essayer de les recevoir maintenant.

Remplaçons la méthode de réception vide deReceiverAdaptor’s:

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

Puisque nous savons que le message contient unString, nous pouvons passer en toute sécuritégetObject() àSystem.out.

7. Échange d'État

Lorsqu'un nœud entre sur le réseau, il peut être nécessaire de récupérer des informations d'état sur le cluster. JGroups fournit un mécanisme de transfert d'état pour cela.

Lorsqu'un nœud rejoint le cluster, il appelle simplementgetState(). Le cluster récupère généralement l'état du membre le plus ancien du groupe, le coordinateur.

Ajoutons un nombre de messages de diffusion à notre application. Nous allons ajouter une nouvelle variable membre et l'incrémenter dansreceive():

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

Nous recherchons une destinationnullcar si nous comptons les messages directs, chaque nœud aura un numéro différent.

Ensuite, nous remplaçons deux autres méthodes dansReceiverAdaptor:

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

Similaire aux messages, JGroups transfère l'état sous la forme d'un tableau debytes.

JGroups fournit unInputStream au coordinateur pour écrire l'état et unOutputStream pour le nouveau nœud à lire. L'API fournit des classes de commodité pour la sérialisation et la désérialisation des données.

Notez qu'en code de production, l'accès aux informations d'état doit être thread-safe.

Enfin, nous ajoutons l'appel àgetState() à notre démarrage, après nous être connectés au cluster:

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

getState() accepte une destination à partir de laquelle demander l'état et un délai en millisecondes. Une destinationnull indique le coordinateur et 0 signifie ne pas expirer.

Lorsque nous exécutons cette application avec une paire de nœuds et échangeons des messages à diffusion générale, nous voyons l'incrémentation du nombre de messages.

Ensuite, si nous ajoutons un troisième client ou arrêtons et démarrons l'un d'entre eux, nous verrons le nœud nouvellement connecté imprimer le nombre de messages correct.

8. Conclusion

Dans ce tutoriel, nous avons utilisé JGroups pour créer une application permettant d’échanger des messages. Nous avons utilisé l'API pour surveiller les nœuds connectés et sortants du cluster, ainsi que pour transférer l'état du cluster vers un nouveau nœud lors de son inscription.

Des échantillons de code, comme toujours, peuvent être trouvésover on GitHub.