Integrações entre Sistemas – Parte 13 – Rabbit MQ

Objetivo

Durante minhas pesquisas, tomei conhecimento do Rabbit MQ (http://www.rabbitmq.com/) que trata-se de uma plataforma open-source para mensageria, disponível em diversos sistemas operacionais (Windows, Linux/Unix, Mac OS X e Amazon EC2).

Resolvi conhecê-lo e aproveitei para também incluí-lo junto aos demais benchmarks.

Instalação

Utilizei o Rabbit MQ em plataforma Windows. O processo de instalação dele não tem grande segredo. A única curiosidade é que por ele ter sido escrito em Erlang (http://erlang.org/), o runtime do Erlang precisa ser instalado.

Outro componente interessante de ser instalado (você vai precisar dele para rodar os exemplos) é o cliente .NET para o RabbitMQ. Você o encontra na página de downloads do Rabbit MQ. A documentação da API pode ser encontrada na página de documentação do Rabbit MQ. As vezes eu me surpreendo com a simplicidade e a organização de projetos open source.

Operação

O Rabbit MQ é relativamente simples. Não explorei todas as suas features, apenas o necessário para executar este teste/exemplo.

Existe um “Command Prompt” do Rabbit MQ (instalado junto com o servidor).

Alguns comandos úteis:

rabbitmqctl status

Mostra uma série de indicadores sobre o status do servidor.

rabbitmqctl environment

Mostra uma série de informações sobre o ambiente do servidor, entre elas uma das mais úteis que é a porta tcp que está sendo ouvida (default 5672).

rabbitmqctl list_queues

Lista as filas criadas.

rabbitmq-plugins enable rabbitmq_management

Habilita o plugin de gerenciamento do RabbitMQ. Uma vez que a instalação do plugin é realizada, basta reiniciar o servidor e acessá-lo através da URL http://localhost:15672/. Não esqueça de trocar localhost pelo IP ou hostname do seu servidor. O usuário default é guest/guest.

Cliente Rabbit MQ

A única coisa que fiz aqui foi pegar minha implementação de Websphere MQ e trocar as chamadas para o Rabbit MQ. Sim, eu poderia ter criado uma abstração mas como meu objetivo aqui é apenas entender como funciona e realizar alguns benchmarks, resolvi ser objetivo.

A implementação do cliente é muito simples. O método principal que executa a Task MSBuild segue:

		public override bool Execute()
		{
			Stopwatch watch = new Stopwatch();
			watch.Start();

			Log.LogMessage("Starting Rabbit MQ transfer with " + TotalBatches.ToString() + " batchs with " + BatchSize.ToString() + " items each");

			ConnectionFactory factory = new ConnectionFactory();
			factory.HostName = HostName;
			IConnection conn = factory.CreateConnection();
			IModel channel = conn.CreateModel();			
			channel.QueueDeclare(OutputQueueName, false, false, false, null);			
			IBasicProperties props = channel.CreateBasicProperties();
			props.ContentType = "text/xml";
			props.DeliveryMode = 2; //persistent

			inputChannel = conn.CreateModel();			
			inputChannel.QueueDeclare(InputQueueName, false, false, false, null);			

			messageCount = TotalBatches;

			System.Threading.Tasks.Task.Factory.StartNew(ProcessInputQueue);

			int count = 1;
			for (int i = 0; i < TotalBatches; i++)
			{
				MemoryStream ms = new MemoryStream();
				StreamUtil.GenerateBigRequest(ms, false, count, count + (BatchSize - 1));
				channel.BasicPublish("", OutputQueueName, props, ms.GetBuffer());

				count += BatchSize;
				Log.LogMessage("Sent " + count.ToString());
			}

			finishedEvent.WaitOne();

			watch.Stop();
			Log.LogMessage("Total processing time: " + watch.Elapsed.TotalSeconds.ToString("0.00") + " seconds");
			conn.Close();

			return true;
		}

ConnectionFactory é o objeto da API que encapsula uma conexão com uma fila. A criação do channel, QueueDeclare seguem o modelinho da API do Rabbit MQ.

O método que efetivamente faz a publicação na fila é o channel.BasicPublish. Existem algumas regras para publicações com tópicos, publish/subscribe, mas para o nosso exemplo, somente o BasicPublish resolve o problema.

Este método também inicia uma Task (que internamente inicia uma segunda thread) para processar a fila de resposta para o cliente, no método ProcessInputQueue. O código dele segue:

		private void ProcessInputQueue()
		{			
			while (true)
			{
				BasicGetResult result = inputChannel.BasicGet(InputQueueName, true);
				if (result == null)
				{
					Thread.Sleep(250);
					continue;
				}
				MemoryStream ms = new MemoryStream(result.Body);
				messageCount--;

				Log.LogMessage("Waiting for more " + messageCount.ToString());

				StreamUtil.ImportarStream(ConnString, ms);

				if (messageCount <= 0)				
					break;									
			}

			finishedEvent.Set();
		}

Mesma dinâmica do Websphere MQ. Aqui fico num loop infinito, pegando as mensagens. Sempre que não tem nada a processar (result == null), dou uma pequena dormidinha na thread somente para não matar a CPU da máquina. Pega a resposta e processa. Sem grandes segredos.

Servidor RabbitMQ

O servidor segue a mesma dinâmica:

		public override bool Execute()
		{
			Log.LogMessage("Starting RabbitMQ Server");

			ConnectionFactory factory = new ConnectionFactory();
			factory.HostName = "localhost";
			IConnection conn = factory.CreateConnection();

			IModel inputChannel = conn.CreateModel();
			inputChannel.QueueDeclare(InputQueueName, false, false, false, null);

			IConnection conn2 = factory.CreateConnection();			

			IBasicProperties props = inputChannel.CreateBasicProperties();
			props.ContentType = "text/xml";
			props.DeliveryMode = 2; //persistent

			IModel outputChannel = conn2.CreateModel();			
			outputChannel.QueueDeclare(OutputQueueName, false, false, false, null);			

			int count = 0;

			while (true)
			{
				BasicGetResult result = inputChannel.BasicGet(InputQueueName, true);
				if (result == null)
				{
					Thread.Sleep(250);
					continue;
				}

				MemoryStream ms = new MemoryStream(result.Body);
				result = null; // let GC work.
				MemoryStream respStream = new MemoryStream();

				StreamUtil.ProcessClientBigRequest(ConnString, ms, respStream, false, null);

				outputChannel.BasicPublish("", OutputQueueName, props, respStream.GetBuffer());
				respStream = null; //let GC work

				count++;

				Log.LogMessage("Processed " + count.ToString());

			}

Comparativo dos resultados

Como nem tudo são rosas, a máquina que estava usando como servidor resolveu morrer e tive que reinstalar tudo novamente. Por tabela, perdi a confiança nos números que tinha executado anteriormente e acabei reexecutando todos os testes no novo ambiente, com o objetivo de manter a premissa de “mesmo payload, mesmo ambiente” que utilizei desde o início dessa série.

Mesmo assim, continuei executando 10 vezes cada teste com cada um dos volumes (20k, 50k, 500k, 1M) e o número apresentado aqui é a média dos 10, para evitar diferenças que ocorrem numa ou outra execução devido a cache de banco, paginação de memória, garbage collector, etc.

Os números são:

integracoes1

integracoes2

Como os números mostram, o desempenho do RabbitMQ não foi satisfatório. Fiquei bastante frustrado com esse resultado. Apesar de toda a simplicidade da implementação, da instalação, etc., o número ficou praticamente a mesma coisa do que usar Web Services em SOAP.

Além disso, apesar de todo o trabalho para montar um novo ambiente (que mudou de 32 para 64 bits na máquina servidora), houve uma piora significativa dos números do File Transfer. Vou investigar mais a fundo as razões desta piora dado o novo ambiente instalado.

Código fonte

O código fonte das implementações acima foi atualizado no Git Hub: https://github.com/ericlemes/IntegrationTests

Conclusão

Eu vi várias pessoas recomendando o RabbitMQ, o que me motivou a realizar esta pesquisa comparando também com o MSMQ e Websphere MQ. Acho que ainda vale a pena uma investigação mais aprofundada de como otimizar o RabbitMQ antes de simplesmente descartá-lo. Este trabalho é bastante superficial.

Mas acho que a principal conclusão que eu chego é que antes de sair utilizando uma nova tecnologia, vale a pena buscar informações sobre ela, testar, brincar, comparar. Baseado nos números aqui obtidos, numa implementação entre plataformas distintas (java/.NET por exemplo), ficaria com SOAP e numa implementação apenas em plataforma Windows, com o MSMQ.

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s