Publicar e receber mensagens com o Nats Java Client
1. Visão geral
Neste tutorial, usaremosJava Client for NATs para se conectar a umNATS Servere publicar e receber mensagens.
O NATS oferece três modos principais de troca de mensagens. 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.
Os assinantes também podem ingressar em grupos de filas de mensagens quando assinam um tópico. As mensagens enviadas para o tópico associado são entregues apenas a um assinante no grupo de filas.
2. Configuração
2.1. Dependência do Maven
Primeiro, precisamos adicionar a biblioteca NATS ao nossopom.xml:
io.nats
jnats
1.0
A versão mais recente da bibliotecacan be found here e do projeto Github éhere.
2.2. Servidor NATS
Em segundo lugar, precisaremos de um servidor NATS para troca de mensagens. Existem instruções para todas as principais plataformashere.
Presumimos que haja um servidor em execução no localhost: 4222.
3. Conectar e trocar mensagens
3.1. Conecte-se ao NATS
O métodoconnect() na classe NATS estática criaConnections.
Se quisermos usar uma conexão com opções padrão e ouvir localhost na porta 4222, podemos usar o método padrão:
Connection natsConnection = Nats.connect();
MasConnections tem muitas opções configuráveis, algumas das quais queremos substituir.
Vamos criar um objetoOptions e passá-lo paraNats:
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. A API tentará reconectar uma conexão perdida.
Instalamos callbacks para nos notificar quando ocorrer uma desconexão e quando a conexão for restaurada. Neste exemplo, estamos usando lambdas, mas para aplicativos que precisam fazer mais do que simplesmente registrar o evento, podemos instalar objetos que implementam as interfaces necessárias.
Podemos fazer um teste rápido. Crie uma conexão e adicione um sono por 60 segundos para manter o processo em execução:
Connection natsConnection = initConnection();
Thread.sleep(60000);
Rode isto. Então pare e inicie o servidor 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]
Podemos ver os retornos de chamada registrar a desconexão e reconectar.
3.2. Assinar mensagens
Agora que temos uma conexão, podemos trabalhar no processamento de mensagens.
Um NATSMessage é um contêiner para uma matriz debytes[]. Além dos métodossetData(byte[]) ebyte[] getData() esperados, existem métodos para definir e obter o destino da mensagem e responder aos tópicos.
Nós assinamos tópicos, que sãoStrings.
O NATS oferece suporte a assinaturas síncronas e assíncronas.
Vamos dar uma olhada em uma assinatura assíncrona:
AsyncSubscription subscription = natsConnection
.subscribe( topic, msg -> log.info("Received message on {}", msg.getSubject()));
A API entregaMessages para nossoMessageHandler(), em seu segmento.
Alguns aplicativos podem querer controlar o encadeamento que processa as mensagens:
SyncSubscription subscription = natsConnection.subscribeSync("foo.bar");
Message message = subscription.nextMessage(1000);
SyncSubscription tem um método de bloqueionextMessage() que irá bloquear pelo número especificado de milissegundos. Usaremos assinaturas síncronas para nossos testes para manter os casos de teste simples.
AsyncSubscription eSyncSubscription têm um métodounsubscribe() que podemos usar para fechar a assinatura.
subscription.unsubscribe();
3.3. Publicar mensagens
PublicarMessages pode ser feito de várias maneiras.
O método mais simples requer apenas um tópicoStringe a mensagembytes:
natsConnection.publish("foo.bar", "Hi there!".getBytes());
Se um editor deseja uma resposta ou fornecer informações específicas sobre a origem de uma mensagem, ele também pode enviar uma mensagem com um tópico para resposta:
natsConnection.publish("foo.bar", "bar.foo", "Hi there!".getBytes());
Existem também sobrecargas para algumas outras combinações, como passarMessage em vez debytes.
3.4. Uma troca de mensagens simples
Dado umConnection válido, podemos escrever um teste que verifica a troca de mensagens:
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()));
Começamos assinando dois tópicos com assinaturas síncronas, pois elas funcionam muito melhor dentro de um teste JUnit. Em seguida, enviamos uma mensagem para um deles, especificando o outro como endereçoreplyTo.
Depois de ler a mensagem do primeiro destino, invertemos os tópicos para enviar uma resposta.
3.5. Assinaturas curinga
O servidor NATS suporta caracteres curinga do tópico.
Os curingas operam em tokens de tópicos separados pelo caractere "." O caractere asterisco ‘* 'corresponde a um token individual. O símbolo maior que ‘> 'é uma correspondência curinga para o restante de um tópico, que pode ser mais de um token.
Por exemplo:
-
foo. * corresponde a foo.bar, foo.requests,but not foo.bar.requests
-
foo.> corresponde a foo.bar, foo.requests, foo.bar.requests, foo.bar.example, etc.
Vamos tentar alguns testes:
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
Nosso teste de troca de mensagens se assemelhava a um idioma comum nos sistemas de mensagens de pub / sub; pedido / resposta. NATS has explicit support for this request/reply messaging.
Os editores podem instalar um manipulador de solicitações usando o método de assinatura assíncrona que usamos acima:
AsyncSubscription subscription = natsConnection
.subscribe("foo.bar.requests", new MessageHandler() {
@Override
public void onMessage(Message msg) {
natsConnection.publish(message.getReplyTo(), reply.getBytes());
}
});
Ou eles podem responder aos pedidos assim que chegarem.
A API fornece um métodorequest():
Message reply = natsConnection.request("foo.bar.requests", request.getBytes(), 100);
Este método cria uma caixa de correio temporária para a resposta e escreve o endereço de resposta para nós.
Request() retorna a resposta ounull se a solicitação expirar. O último argumento é o número de milissegundos a aguardar.
Podemos modificar nosso teste para solicitação / resposta:
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. Filas de mensagens
Os assinantes podem especificar grupos de filas no momento da assinatura. 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. Se não houver ouvintes disponíveis, a mensagem será descartada.
5.1. Inscrever-se em filas
Os assinantes especificam um nome de grupo de filas comoString:
SyncSubscription subscription = natsConnection.subscribe("topic", "queue name");
Há também uma versão assíncrona, é claro:
SyncSubscription subscription = natsConnection
.subscribe("topic", "queue name", new MessageHandler() {
@Override
public void onMessage(Message msg) {
log.info("Received message on {}", msg.getSubject());
}
});
A assinatura cria a fila no servidor NATS.
5.2. Publicação em filas
A publicação de mensagens em grupos de filas simplesmente requer publicação no tópico associado:
natsConnection.publish("foo", "queue message".getBytes());
O servidor NATS encaminhará a mensagem para a fila e selecionará um receptor de mensagem.
Podemos verificar isso com um teste:
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());
Recebemos apenas uma mensagem.
Se alterarmos as duas primeiras linhas para uma assinatura normal:
SyncSubscription queue1 = natsConnection.subscribe("foo");
SyncSubscription queue2 = natsConnection.subscribe("foo");
O teste falha porque a mensagem é entregue aos dois assinantes.
6. Conclusão
Nesta breve introdução, nos conectamos a um servidor NATS e enviamos mensagens de pub / sub e mensagens de fila com balanceamento de carga. Analisamos o suporte do NATS para assinaturas de curingas. Também usamos mensagens de solicitação / resposta.
Amostras de código, como sempre, podem ser encontradasover on GitHub.