Публикация и получение сообщений с помощью клиента Java Nats

Публикация и получение сообщений с помощью клиента Nats Java

1. обзор

В этом руководстве мы будем использоватьJava Client for NATs для подключения кNATS Server, публикации и получения сообщений.

NATS предлагает три основных режима обмена сообщениями. 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.

Подписчики также могут присоединяться к группам очередей сообщений при подписке на тему. Сообщения, отправленные в связанную тему, доставляются только одному подписчику в группе очередей.

2. Настроить

2.1. Maven Dependency

Во-первых, нам нужно добавить библиотеку NATS в нашpom.xml:


    io.nats
    jnats
    1.0

Последняя версия библиотекиcan be found here, а проект Github -here.

2.2. NATS сервер

Во-вторых, нам понадобится NATS-сервер для обмена сообщениями. Есть инструкции для всех основных платформhere.

Мы предполагаем, что на localhost: 4222 работает сервер.

3. Подключайтесь и обменивайтесь сообщениями

3.1. Подключиться к NATS

Методconnect() в статическом классе NATS создаетConnections.

Если мы хотим использовать соединение с параметрами по умолчанию и прослушивать локальный хост через порт 4222, мы можем использовать метод по умолчанию:

Connection natsConnection = Nats.connect();

Но уConnections есть много настраиваемых параметров, некоторые из которых мы хотим переопределить.

Мы создадим объектOptions и передадим его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. API попытается восстановить потерянное соединение.

Мы установили обратные вызовы, чтобы уведомлять нас о том, когда происходит отключение и когда соединение восстанавливается. В этом примере мы используем лямбда-выражения, но для приложений, которые должны делать больше, чем просто регистрировать событие, мы можем установить объекты, реализующие требуемые интерфейсы.

Мы можем запустить быстрый тест. Создайте соединение и добавьте спящий режим на 60 секунд, чтобы процесс продолжался:

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

Запустите это. Затем остановитесь и запустите сервер 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]

Мы можем видеть обратные вызовы, регистрирующие разъединение и переподключение.

3.2. Подписка на сообщения

Теперь, когда у нас есть соединение, мы можем работать над обработкой сообщений.

NATSMessage - это контейнер для массиваbytes[]. Помимо ожидаемых методовsetData(byte[]) иbyte[] getData(), существуют методы для настройки и получения адресата сообщения и ответа на темы.

Подписываемся на темыStrings.

NATS поддерживает как синхронные, так и асинхронные подписки.

Давайте посмотрим на асинхронную подписку:

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

API доставляетMessages нашемуMessageHandler(), в своем потоке.

Некоторые приложения могут захотеть управлять потоком, который вместо этого обрабатывает сообщения:

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

SyncSubscription имеет блокирующий методnextMessage(), который будет блокироваться на указанное количество миллисекунд. Мы будем использовать синхронные подписки для наших тестов, чтобы тесты были простыми.

УAsyncSubscription иSyncSubscription есть методunsubscribe(), который мы можем использовать для закрытия подписки.

subscription.unsubscribe();

3.3. Опубликовать сообщения

ОпубликоватьMessages можно несколькими способами.

Самый простой способ требует только темыString и сообщенияbytes:

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

Если издатель хочет получить ответ или предоставить конкретную информацию об источнике сообщения, он также может отправить сообщение с темой ответа:

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

Есть также перегрузки для нескольких других комбинаций, таких как передачаMessage вместоbytes.

3.4. Простой обмен сообщениями

Учитывая допустимыйConnection, мы можем написать тест, который проверяет обмен сообщениями:

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

Мы начинаем с подписки на две темы с синхронными подписками, так как они работают намного лучше в тесте JUnit. Затем мы отправляем сообщение одному из них, указывая другому как адресreplyTo.

После прочтения сообщения из первого пункта назначения мы «переворачиваем» темы, чтобы отправить ответ.

3.5. Подписки с подстановочными знаками

Сервер NATS поддерживает тематические символы подстановки.

Подстановочные знаки используются для лексем тем, разделенных символом «.». Символ звездочки ‘* 'соответствует отдельному токену. Символ «больше» ‘> '- это подстановочный знак для остальной части темы, который может содержать более одного токена.

Например:

  • foo. * соответствует foo.bar, foo.requests,but not foo.bar.requests

  • foo.> соответствует foo.bar, foo.requests, foo.bar.requests, foo.bar.example и т. д.

Давайте попробуем несколько тестов:

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

Наш тест обмена сообщениями напоминал обычную идиому в системах обмена сообщениями pub / sub; запрос / ответ. NATS has explicit support for this request/reply messaging.

Издатели могут установить обработчик запросов, используя метод асинхронной подписки, который мы использовали выше:

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

Или они могут отвечать на запросы по мере их поступления.

API предоставляет методrequest():

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

Этот метод создает временный почтовый ящик для ответа и пишет адрес для ответа для нас.

Request() возвращает ответ илиnull, если время ожидания запроса истекло. Последний аргумент - это количество миллисекунд ожидания.

Мы можем изменить наш тест для запроса / ответа:

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. Очереди сообщений

Подписчики могут указывать группы очередей во время подписки. 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. Если слушателей нет, сообщение отбрасывается.

5.1. Подписка на очереди

Подписчики указывают имя группы очередей какString:

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

Конечно, есть и асинхронная версия:

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

Подписка создает очередь на сервере NATS.

5.2. Публикация в очередях

Публикация сообщения в группах очередей просто требует публикации в соответствующей теме:

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

Сервер NATS направит сообщение в очередь и выберет получателя сообщения.

Мы можем проверить это с помощью теста:

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

Мы получаем только одно сообщение.

Если мы изменим первые две строки на обычную подписку:

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

Тест не пройден, поскольку сообщение доставляется обоим подписчикам.

6. Заключение

В этом кратком введении мы подключились к серверу NATS и отправили как сообщения pub / sub, так и сообщения очереди с балансировкой нагрузки. Мы рассмотрели поддержку NATS для групповых подписок. Мы также использовали запрос / ответ.

Примеры кода, как всегда, можно найтиover on GitHub.