16 de abr de 2013

Por que utilizar filas? Implementação com RabbitMq

Atualmente estamos sempre precisando de novos requisitos para a construção dos sistemas. Um dos maiores requisitos é a comunicação assíncrona e replicação de sistemas, para isto, a utilização de filas é bastante comum.

Mas por que utilizar uma fila?


Uma fila de mensagens basicamente é composta por um ou mais remetentes, um ou mais receptores e um broker. 

A função principal do broker é pegar a mensagem do remetente e garantir que ela chegue até o receptor que pode tratá-la.

A forma mais simples de fila é com um remetente, um receptor e o broker:

http://www.rabbitmq.com/img/tutorials/python-one.png

Nesta forma de implementação não obtemos todas as vantagens na utilização da fila,  mas ainda continuamos com algumas, como a comunicação assíncrona e armazenamento de requisições.

Outra forma comum é com um remetente e vários receptores:

http://www.rabbitmq.com/img/tutorials/python-two.png 
Com o aumento no número de receptores obtemos mais uma vantagem na utilização das filas que é o aumento da capacidade de resposta do sistema, e redução do número de mensagens armazenadas na fila.

Ainda há a possibilidade de implementar um padrão muito comum, o publish-subscribe:

http://www.rabbitmq.com/img/tutorials/python-three.png


Neste padrão, todas as vantagens de uma fila são alcançadas, ou seja, é possível realizar comunicação assíncrona, armazenar mensagens, utilizar vários receptores, entre outros.

A utilização de filas torna os sistemas desacoplados quanto ao espaço, o que é um efeito que segue as boas práticas de programação. Este desacoplamento permite que a execução de um mesmo serviço seja paralelizada e replicada em várias instâncias.

Neste post vamos abordar com ênfase o papel do broker. O broker escolhido é o RabbitMq. O RabbitMq  é construido utilizando a linguagem Erlang e implementa o Advanced Message Queuing Protocol (AMQP). O AMQP da suporte a vários tipos de comunicação, como point-to-point e publish-subscribe (que são os focos principais).

Vamos mostrar a implementação com um receptor, porém a mesma pode ser replicada para que também possa cobrir a segunda forma apresentada aqui.

Antes de começar a implementação do cliente é necessário baixar o RabbitMqServer:

$ deb http://www.rabbitmq.com/debian/ testing mai
$ wget http://www.rabbitmq.com/rabbitmq-signing-key-public.asc 
$ sudo apt-key add rabbitmq-signing-key-public.asc 
$ sudo apt-get update 
$ sudo apt-get install rabbitmq-server 


Por padrão, na instalação a fila fica no localhost.

Vamos utilizar o cliente em Java, mas é possível criar um cliente em diversas linguagens.

Os jars podem ser baixados no site do RabbitMq. Para quem utiliza Maven, basta adicionar o seguinte código no arquivo pom.xml do projeto:

<dependency>
  <groupId>com.rabbitmq</groupId>
  <artifactId>amqp-client</artifactId>
  <version>3.0.4</version>
</dependency>
 

Criando o Produtor de mensagens:

 
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.MessageProperties;

public class MqProducer {

 public static void produce() throws Exception{
  ConnectionFactory factory = new ConnectionFactory();
  factory.setUsername("guest");
  factory.setPassword("guest");
  factory.setVirtualHost("/");
  factory.setHost("127.0.0.1");
  factory.setPort(5672);
  Connection conn = factory.newConnection();
  Channel channel = conn.createChannel();
  String exchangeName = "myExchange";
  String routingKey = "testRoute";
  byte[] messageBodyBytes = "relatorioX".getBytes();
  channel.basicPublish(exchangeName, routingKey, MessageProperties.PERSISTENT_TEXT_PLAIN, messageBodyBytes);
  channel.close();
  conn.close();
 }
}


Por padrão o usuário e senha da fila é guest, é indispensável que este usuário seja apagado antes que um sistema seja colocado em produção.


Criando o receptor de mensagens:

Como o receptor é desacoplado, ele não tem conhecimento do momento exato que uma mensagem chega na fila, desta forma é necessário que a verificação seja feita constantemente. Nesta implementação fizemos tudo da maneira mais simples possível, então utilizamos um loop infinito. Mas isso pode ser feito utilizando um escalonador, como Quartz.

import com.rabbitmq.client.*;

public class Consummer {
    public static void main(String[] args) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        factory.setPort(5672);
        Connection conn = factory.newConnection();
        Channel channel = conn.createChannel();
        String exchangeName = "myExchange";
        String queueName = "myQueue";
        String routingKey = "testRoute";
        boolean durable = true;
        channel.exchangeDeclare(exchangeName, "direct", durable);
        channel.queueDeclare(queueName, durable, false, false, null);
        channel.queueBind(queueName, exchangeName, routingKey);
        boolean noAck = false;
        QueueingConsumer consumer = new QueueingConsumer(channel);
        channel.basicConsume(queueName, noAck, consumer);
        boolean runInfinite = true;
        while (runInfinite) {
            QueueingConsumer.Delivery delivery;
            try {
                delivery = consumer.nextDelivery();
            } catch (InterruptedException ie) {
                continue;
            }
            System.out.println("Message received" + new String(delivery.getBody()));
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
        }
        channel.close();
        conn.close();
    }

}


Para que ocorra uma comunicação no sentido inverso é necessário implementar o produtor e receptor em ambos os lados.

No próximo post sobre filas será mostrado a implementação do publish-subscribe.

Nenhum comentário:

Postar um comentário