Publier et recevoir des messages avec le client Java Nats

Publier et recevoir des messages avec le client Java Nats

1. Vue d'ensemble

Dans ce didacticiel, nous utiliserons lesJava Client for NATs pour nous connecter à unNATS Server et publier et recevoir des messages.

NATS offre trois modes principaux d’échange de messages. Publish/Subscribe semantics delivers messages to all subscribers of a topic. Request/Reply messaging sends requests via topics and routes responses back to the requestor.

Les abonnés peuvent également rejoindre des groupes de files d'attente de messages lorsqu'ils s'abonnent à un sujet. Les messages envoyés au sujet associé ne sont remis qu'à un seul abonné du groupe de files d'attente.

2. Installer

2.1. Dépendance Maven

Tout d'abord, nous devons ajouter la bibliothèque NATS à nospom.xml:


    io.nats
    jnats
    1.0

La dernière version de la bibliothèquecan be found here et du projet Github esthere.

2.2. Serveur NATS

Deuxièmement, nous aurons besoin d’un serveur NATS pour échanger des messages. Il existe des instructions pour toutes les principales plates-formeshere.

Nous supposons qu’un serveur s’exécute sur localhost: 4222.

3. Se connecter et échanger des messages

3.1. Connectez-vous à NATS

La méthodeconnect() de la classe NATS statique crée desConnections.

Si nous voulons utiliser une connexion avec des options par défaut et écouter sur localhost sur le port 4222, nous pouvons utiliser la méthode par défaut:

Connection natsConnection = Nats.connect();

MaisConnections a de nombreuses options configurables, dont certaines que nous voulons remplacer.

Nous allons créer un objetOptions et le transmettre àNats:

private Connection initConnection() {
    Options options = new Options.Builder()
      .errorCb(ex -> log.error("Connection Exception: ", ex))
      .disconnectedCb(event -> log.error("Channel disconnected: {}", event.getConnection()))
      .reconnectedCb(event -> log.error("Reconnected to server: {}", event.getConnection()))
      .build();

    return Nats.connect(uri, options);
}

NATS Connections are durable. L'API tentera de reconnecter une connexion perdue.

Nous avons installé des rappels pour nous informer du moment où une déconnexion se produit et lorsque la connexion est rétablie. Dans cet exemple, nous utilisons des lambdas, mais pour les applications qui doivent faire plus que simplement consigner l'événement, nous pouvons installer des objets qui implémentent les interfaces requises.

Nous pouvons faire un test rapide. Créez une connexion et ajoutez une veille pendant 60 secondes pour que le processus continue:

Connection natsConnection = initConnection();
Thread.sleep(60000);

Lance ça. Puis arrêtez et démarrez votre serveur NATS:

[jnats-callbacks] ERROR com.example.nats.NatsClient
  - Channel disconnected: [email protected]
[reconnect] WARN io.nats.client.ConnectionImpl
  - couldn't connect to nats://localhost:4222 (nats: connection read error)
[jnats-callbacks] ERROR com.example.nats.NatsClient
  - Reconnected to server: [email protected]

Nous pouvons voir que les callbacks enregistrent la déconnexion et se reconnectent.

3.2. Abonnez-vous aux messages

Maintenant que nous avons une connexion, nous pouvons travailler sur le traitement des messages.

Un NATSMessage est un conteneur pour un tableau debytes[]. En plus des méthodessetData(byte[]) etbyte[] getData() attendues, il existe des méthodes pour définir et obtenir la destination du message et répondre aux sujets.

Nous nous abonnons à des sujets, qui sontStrings.

NATS prend en charge les abonnements synchrones et asynchrones.

Jetons un coup d'œil à un abonnement asynchrone:

AsyncSubscription subscription = natsConnection
  .subscribe( topic, msg -> log.info("Received message on {}", msg.getSubject()));

L'API délivreMessages à nosMessageHandler(), dans son thread.

Certaines applications peuvent vouloir contrôler le thread qui traite les messages à la place:

SyncSubscription subscription = natsConnection.subscribeSync("foo.bar");
Message message = subscription.nextMessage(1000);

SyncSubscription a une méthode de blocagenextMessage() qui bloquera pendant le nombre de millisecondes spécifié. Nous utiliserons des abonnements synchrones pour nos tests afin de simplifier les cas de test.

AsyncSubscription etSyncSubscription ont tous deux une méthodeunsubscribe() que nous pouvons utiliser pour fermer l'abonnement.

subscription.unsubscribe();

3.3. Publier des messages

La publication deMessages peut être effectuée de plusieurs manières.

La méthode la plus simple ne nécessite qu'un sujetString et le messagebytes:

natsConnection.publish("foo.bar", "Hi there!".getBytes());

Si un éditeur souhaite une réponse ou fournir des informations spécifiques sur la source d'un message, il peut également envoyer un message avec un sujet de réponse:

natsConnection.publish("foo.bar", "bar.foo", "Hi there!".getBytes());

Il existe également des surcharges pour quelques autres combinaisons telles que le passage d'unMessage au lieu debytes.

3.4. Un échange de messages simple

Étant donné unConnection valide, nous pouvons écrire un test qui vérifie l'échange de messages:

SyncSubscription fooSubscription = natsConnection.subscribe("foo.bar");
SyncSubscription barSubscription = natsConnection.subscribe("bar.foo");
natsConnection.publish("foo.bar", "bar.foo", "hello there".getBytes());

Message message = fooSubscription.nextMessage();
assertNotNull("No message!", message);
assertEquals("hello there", new String(message.getData()));

natsConnection
  .publish(message.getReplyTo(), message.getSubject(), "hello back".getBytes());

message = barSubscription.nextMessage();
assertNotNull("No message!", message);
assertEquals("hello back", new String(message.getData()));

Nous commençons par nous abonner à deux sujets avec des abonnements synchrones car ils fonctionnent beaucoup mieux dans un test JUnit. Ensuite, nous envoyons un message à l'un d'eux, en spécifiant l'autre comme une adressereplyTo.

Après avoir lu le message de la première destination, nous «retournons» les sujets pour envoyer une réponse.

3.5. Abonnements Wildcard

Le serveur NATS prend en charge les caractères génériques de sujet.

Les caractères génériques opèrent sur des jetons de sujet séparés par le caractère ’.’. Le caractère astérisque «*» correspond à un jeton individuel. Le symbole supérieur à '>' est une correspondance générique pour le reste d'un sujet, qui peut être plus d'un jeton.

Par exemple:

  • foo. * correspond à foo.bar, foo.requests,but not foo.bar.requests

  • foo.> correspond à foo.bar, foo.requests, foo.bar.requests, foo.bar.example, etc.

Essayons quelques tests:

SyncSubscription fooSubscription = client.subscribeSync("foo.*");

client.publishMessage("foo.bar", "bar.foo", "hello there");

Message message = fooSubscription.nextMessage(200);
assertNotNull("No message!", message);
assertEquals("hello there", new String(message.getData()));

client.publishMessage("foo.bar.plop", "bar.foo", "hello there");
message = fooSubscription.nextMessage(200);
assertNull("Got message!", message);

SyncSubscription barSubscription = client.subscribeSync("foo.>");

client.publishMessage("foo.bar.plop", "bar.foo", "hello there");

message = barSubscription.nextMessage(200);
assertNotNull("No message!", message);
assertEquals("hello there", new String(message.getData()));

4. Request/Reply Messaging

Notre test d’échange de messages ressemblait à un idiome courant sur les systèmes de messagerie pub / sub; demande / réponse. NATS has explicit support for this request/reply messaging.

Les éditeurs peuvent installer un gestionnaire de demandes à l'aide de la méthode d'abonnement asynchrone que nous avons utilisée ci-dessus:

AsyncSubscription subscription = natsConnection
  .subscribe("foo.bar.requests", new MessageHandler() {
    @Override
    public void onMessage(Message msg) {
        natsConnection.publish(message.getReplyTo(), reply.getBytes());
    }
});

Ou ils peuvent répondre aux demandes dès leur arrivée.

L'API fournit une méthoderequest():

Message reply = natsConnection.request("foo.bar.requests", request.getBytes(), 100);

Cette méthode crée une boîte aux lettres temporaire pour la réponse et écrit l'adresse de réponse pour nous.

Request() renvoie la réponse, ounull si la demande expire. Le dernier argument est le nombre de millisecondes à attendre.

Nous pouvons modifier notre test pour demande / réponse:

natsConnection.subscribe(salary.requests", message -> {
    natsConnection.publish(message.getReplyTo(), "denied!".getBytes());
});
Message reply = natsConnection.request("salary.requests", "I need a raise.", 100);
assertNotNull("No message!", reply);
assertEquals("denied!", new String(reply.getData()));

5. Files d'attente de messages

Les abonnés peuvent spécifier des groupes de files d'attente au moment de l'abonnement. When a message is published to the group NATS will deliver it to a one-and-only-one subscriber.

Queue groups do not persist messages. Si aucun écouteur n'est disponible, le message est ignoré.

5.1. S'abonner aux files d'attente

Les abonnés spécifient un nom de groupe de files d'attente en tant queString:

SyncSubscription subscription = natsConnection.subscribe("topic", "queue name");

Il existe aussi une version asynchrone, bien sûr:

SyncSubscription subscription = natsConnection
  .subscribe("topic", "queue name", new MessageHandler() {
    @Override
    public void onMessage(Message msg) {
        log.info("Received message on {}", msg.getSubject());
    }
});

L'abonnement crée la file d'attente sur le serveur NATS.

5.2. Publication dans les files d'attente

La publication d'un message dans des groupes de files d'attente nécessite simplement la publication dans la rubrique associée:

natsConnection.publish("foo",  "queue message".getBytes());

Le serveur NATS acheminera le message vers la file d'attente et sélectionnera un destinataire du message.

Nous pouvons le vérifier avec un test:

SyncSubscription queue1 = natsConnection.subscribe("foo", "queue name");
SyncSubscription queue2 = natsConnection.subscribe("foo", "queue name");

natsConnection.publish("foo", "foobar".getBytes());

List messages = new ArrayList<>();

Message message = queue1.nextMessage(200);
if (message != null) messages.add(message);

message = queue2.nextMessage(200);
if (message != null) messages.add(message);

assertEquals(1, messages.size());

Nous ne recevons qu'un message.

Si nous changeons les deux premières lignes en un abonnement normal:

SyncSubscription queue1 = natsConnection.subscribe("foo");
SyncSubscription queue2 = natsConnection.subscribe("foo");

Le test échoue car le message est remis aux deux abonnés.

6. Conclusion

Dans cette brève introduction, nous nous sommes connectés à un serveur NATS et avons envoyé à la fois des messages pub / sous et des messages en file d'attente équilibrés. Nous avons examiné la prise en charge par NATS des abonnements génériques. Nous avons également utilisé la messagerie demande / réponse.

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