Sistema de Mensagens Confiável com JGroups

Sistema de Mensagens Confiável com JGroups

1. Visão geral

JGroups é uma API Java para troca confiável de mensagens. Possui uma interface simples que fornece:

  • uma pilha de protocolos flexível, incluindo TCP e UDP

  • fragmentação e remontagem de mensagens grandes

  • unicast e multicast confiáveis

  • detecção de falhas

  • controle de fluxo

Bem como muitos outros recursos.

Neste tutorial, criaremos um aplicativo simples para trocar mensagens deString entre aplicativos e fornecer estado compartilhado para novos aplicativos à medida que eles ingressam na rede.

2. Configuração

2.1. Dependência do Maven

Precisamos adicionar uma única dependência ao nossopom.xml:


    org.jgroups
    jgroups
    4.0.10.Final

A última versão da biblioteca pode ser verificada emMaven Central.

2.2. Trabalho em rede

JGroups tentará usarIPV6 por padrão. Dependendo da configuração do sistema, isso pode resultar em aplicativos que não conseguem se comunicar.

Para evitar isso, definiremos a propriedadejava.net.preferIPv4Stack paratrue ao executar nossos aplicativos aqui:

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

3. JChannels

Nossa conexão a uma rede JGroups é aJChannel. O canal se junta a um cluster e envia e recebe mensagens, bem como informações sobre o estado da rede.

3.1. Criação de um canal

Criamos umJChannel com um caminho para um arquivo de configuração. Se omitirmos o nome do arquivo, ele procuraráudp.xml no diretório de trabalho atual.

Criaremos um canal com um arquivo de configuração nomeado explicitamente:

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

A configuração de JGroups pode ser muito complicada, mas as configurações padrão de UDP e TCP são suficientes para a maioria dos aplicativos. Incluímos o arquivo para UDP em nosso código e o usaremos neste tutorial.

Para obter mais informações sobre como configurar o transporte, consulte o manual JGroupshere.

3.2. Conectando um canal

Depois de criar nosso canal, precisamos entrar em um cluster. A cluster is a group of nodes that exchange messages.

Para ingressar em um cluster, é necessário um nome de cluster:

channel.connect("example");

O primeiro nó que tenta ingressar em um cluster irá criá-lo se ele não existir. Veremos esse processo em ação abaixo.

3.3. Nomeando um Canal

Os nós são identificados por um nome para que os pares possam enviar mensagens direcionadas e receber notificações sobre quem está entrando e saindo do cluster. O JGroups atribuirá um nome automaticamente, ou podemos definir o nosso:

channel.name("user1");

Usaremos esses nomes abaixo para rastrear quando os nós entram e saem do cluster.

3.4. Fechando um canal

A limpeza do canal é essencial se quisermos que os colegas recebam uma notificação oportuna de que saímos.

Fechamos aJChannel com seu método de fechamento:

channel.close()

4. Mudanças na visualização do cluster

Com um JChannel criado, agora estamos prontos para ver o estado dos pares no cluster e trocar mensagens com eles.

JGroups maintains cluster state inside the View class. Cada canal possui um únicoView da rede. Quando a visualização muda, é entregue por meio do retorno de chamada deviewAccepted().

Para este tutorial, vamos estender a classe de APIReceiverAdaptor que implementa todos os métodos de interface necessários para um aplicativo.

É a maneira recomendada de implementar callbacks.

Vamos adicionarviewAccepted ao nosso aplicativo:

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

CadaView contém umList de objetosAddress, representando cada membro do cluster. O JGroups oferece métodos de conveniência para comparar uma visualização com outra, que usamos para detectar membros novos ou encerrados do cluster.

5. Enviando Mensagens

A manipulação de mensagens no JGroups é direta. UmMessage contém uma matrizbytee objetosAddress correspondentes ao emissor e ao receptor.

Para este tutorial, estamos usandoStrings lido na linha de comando, mas é fácil ver como um aplicativo pode trocar outros tipos de dados.

5.1. Mensagens de difusão

UmMessage é criado com um destino e uma matriz de bytes; JChannel define o remetente para nós. If the target is null, __ the entire cluster will receive the message.

Aceitaremos o texto da linha de comando e o enviaremos ao cluster:

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

Se executarmos várias instâncias do nosso programa e enviarmos esta mensagem (depois de implementar o métodoreceive() abaixo), todos eles receberão,including the sender.

5.2. Bloqueando nossas mensagens

Se não quisermos ver nossas mensagens, podemos definir uma propriedade para isso:

channel.setDiscardOwnMessages(true);

Quando executamos o teste anterior, o remetente da mensagem não recebe sua mensagem de difusão.

5.3. Mensagens diretas

O envio de uma mensagem direta requer umAddress válido. Se estamos nos referindo aos nós pelo nome, precisamos encontrar uma maneira de procurarAddress. Felizmente, temosView para isso.

OView atual está sempre disponível emJChannel:

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

Os nomes deAddress estão disponíveis por meio do método classtoString(), portanto, apenas procuramos osList dos membros do cluster pelo nome que desejamos.

Assim, podemos aceitar um nome no console, encontrar o destino associado e enviar uma mensagem direta:

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. Recebendo Mensagens

Podemos enviar mensagens, agora vamos adicionar, tente recebê-las agora.

Vamos substituir o método de recebimento vazio deReceiverAdaptor’s:

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

Como sabemos que a mensagem contémString, podemos passargetObject() paraSystem.out com segurança.

7. Troca estadual

Quando um nó entra na rede, pode ser necessário recuperar informações de estado sobre o cluster. O JGroups fornece um mecanismo de transferência de estado para isso.

Quando um nó se junta ao cluster, ele simplesmente chamagetState(). O cluster geralmente recupera o estado do membro mais antigo do grupo - o coordenador.

Vamos adicionar uma contagem de mensagens de transmissão ao nosso aplicativo. Vamos adicionar uma nova variável de membro e incrementá-la dentro dereceive():

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

Verificamos um destinonull porque se contarmos as mensagens diretas, cada nó terá um número diferente.

A seguir, substituímos mais dois métodos emReceiverAdaptor:

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

Semelhante às mensagens, o JGroups transfere o estado como uma matriz debytes.

JGroups fornece umInputStream para o coordenador para escrever o estado, e umOutputStream para o novo nó ler. A API fornece classes de conveniência para serializar e desserializar os dados.

Observe que, no código de produção, o acesso às informações de estado deve ser seguro para threads.

Finalmente, adicionamos a chamada paragetState() à nossa inicialização, depois de nos conectarmos ao cluster:

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

getState() aceita um destino do qual solicitar o estado e um tempo limite em milissegundos. Um destinonull indica o coordenador e 0 significa não timeout.

Quando executamos este aplicativo com um par de nós e trocamos mensagens de difusão, vemos o incremento da contagem de mensagens.

Então, se adicionarmos um terceiro cliente ou parar e iniciar um deles, veremos o nó recém-conectado imprimir a contagem de mensagens correta.

8. Conclusão

Neste tutorial, usamos o JGroups para criar um aplicativo para troca de mensagens. Usamos a API para monitorar quais nós se conectaram e deixaram o cluster e também para transferir o estado do cluster para um novo nó quando ele ingressou.

Amostras de código, como sempre, podem ser encontradasover on GitHub.