fbpx
+55 (11) 4506-3239

App MQ lenta, mensagens perdidas e alta CPU? Quatro maneiras fáceis de melhorar seu aplicativo MQ

IBM MQ na nuvem
31 jul 2019

App IBM MQ na nuvem lenta, mensagens perdidas e alta CPU? Quatro maneiras fáceis de melhorar seu aplicativo MQ

IBM MQ na nuvem lenta, mensagens perdidas e alta CPU? Quatro maneiras fáceis de melhorar seu aplicativo MQ

1 – IBM MQ na nuvem lento? App continua crashing? CPU fritando?

As escolhas que você faz quando desenvolve um aplicativo podem tornar sua vida mais fácil… ou muito mais difícil.

Neste artigo, você aprenderá três armadilhas comuns em que os desenvolvedores frequentemente se enquadram e como evitá-los. O código para este exemplo foi escrito para a API do JMS 2.0, com nosso gerenciador de filas sendo executado em um contêiner do Docker. Para mais informações sobre isso, dê uma olhada neste tutorial. Para mais informações sobre a API do JMS 2.0 com o IBM MQ na nuvem.

2 – O IBM MQ na nuvem está lento? Veja como você está se conectando

Uma armadilha muito comum aparece ao se conectar a um gerenciador de filas para enviar mensagens. É fácil pensar que cada mensagem que você enviar pode precisar de sua própria sessão e conexão (juntos, chamada de contexto na especificação do JMS 2.0), objetos de produtor e destino … parece sensato? Nós não precisamos fazer isso. Podemos reutilizar esses objetos para enviar várias mensagens.

Mas se funciona, funciona né? Não necessariamente … digamos que você queira enviar 5000 mensagens para uma fila. Criar 5000 conjuntos de objetos de conexão significa que o envio de muitas mensagens leva muito tempo.

Vamos dar uma olhada. Este código mostra parte de um aplicativo JMS 2.0 para enviar algumas mensagens com o MQ.

for (int i = 1; i <= 5000; i++) {
context = cf.createContext();
destination = context.createQueue("queue:///" + QUEUE_NAME);
TextMessage message = context.createTextMessage("This is message number " + i + ".\n");
producer = context.createProducer();
producer.send(destination, message);
context.close();
}

O aplicativo coloca o processo de criação de um novo contexto (sessão e conexão), produtor e mensagem em um loop para enviar 5000 mensagens.

Não é uma boa ideia. A saída é mostrada aqui:

Sent all messages!
Total time to send all messages: 46.52 seconds
SUCCESS

O programa leva 46,52 segundos para enviar tudo. Parece muito lento? Fica muito pior: se você não se lembrar de fechar o contexto (com context.close ()) entre criar cada conjunto de variáveis de conexão, poderá receber alguns erros seriamente desagradáveis.

Então, vamos melhorar. Nós reescrevemos o código aqui:

context = cf.createContext();
destination = context.createQueue("queue:///" + QUEUE_NAME);
producer = context.createProducer();
for (int i = 1; i <= 5000; i++) {
TextMessage message = context.createTextMessage("This is message number " + i + ".\n");
producer.send(destination, message);
}
context.close();

Desta vez, temos uma sessão, conexão e produtor para enviar todas as 5000 mensagens. Vamos dar uma olhada na saída desta vez:

Sent all messages!
Total time to send all messages: 9.28 seconds
SUCCESS

Agora, todo o processo leva apenas 9,28 segundos, portanto, mais de 5 vezes mais rápido.

Agora suas mensagens são enviadas com mais eficiência e tudo acontece mais rápido.

3 – CPU muito alta? Experimente os métodos de recebimento de mensagens inteligentes

Agora você já enviou suas mensagens, é hora de tirá-las da fila. A maneira de fazer isso no JMS 2.0 está usando um método definido no objeto MessageConsumer, receive (). No entanto, se receive () não estiver configurado para bloquear o encadeamento até que uma mensagem chegue, haverá alguns sérios problemas de desempenho.

Este código mostra parte de um aplicativo JMS 2.0 que tenta receber mensagens indefinidamente. O desenvolvedor que escreveu isso quer que as mensagens na fila sejam removidas imediatamente para que o tempo limite seja definido como 1 milissegundo.

while (true) { Message receivedMessage = consumer.receive(1); 
// wait time of 1ms getAndDisplayMessageBody(receivedMessage); 
// refactored for clarity 
}

O loop enquanto é executado a cada 1 milissegundo. Isso significa que o aplicativo usa muitos recursos para as novas mensagens.

Vamos dar uma olhada no nosso uso da CPU. O aplicativo de nosso desenvolvedor é o único programa em execução em um contêiner para que nós analisamos uma CPU com um comando docker stats em uma nova janela de terminal:

MQClient$ docker stats --format "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}"
CONTAINER ID        NAME                CPU %
f1537712cda5        pensive_johnson     58.38.%

Sem o aplicativo em execução, o contêiner usa 1-3% do total de CPU. Portanto, este aplicativo está usando> 55% de toda a CPU do contêiner. Isso é muito para apenas um aplicativo!

Vamos tentar uma abordagem diferente:

while (true) { Message receivedMessage = consumer.receive(); 
getAndDisplayMessageBody(receivedMessage);
 // refactored for clarity 
}

O aplicativo agora espera indefinidamente por novas mensagens (a configuração padrão receive ()). No entanto, quando uma mensagem é recebida, ela imediatamente faz uma nova solicitação. Nossa CPU estará muito mais tranquila também:

MQClient$ docker stats --format "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}"
CONTAINER ID        NAME                CPU %
f1537712cda5        pensive_johnson     2.95%

Portanto, adicionar um recebimento mais inteligente () é extremamente bom para o seu processador!

Se queremos receber mais de uma mensagem, o bloqueio trata bem disso. O recebimento aguarda até que uma mensagem chegue. Quando um chega, um novo pedido é feito instantaneamente para a próxima mensagem.

Se quisermos obter mensagens de uma fila de um modo orientado a eventos, podemos usar um objeto MessageListener. Isso vincula a um MessageConsumer e escuta em um thread separado para mensagens de um destino especificado. Isso nos dá muita flexibilidade, pois nosso aplicativo pode continuar em execução enquanto o ouvinte escuta em segundo plano. Para um exemplo de código completo de um objeto MessageListener em uso.

Agora você pode salvar sua CPU do trabalho duro e obter todas as mensagens que você deseja.

4 – Mensagens perdidas? App continua batendo? Pegue exceções para manter seu programa voando.

Na especificação do JMS 1.1, a maioria dos métodos de API que usamos são declarados como Throwable, portanto, você precisa capturar exceções ou o programa não compilará.

Na especificação do JMS 2.0, muitos métodos de API não são declarados como Throwable, o que significa que você não precisa capturar o JMSExceptions. Se uma RuntimeException for lançada, seu programa será finalizado. Além disso, os problemas de conexão, envio ou recebimento de mensagens também fazem com que seu aplicativo lance uma exceção e morra.

Essa é uma maneira de as mensagens se perderem: elas não estão conseguindo.

Veja um exemplo de um aplicativo JMS 2.0 em que um desenvolvedor deseja colocar uma mensagem importante em uma fila:

TextMessage message = context.createTextMessage("This important message MUST be sent"); 
producer.send(destination, message); 
System.out.println("Sent message!");

Se a fila estiver cheia, o programa termina com essa mensagem de erro (abreviada):

com.ibm.msg.client.jms.DetailedInvalidDestinationRuntimeException: Failed to send a message to destination 'DEV.QUEUE.1'.
JMS attempted to perform an MQPUT or MQPUT1; however IBM MQ reported an error.
    ... (abbreviated for clarity)
    at com.ibm.mq.samples.jms.MyJmsApplication.main(MyJmsApplication.java:56)
Caused by: com.ibm.mq.MQException: IBM MQ call failed with compcode '2' ('MQCC_FAILED') reason '2053' ('MQRC_Q_FULL').
    ... 13 more (abbreviated again)

Isto é ruim. A mensagem importante não será enviada. Isso pode acontecer porque

Um aplicativo consumidor morreu ou está sendo executado muito lentamente, permitindo que as mensagens preencham a fila. Muitos aplicativos podem estar enviando mensagens para a mesma fila e preenchendo-as antes que um aplicativo consumidor possa tirá-las da fila. O desenvolvedor antecipa que o aplicativo consumidor pode ter dificuldades se houver tráfego intenso na fila. Eles escrevem código para capturar uma exceção “fila cheia”:

Boolean sent = false; while (!sent){ try { TextMessage message = context.createTextMessage("This important message MUST be sent"); producer.send(destination, message); System.out.println("Sent message!"); sent = true; } catch (Exception ex) { ex.printStackTrace(); Throwable causeex = ex.getCause(); if ((causeex instanceof MQException) && (MQConstants.MQRC_Q_FULL == ((MQException) causeex).getReason())) { try{ Thread.sleep(1000); } catch (InterruptedException ie) { ie.printStackTrace(); } } else { throw ex; } } }

Agora, o erro “fila cheia” é tratado corretamente. O aplicativo pode continuar em execução até que a mensagem seja enviada, mesmo que tenha que aguardar até que o aplicativo consumidor limpe a fila. Aqui, vemos o erro “fila cheia” exibido antes que a fila seja limpa e a mensagem finalmente passe:

com.ibm.msg.client.jms.DetailedInvalidDestinationRuntimeException: Failed to send a message to destination 'DEV.QUEUE.1'.
JMS attempted to perform an MQPUT or MQPUT1; however IBM MQ reported an error.
    ... (abbreviated)
Caused by: com.ibm.mq.MQException: IBM MQ call failed with compcode '2' ('MQCC_FAILED') reason '2053' ('MQRC_Q_FULL').
    ... 13 more (abbreviated)
Sleeping for 1 second.
Sent message!

Capturar e lidar corretamente com esse erro significa que o aplicativo pode continuar em execução e enviar uma mensagem importante.

Agora você está capturando exceções, tem maior controle sobre seu aplicativo e o poder de torná-lo resiliente a problemas externos.

Gostou deste artigo? Não deixe de ver também o artigo sobre Visão geral do IBM MQ e Integration Bus.

Quer dar um gás na sua carreira? Então não deixe se acessar nosso curso de IBM Integration Bus. Clique no botão abaixo. Inscreva-se!!!

Inscreva-se