middleware com escalonamento de aplicaÇÕespericas/orientacoes/escalonament... · soluções para...
TRANSCRIPT
UNIVERSIDADE REGIONAL DE BLUMENAU
CENTRO DE CIÊNCIAS EXATAS E NATURAIS
CURSO DE CIÊNCIA DA COMPUTAÇÃO – BACHARELADO
MIDDLEWARE COM ESCALONAMENTO DE APLICAÇÕES
PAULO MATHEUS BAEHR WEBER
BLUMENAU
2016
PAULO MATHEUS BAEHR WEBER
MIDDLEWARE COM ESCALONAMENTO DE APLICAÇÕES
Trabalho de Conclusão de Curso apresentado
ao curso de graduação em Ciência da
Computação do Centro de Ciências Exatas e
Naturais da Universidade Regional de
Blumenau como requisito parcial para a
obtenção do grau de Bacharel em Ciência da
Computação.
Prof. Francisco Adell Péricas - Orientador
BLUMENAU
2016
MIDDLEWARE COM ESCALONAMENTO DE APLICAÇÕES
Por
PAULO MATHEUS BAEHR WEBER
Trabalho de Conclusão de Curso aprovado
para obtenção dos créditos na disciplina de
Trabalho de Conclusão de Curso II pela banca
examinadora formada por:
Presidente: Prof. Francisco Adell Péricas, Ms. – Orientador, FURB
______________________________________________________
Membro: Prof. Mauro Marcelo Mattos, Dr. – FURB
______________________________________________________
Membro: Prof. Miguel Alexandre Wisintainer, Ms. – FURB
Blumenau, 05 de Julho de 2016
Dedico este trabalho a minha mãe, que sempre
me incentivou a estudar e me apoiou durante o
período da faculdade.
AGRADECIMENTOS
À minha família pelo incentivo ao estudo e pelos cafés durante noites de trabalho.
Aos meus amigos pelas dicas e dúvidas esclarecidas.
Ao meu orientador pela motivação e apoio no cumprimento dos prazos.
Aos professores e colegas de curso, que contribuíram para o meu aprendizado durante
o curso.
Não é preciso ter olhos abertos para ver o sol,
nem é preciso ter ouvidos afiados para ouvir o
trovão. Para ser vitorioso você precisa ver o
que não está visível.
Sun Tzu
RESUMO
Este trabalho descreve o desenvolvimento de um middleware utilizando a ferramenta
RabbitMQ para envio e recebimento de requisições, permitindo integração com outras
linguagens de programação também suportadas pela ferramenta. O middleware inicializa e
encerra aplicações de acordo com a demanda de requisições. Para inicializar ou encerrar
aplicações é necessário que esteja em execução na máquina em questão um aplicativo de
extensão ao middleware responsável pela inicialização de aplicativos. Para validar este
trabalho será utilizado um aplicativo que simula a geração do XML de uma nota fiscal de
produto, e outro aplicativo que processa o XML gerado, retornando um resultado. O
middleware armazena o tempo de execução de cada requisição para prever o tempo estimado
de outras requisições do mesmo tipo e priorizar algumas requisições que levam menos tempo.
Os resultados obtidos a partir dos experimentos e testes realizados demonstram que o
middleware é capaz de iniciar e encerrar aplicações de acordo com a demanda e priorizar
requisições considerando o tempo estimado de execução, sem que as requisições mais
demoradas fiquem sempre no final da fila.
Palavras-chave: Middleware. RabbitMQ. C#. Escalonamento.
ABSTRACT
This word describes the development of a middleware using RabbitMQ tool for sending and
receiving requests, allowing integration with other programming languages also supported by
the tool. The middleware initializes and closes applications according to demand requests. To
boot or shut down applications you must be running on the machine in question an extension
application middleware responsible for application startup. To validate this work will be used
an application that simulates the generation of XML of an invoice product, and other
application that processes the XML generated by returning a result. The middleware stores the
run time of each request to predict the estimated time of other requests of the same type and
prioritize some requests that take less time. The results from the experiments and tests show
that the middleware is able to start and stop applications according to demand and prioritize
requests considering the estimated runtime, without the most time-consuming requests always
stay at the end of the queue.
Key-words: Middleware. RabbitMQ. C#. Schedulling.
LISTA DE FIGURAS
Figura 1– Ilustração de um sistema distribuído ........................................................................ 14
Figura 2 – Integração múltipla ponto a ponto .......................................................................... 16
Figura 3– Integração com message broker ............................................................................... 16
Figura 4 – Rede integrada com múltiplos message brokers ..................................................... 17
Figura 5 – Camada Middleware ............................................................................................... 18
Figura 6 – RPC ......................................................................................................................... 19
Figura 7 – Comunicação com Middleware Orientado a Mensagens ........................................ 20
Figura 8 – Troca de mensagens através de filas ....................................................................... 21
Figura 9 – Publicação e inscrição ............................................................................................. 22
Figura 10 – Configuração de alerta .......................................................................................... 27
Figura 11 – Política de incremento de quantidade de instâncias .............................................. 28
Figura 12 – Comparação entre o consumo real e o previsto .................................................... 29
Figura 13 - Carga prevista e carga escalonada pelo AAS ........................................................ 29
Figura 14 – Agentes do sistema e principais interações ........................................................... 31
Figura 15 – Diagrama de atividades da aplicação servidora .................................................... 32
Figura 16 - Diagrama de atividades da aplicação AppStarter .................................................. 33
Figura 17 – Diagrama de atividades da aplicação Monitor ...................................................... 34
Figura 18 – Diagrama de atividades do Middleware: Envio de dados estatísticos .................. 34
Figura 19 – Recebimento de dados estatísticos ........................................................................ 35
Figura 20 – Recebimento de novas requisições ....................................................................... 35
Figura 21 – Registro de AppStarter .......................................................................................... 36
Figura 22 – Registro de Monitores ........................................................................................... 36
Figura 23 – Tratamento de requisições..................................................................................... 37
Figura 24 – Verificação da quantidade de aplicações servidoras ............................................. 37
Figura 25 – Modelo entidade relacional ................................................................................... 38
Figura 26 – Primeira parte dos gráficos de informações de uso do primeiro teste ................... 55
Figura 27 – Segunda parte dos gráficos de informações de uso do primeiro teste ................... 56
Figura 28 – Fila de mensagens no broker e taxa de mensagens do primeiro teste ................... 57
Figura 29 - Primeira parte dos gráficos de informações de uso do segundo teste .................... 59
Figura 30 – Segunda parte dos gráficos de informações de uso do segundo teste ................... 60
Figura 31 – Fila de mensagens do broker e taxa de mensagens do segundo teste ................... 61
LISTA DE QUADROS
Quadro 1 – Envio de mensagens .............................................................................................. 40
Quadro 2 – Recepção de mensagens ........................................................................................ 41
Quadro 3 – Preenchimento de campos para publicação da mensagem na aplicação cliente.... 42
Quadro 4 – Criação e Serialização de Nota em formato XML. ............................................... 43
Quadro 5 – Criação do consumidor utilizado na leitura de respostas ...................................... 43
Quadro 6 – Utilização do consumidor de mensagens............................................................... 44
Quadro 7 – Tratamento da requisição pela aplicação servidora ............................................... 45
Quadro 8 – Inicialização de aplicação servidora ...................................................................... 46
Quadro 9 – Inicialização de contadores de performance .......................................................... 47
Quadro 10 – Cálculo dos contadores de performance .............................................................. 48
Quadro 11 – Verificação das aplicações ainda em execução ................................................... 48
Quadro 12 – Criação dos gráficos com JFreeChart .................................................................. 49
Quadro 13 – Criação de séries .................................................................................................. 49
Quadro 14 – Adição de pontos em gráfico utilizando JFreeChart ........................................... 49
Quadro 15 - Adição de mensagens na fila ................................................................................ 50
Quadro 16 – Verificação se a máquina pode executar requisição ............................................ 51
Quadro 17 – Envio de requisição e cálculo de tempo de execução .......................................... 52
Quadro 18 – Verificação de quantidade de aplicações servidoras ativas ................................. 53
Quadro 19 – Verificação de AppStarter apto a iniciar aplicação ............................................. 54
SUMÁRIO
1 INTRODUÇÃO .................................................................................................................. 12
1.1 OBJETIVOS ...................................................................................................................... 13
1.2 ESTRUTURA.................................................................................................................... 13
2 FUNDAMENTAÇÃO TEÓRICA .................................................................................... 14
2.1 SISTEMAS DISTRIBUIDOS ........................................................................................... 14
2.2 MESSAGE BROKER ....................................................................................................... 16
2.3 MIDDLEWARE ................................................................................................................ 17
2.3.1 Remote Procedure Call (RPC) ........................................................................................ 19
2.3.2 Object Oriented Middleware (OOM).............................................................................. 20
2.3.3 Message Oriented Middleware (MOM).......................................................................... 20
2.3.3.1 Modelo de envio de mensagens .................................................................................... 21
2.4 ESCALONAMENTO DE PROCESSOS .......................................................................... 23
2.4.1 Fairness ........................................................................................................................... 24
2.4.2 Genetic Algorithm Scheduling ........................................................................................ 24
2.4.3 Throughput ...................................................................................................................... 25
2.4.4 Turnaround ...................................................................................................................... 25
2.4.5 Shortest Job First ............................................................................................................. 26
2.5 RABBITMQ ...................................................................................................................... 26
2.6 TRABALHOS CORRELATOS ........................................................................................ 26
2.6.1 AAS ................................................................................................................................. 26
2.6.2 Scryer .............................................................................................................................. 28
3 DESENVOLVIMENTO .................................................................................................... 30
3.1 REQUISITOS .................................................................................................................... 30
3.2 ESPECIFICAÇÃO ............................................................................................................ 30
3.2.1 Diagrama de Arquitetura ................................................................................................. 30
3.2.2 Diagramas de atividades. ................................................................................................ 31
3.2.3 Modelo Entidade Relacionamento .................................................................................. 38
3.3 IMPLEMENTAÇÃO ........................................................................................................ 38
3.3.1 Técnicas e ferramentas utilizadas.................................................................................... 39
3.3.1.1 Aplicação cliente .......................................................................................................... 41
3.3.1.2 Aplicação servidora ...................................................................................................... 44
3.3.1.3 AppStarter ..................................................................................................................... 45
3.3.1.4 Aplicação de monitoramento ........................................................................................ 49
3.3.1.5 Middleware ................................................................................................................... 50
3.4 RESULTADOS E DISCUSSÕES ..................................................................................... 54
3.4.1 Execução em uma máquina ............................................................................................. 54
3.4.2 Execução em duas máquinas ........................................................................................... 58
3.4.3 Principais dificuldades ao utilizar o RabbitMQ .............................................................. 62
CONCLUSÕES ....................................................................................................................... 63
3.5 EXTENSÕES .................................................................................................................... 63
REFERÊNCIAS ..................................................................................................................... 64
12
1 INTRODUÇÃO
Segundo Sordi (2007, p. 1), “existe uma crescente demanda das organizações por
soluções para integração entre Sistemas de Informação”. Entretanto muitas empresas
encontram dificuldades nesta integração, essencialmente os problemas que envolvem a
integração de sistemas legado com sistemas em desenvolvimento, ou na integração entre
vários sistemas interconectados pela rede, onde cada sistema desenvolve um papel específico,
como se conectar com o mundo real ou se conectar com outros dispositivos através da internet
(KRAKOWIAK, 2009, p. 16).
Segundo Machado (2014, p. 37), “O middleware é uma camada de software adicional
que fica entre a comunicação de rede oferecida pelo sistema operacional e a camada de
aplicação”. Atualmente existem diversas aplicações que já fazem o papel de middleware para
a integração de sistemas, desde modelos mais simples até modelos mais robustos.
Neste trabalho pretende-se apresentar uma solução capaz de integrar sistemas em
diversas plataformas e linguagens e escalonar aplicações (alocar aplicações de acordo com a
demanda). Será implementada uma política de escalonamento que é composta por elementos
de mais de uma política de escalonamento, desta forma, considerando fatores como a
quantidade de requisições por segundo, e ordenando as requisições através de uma fila, onde o
primeiro a entrar é o primeiro a sair, porém, requisições cujo tempo estimado de execução é
baixo podem ser priorizadas.
Para aplicar e validar o middleware, foi implementada uma aplicação simplificada de
Emissão de Notas Fiscais Eletrônicas (NF-e), onde questões como layouts serão abstraídos,
além disto a NF-e não será enviada para a Secretaria de Estado da Fazenda (SEFAZ), mas
será enviada para um serviço local que irá emular a SEFAZ, de modo que todo o ambiente
seja controlado.
O middleware proposto neste trabalho foi desenvolvido em C#, utilizando a plataforma
Visual Studio, e foi executado em uma máquina com Windows. A plataforma e linguagem
foram escolhidas devido a facilidade de se utilizar o Visual Studio.
Uma das características exploradas no trabalho é o fato de o middleware poder se
comunicar com aplicações desenvolvidas em mais de uma linguagem. Isto se deve à
utilização da ferramenta RabbitMQ, cuja responsabilidade é abstrair a utilização do socket nas
várias linguagens de programação suportadas, propiciando maior facilidade em
implementações multilinguagem.
13
Neste trabalho foi desenvolvida também uma aplicação de monitoramento do
middleware e das máquinas que iniciam aplicações, de modo a facilitar a detecção de
possíveis oportunidades de melhoria. A implementação da aplicação foi no Eclipse com o
plug-in WindowBuilder. Foi utilizada a biblioteca JFreeChart para a criação dos gráficos
necessários.
1.1 OBJETIVOS
O objetivo deste trabalho é desenvolver um middleware utilizando a biblioteca
RabbitMQ, capaz de permitir a integração de sistemas multiplataforma e multilinguagem.
Os objetivos específicos do trabalho são:
a) desenvolver uma aplicação de envio de NF-e para validação do middleware;
b) desenvolver o middleware implementando escalonamento de aplicações;
c) exibir relatórios de status de utilização do middleware e das máquinas que iniciam
aplicações.
1.2 ESTRUTURA
Este trabalho está dividido em quatro capítulos. O primeiro capítulo é composto pela
justificativa do trabalho, os objetivos e a apresentação de sua estrutura.
O segundo capítulo apresenta a fundamentação teórica, abordando conceitos sobre
sistemas distribuídos, message broker, middleware, escalonamento de processos, nota fiscal
eletrônica e RabbitMQ, esclarecendo conceitos utilizados no desenvolvimento do trabalho.
No terceiro capítulo é apresentado o desenvolvimento do aplicativo, iniciando pelos
requisitos e a especificação por meio de diagramas. A seguir é detalhada a implementação do
aplicativo, descrevendo técnicas e ferramentas utilizadas, após isto, são apresentados os
resultados obtidos.
Por fim, o quarto capítulo apresenta as conclusões e as sugestões para extensões e
melhorias futuras.
14
2 FUNDAMENTAÇÃO TEÓRICA
Neste capítulo são explorados os principais assuntos necessários para a realização do
trabalho. Estes estão subdivididos em cinco partes, sendo que na seção 2.1 são conceituados
Sistemas Distribuídos, na seção 2.2 é apresentado o conceito de message broker. A seção 2.3
trata de conceituar um middleware e a seção 2.4 aborda formas de escalonamento de serviços.
Já na seção 2.5 é apresentada a ferramenta RabbitMQ e na seção 2.6 são apresentados
trabalhos correlatos a este.
2.1 SISTEMAS DISTRIBUIDOS
Segundo Blair et al. (2013), um sistema distribuído é composto por componentes
localizados na rede de computadores que se comunicam e coordenam as atividades através de
mensagens. A principal motivação para construir e usar sistemas distribuídos é proveniente do
desejo de compartilhar recursos, sendo o termo “recurso” bastante abstrato. Pode-se
considerar recurso como o conjunto de coisas que podem ser compartilhadas de maneira útil
em um sistema de computadores interligados em rede. Ele abrange desde componentes de
hardware, como discos, impressoras ou processamento, até entidades definidas pelo software,
como arquivos, bancos de dados e objetos de dados de todos os tipos.
A Figura 1 ilustra a arquitetura de um sistema distribuído, onde vários sistemas se
comunicam através de uma rede local, que possui uma entrada para a internet.
Figura 1– Ilustração de um sistema distribuído
Fonte: Thiruvathukal e Kaylor (2013).
Um sistema distribuído possui alguns desafios, como a necessidade de tratamento de
concorrência, pois vários dispositivos podem solicitar o mesmo recurso e é necessário que
algum intermediário gerencie o acesso aos recursos. Caso esse gerenciamento não seja efetivo
15
podem ocorrer situações de deadlock, que são situações onde um processo para continuar
precisa de um determinado recurso que está mantido por outro processo, da mesma forma o
outro processo para continuar precisa de algum recurso já sendo utilizado, e por tanto nenhum
dos processos consegue continuar.
Outro desafio de um sistema distribuído é a necessidade de gerenciamento de tempo,
visto que diferentes dispositivos podem estar em diferentes locais, com diferentes horários e a
comunicação entre os dispositivos pode levar tempos diferente, devido a própria conexão de
rede.
A escalabilidade também é um desafio dos sistemas distribuídos. Um sistema é descrito
como escalável se permanece eficiente quando há um aumento significativo no número de
recursos e no número de usuários (BLAIR et al., 2013). Esta necessidade por escalabilidade se
aplica quando, por exemplo, em diferentes horários do dia, um sistema tem um aumento
muito grande na quantidade de acessos simultâneos.
Porém, deve-se considerar que em períodos de pouca utilização do sistema o mesmo
pode “diminuir de tamanho” para economizar energia. Desta forma um sistema distribuído
pode identificar a necessidade de aumentar ou diminuir os recursos conforme a demanda.
Conforme BLAIR et al. (2013) , o tratamento de falhas é um desafio para um sistema
distribuído, pois a falha de um dispositivo não deveria inviabilizar as operações. Para isso um
sistema deve estar apto a redirecionar a demanda para outro dispositivo que esteja apto a tratar
a demanda. Em ambientes onde existe um middleware este tratamento pode ser mais
complexo, pois nem sempre em caso de falha de um middleware é possível que outro assuma
o seu lugar e continue as tarefas pendentes. Existe o conceito de redundância, que consiste em
manter sempre um backup das informações disponíveis, seja um disco com os arquivos, um
servidor a mais para ser acionado, um middleware que está sincronizado com o atuante, etc.
Segundo BLAIR et al. (2013), um desafio de um sistema distribuído é facilitar o
compartilhamento de novos recursos, levando em consideração a quantidade de hardware e
software novos que chegam ao mercado. É importante que o sistema esteja apto a aceitar
novos recursos sem necessidade de restruturação do sistema.
A segurança em um sistema distribuído é um desafio complexo. Segundo Blair et al.
(2013), a segurança é composta por três componentes: confidencialidade (proteção contra
acesso não autorizado), integridade (proteção contra alteração da informação) e
disponibilidade (proteção contra interferência nos meios de acesso aos recursos). Há ainda um
desafio para lidar com ataques de negação de serviço, que consiste em uma forma de ataque
onde um servidor recebe tantas requisições que acaba travando e ficando indisponível. Este
16
tipo de ataque é em geral difícil de lidar, pois pode ser complexo definir quando uma
requisição é maliciosa ou não.
2.2 MESSAGE BROKER
Segundo Hohpe e Woolf (2003, p. 287), em sistemas distribuídos onde existe a troca
de mensagens entre as aplicações, as aplicações podem precisar trocar mensagens com
diversas outras aplicações, podendo criar uma sobrecarga de canais de comunicações entre
aplicações. Um exemplo de rede interconectada pode ser visto na Figura 2.
Figura 2 – Integração múltipla ponto a ponto
Fonte: Hohpe e Woolf (2003)
Para resolver este problema foi desenvolvido o message broker. Segundo Hohpe e
Woolf (2003, p. 288) pode-se definir um message broker como uma aplicação responsável
por receber mensagens de múltiplos remetentes, determinar o destino correto e enviar a
mensagem ao canal de comunicação com o destino correto. Na Figura 3 está ilustrada a
utilização de um message broker.
Figura 3– Integração com message broker
Fonte: Hohpe e Woolf (2003)
A vantagem de centralizar e diminuir a quantidade de canais de comunicação pode
também se tornar uma desvantagem, pois pode haver sobrecarga do message broker e o
mesmo acabar se tornando o gargalo do sistema. Para resolver esta situação é possível, por
17
exemplo, criar vários message brokers e interligá-los de forma hierárquica. Na Figura 4 é
ilustrado um exemplo de uma rede de message brokers.
Figura 4 – Rede integrada com múltiplos message brokers
Fonte: Hohpe e Woolf (2003)
2.3 MIDDLEWARE
Segundo Machado (2014, p. 37), “O middleware é uma camada de software adicional
que fica entre a comunicação de rede oferecida pelo sistema operacional e a camada de
aplicação.”. Ele oferece, portanto, uma abstração sobre a camada de rede, necessitando que o
usuário conheça apenas a localização do middleware, e este encaminha a requisição até o
destino. A Figura 5 ilustra a camada do middleware em uma comunicação entre dois hosts.
18
Figura 5 – Camada Middleware
Fonte: Bakken (2003).
Desenvolver para ambientes distribuídos utilizando um middleware tem benefícios,
como o fato de possibilitar que um programador desenvolva o lado servidor do sistema sem
muito conhecimento do lado cliente, embora ainda seja necessário conhecer o formato de
mensagem esperada pelo outro lado. Outra vantagem é o fato de ocultar boa parte da
complexidade do tratamento da comunicação via rede e da distribuição dos sistemas. Porém,
segundo Bernstein (1993, p. 5), surgem novas dificuldades como a necessidade de distinguir
quais funções ficam no lado cliente e quais ficam no lado servidor.
Segundo Bernstein (1993, p. 5), o grande número de middlewares disponíveis e o
grande número de serviços suportados acabam se tornando uma barreira em sua utilização,
pois é necessária muita programação para poder utilizá-los. Mesmo quando um middleware
possui poucos serviços, ao considerar todas as chamadas, necessidade de programação de
interfaces e definição dos dados acaba se tornando uma programação complexa.
Existem middlewares construídos para diferentes propósitos que disponibilizam
diferentes serviços. Segundo Varela (2007 p. 4), os middlewares comumente fornecem os
seguintes serviços:
a) serviços de comunicação: serviço para abstrair a complexidade da rede, a forma da
comunicação varia de acordo com a categoria do middleware;
b) serviços de acesso a base de dados: serviços que permitem execução de consultas
em um ou mais servidores, assegurando integridade dos dados disponibilizados
para a aplicação;
19
c) serviços de planejamento de execução: serviços que permitem executar múltiplos
processos simultaneamente, balanceamento de carga, priorização de tarefas;
d) serviços de segurança: serviços comumente utilizados para conectar sistemas
distintos, onde cada um possui seu próprio sistema de segurança;
e) serviços de diretório: serviços para administrar recursos de rede.
Nas próximas seções serão exploradas algumas categorias de middleware.
2.3.1 Remote Procedure Call (RPC)
Consiste em um middleware onde o cliente solicita a execução de um método no
servidor. Para isto, o cliente converte os parâmetros em uma mensagem e envia ao servidor,
onde os parâmetros são novamente convertidos e a ação é executada e retornada ao cliente. As
vantagens deste tipo de middleware são a possibilidade de receber vários tipos de parâmetros,
retornar mesmo sem conexão de rede e suportar tratamento de exceção. Porém, as
desvantagens incluem a falta de escalabilidade devido ao fato de não possuir mecanismo de
replicação e por ser um processo rígido e acoplado ao processo (ROSENBAND, 2007).
A Figura 6 ilustra o fluxo durante uma invocação de um método através do RPC, onde
ilustra que os stubs (classes geradas automaticamente por um compilador) são responsáveis
pela interação com a rede, eliminando a necessidade de a aplicação se preocupar com a
complexidade da comunicação em rede.
Figura 6 – RPC
Fonte: Rosenband (1997).
20
2.3.2 Object Oriented Middleware (OOM)
É uma categoria de middleware que possui um message broker, que tem por objetivo
traduzir a mensagem do formato enviado para o formato esperado pelo receptor, conhecido
como Object Request Broker (ORB). Segundo Maciel (2014, p. 8), ORB consiste em uma
evolução do RPC, onde a comunicação ocorre através de objetos.
2.3.3 Message Oriented Middleware (MOM)
Uma das principais vantagens deste tipo de middleware é o suporte a mensagens
assíncronas, possibilitando que a aplicação envie mensagens, continue o seu processamento e
apenas busque pela resposta mais tarde, ou em momentos de inatividade. A aplicação pode
aguardar a mensagem logo após seu envio, da mesma forma que seria feito caso o tratamento
fosse síncrono.
A Figura 7 ilustra a comunicação entre duas aplicações através de um middleware
orientado a mensagens, No exemplo ilustrado as aplicações possuem três camadas, a cama de
aplicação, a camada de interface de troca de mensagens, e a camada de troca de mensagens do
cliente, sendo este último responsável pela comunicação com o middleware.
Figura 7 – Comunicação com Middleware Orientado a Mensagens
Fonte: Monson-Haefel e Chappell (2000).
Para os middlewares orientados a mensagem, a comunicação ocorre por via de
mensagens que são postadas ou lidas de filas. A ordem das mensagens na fila é definida por
algum algoritmo em particular, sendo em geral utilizado o modelo First-in First-out (FIFO).
O cliente envia uma mensagem, que é adicionada na fila, e o middleware então consome as
mensagens, removendo-as da fila.
As filas podem ter algumas propriedades alteradas, como seu tamanho, nome,
algoritmo de ordenação, entre outros. Cada aplicação pode ter sua própria fila, assim como as
21
filas podem ser compartilhadas entre aplicações. Tipicamente os MOMs suportam várias filas
com diferentes propósitos.
2.3.3.1 Modelo de envio de mensagens
Segundo Mahmoud (2004, p. 8), existem dois modelos comumente disponíveis, o
modelo ponto a ponto e o modelo de publicação e inscrição. Ambos os modelos são baseados
em trocas de mensagens através de uma fila.
O Modelo de fila de mensagens é um modelo ponto a ponto, onde o primeiro
consumidor consome uma mensagem de acordo com o algoritmo definido e a mensagem é
retirada da fila. Apenas um consumidor recebe a mensagem e apenas uma vez.
A Figura 8 ilustra um sistema de troca de mensagens onde existem duas filas e existem
produtores que adicionam mensagens na fila e consumidores que removem mensagens da fila.
Neste exemplo cada produtor/consumidor interage apenas com uma fila, porém, é possível
que um produtor produza para mais de uma fila, assim como um consumidor pode consumir
de mais de uma fila.
Figura 8 – Troca de mensagens através de filas
Fonte: Novell (2004?).
Segundo Blair et al. (2013), uma propriedade crucial de uma fila de mensagens é a
persistência, isto é, a fila de mensagem irá armazenar as mensagens até que as mesmas sejam
consumidas, ou que seja recebida uma mensagem de acknowledge, informando que a
mensagem já foi reconhecida e está sendo tratada e não precisa mais ser armazenada.
Segundo Blair et al. (2013), vários sistemas comerciais necessitam do uso de
mensagens transacionadas. Elas são utilizadas quando se pretende executar várias tarefas e as
alterações apenas serão confirmadas se todas as tarefas forem executadas corretamente. Em
22
ambientes onde existem vários consumidores de mensagens este tratamento pode ser bastante
complexo devido à possibilidade de diferentes consumidores tratarem as tarefas.
Outro recurso disponível em sistemas comerciais como o WebSphere MQ é o suporte a
transmissão confidencial de mensagens utilizando SSL. Além disto, é possível utilizar um
controle de autenticação e acesso.
No modelo de publicação e inscrição, Segundo Eugster et al. (2003), aplicações
declaram interesse em um tipo de evento ou padrão de evento, e quando alguma mensagem é
publicada que se encaixa nos interesses registrados a aplicação é notificada. Uma forma de
implementar isto seria cada tipo de mensagem ter sua própria fila, onde cada consumidor
procura apenas em determinadas filas e cada produtor posta mensagens apenas em
determinadas filas, de acordo com o tipo da mensagem.
Na Figura 9 é apresentado um exemplo de sistema de troca de mensagens através do
sistema de publicação e inscrição, ilustrando produtores produzindo mensagens do mesmo
tópico e de tópicos diferentes, além de consumidores consumindo mensagens de diferentes
tópicos. Além disto, é ilustrado o fato de que as mensagens são replicadas conforme o número
de inscritos para o tópico. No caso do “Topic A”, como existem três consumidores inscritos, a
mensagem é replicada três vezes.
Figura 9 – Publicação e inscrição
Fonte: Novell (2004?).
Segundo Blair et al. (2013), este modelo de publicação e inscrição é utilizado, por
exemplo, para o tratamento de propagandas da Google, o Google Ads, onde os usuários são
23
inscritos em determinados tópicos de acordo com o histórico de navegação. Quando um
anúncio é publicado todos que foram inscritos para aquele tópico visualizam a propaganda.
Existem diferentes formas de tratar o esquema de publicação e inscrição. Existe a
forma baseada em canais, onde uma aplicação se inscreve para receber todas as mensagens do
canal, e as mensagens são publicadas sempre em um canal e todos que se subscreveram no
canal a recebem. É de certa forma semelhante a comunicação ponto a ponto.
Existe o tratamento baseado em tópicos, onde uma aplicação se registra para um
determinado tópico em um broker, e as publicações informam qual o tópico em que serão
publicadas. Segundo Eugster et al.(2003) a melhoria mais útil foi a possibilidade de utilizar a
hierarquia para organizar os tópicos. A hierarquia permite, por exemplo, existir o tópico
“Carro” e o tópico “Automóvel”, onde ao se inscrever para o tópico “Automóvel”
automaticamente é inscrito no tópico “Carro”, pois o conjunto “Carro” pertence ao conjunto
“Automóvel”.
O tratamento baseado em conteúdo é uma abordagem que generaliza a abordagem via
tópicos, pois permite a flexibilização das inscrições através de queries. Uma aplicação pode se
inscrever para receber mensagens do tipo “Carro” ou “Caminhão” da Ford. A gramática das
queries varia de acordo com o sistema.
Segundo Eugster et al.(2003), existe o tratamento baseado no tipo, onde é possível
receber notificações de acordo com o tipo de interesse. É possível ainda filtrar o conteúdo de
acordo com o conteúdo do objeto, a partir das propriedades públicas.
2.4 ESCALONAMENTO DE PROCESSOS
O escalonamento consiste em uma técnica utilizada por processos para dividir sua
carga de tarefas entre os processadores das tarefas de modo a maximizar o indicador de
Qualidade de Serviço (QoS), sendo que essa qualidade é medida de acordo com parâmetros
que podem variar de acordo com o processo (ABIRAMI, RAMANATHAN; 2012, p. 12). Por
exemplo, uma emissão de nota fiscal do consumidor (NFC) deve garantir que seja o mais
próximo de instantâneo possível, pois um cliente de um supermercado não pretende ficar
vários minutos na fila aguardando a emissão da nota. Logo, percebe-se, que para este tipo de
serviço é importante garantir que nada fique na fila por muito tempo, caso contrário pode
deixar de gerar lucro para o supermercado.
Segundo Senger (2002), para efetuar as decisões com relação à divisão de carga de
tarefas, o escalonador se baseia em políticas de escalonamento, que representam decisões
como “permitir que aplicações com menor tempo de execução sejam executadas
24
prioritariamente” ou “permitir que aplicações interativas tenham maior prioridade na
utilização de recursos”. A partir destas políticas e considerando o ambiente de execução, o
escalonador efetua a distribuição da carga.
Algumas das políticas necessitam de informações, como o tamanho da fila, utilização
de CPU, memória, entre outras informações. A escolha da estratégia está diretamente ligada
às informações disponíveis no ambiente. Em um ambiente sem informações sobre a carga das
máquinas ou estatísticas de duração de execução de processos, o escalonador acaba optando
por estratégias simples que podem não trazer os melhores resultados. Ainda é possível a
combinação desses índices, para melhor representar o estado atual do sistema e obter assim
comportamentos mais próximos da realidade (ISHII 2004, p. 31).
A determinação dos índices como, consumo de memória, CPU e tempo de resposta
tem uma dependência muito forte com o ambiente, pois dependendo do processamento
disponível na máquina, 10% de CPU pode ser suficiente para executar várias tarefas,
enquanto em outros ambientes isto já pode ser inviabilizado, assim como, valores fixos podem
não ter bons resultados considerando que o necessário para executar o processo varia de
acordo com o processo.
A seguir são citadas políticas comuns, como a fairness, genetic algorithm scheduling,
throughput, turnaround e shortest job first.
2.4.1 Fairness
A política fairness considera que todos os processos devem ter o mesmo tipo de
tratamento. Com isso, todas as requisições vão para uma fila onde o primeiro a entrar é o
primeiro a sair. É uma política de fácil implementação, considerando que basta programar
uma fila FIFO.
2.4.2 Genetic Algorithm Scheduling
A política genetic algorithm scheduling (GAS) visa manter a utilização dos recursos
sempre no maior nível possível de acordo com primitivas informadas. Estas primitivas podem
ser relativas ao consumo de CPU, memória, disco, entre outros fatores. Caso a primitiva
informada seja ultrapassada, o escalonador pode iniciar novas máquinas e nelas novos
servidores ou, caso não seja possível iniciar nova máquina, pode aguardar até que o nível de
utilização dos recursos diminua em alguma máquina. Neste caso, enquanto o uso dos recursos
não diminui, mesmo que existam aplicações servidoras ociosas, elas não recebem requisições
25
até que o nível de recursos da máquina esteja dentro do permitido; caso a média de uso dos
recursos esteja muito baixa, o escalonador pode encerrar uma máquina.
Segundo Senger (2004, p. 153), devido à quantidade de fatores existentes, a busca por
um servidor considerado ideal para executar o processo pode demorar muito tempo. Por isto,
em geral são utilizadas heurísticas para encontrar uma solução aceitável, onde o benefício da
busca pelo servidor aceitável compense o tempo levado pela busca.
2.4.3 Throughput
A estratégia throughput visa garantir que uma determinada quantidade de requisições
seja atendida em uma determinada quantidade de tempo. Definindo-se uma meta de
requisições por tempo, o escalonador irá distribuir as requisições existentes entre os serviços
existentes, e caso verifique que não irá conseguir executar a quantidade definida ele inicia um
novo serviço. Assim como, caso esteja executando muito mais requisições que a meta, o
escalonador pode encerrar um serviço.
Esta estratégia se utilizada sozinha pode gerar resultados indesejáveis, pois em
períodos de pico de utilização, caso o valor seja maior que o informado no throughput, o
escalonador pode manter o throughput e gerar uma fila grande. Assim como, em períodos de
pouca utilização pode ocorrer de processos sequer ficarem na fila e logo serem executados
para tentar alcançar o throughput quando não era necessário.
2.4.4 Turnaround
A estratégia turnaround visa garantir que o tempo total de execução de uma requisição
não seja maior que o configurado. Ao verificar que existem serviços que ainda não foram
executados e estão com o tempo próximo do limite, o escalonador pode iniciar um novo
serviço para conseguir executar as requisições a tempo. Caso esteja executando as requisições
em tempo muito menor que o tempo mínimo, o escalonador pode, da mesma forma, encerrar
um serviço iniciado.
É uma política que pode ser utilizada quando o tempo limite de execução for relativo
ao processo, pois conforme mencionado anteriormente, existem processos que necessitam ser
executados o mais rápido possível, enquanto outros podem aguardar algum tempo até serem
executados.
26
2.4.5 Shortest Job First
A estratégia shortest job first visa garantir que as próximas tarefas a serem executadas
serão as que levarão menos tempo. Para isso é necessário saber, antes de executar a
requisição, quanto tempo aproximadamente ela levará. É uma estratégia que é interessante ser
executada em conjunto com outras estratégias, pois se utilizada exclusivamente, as tarefas que
levam mais tempo teriam tendência a levar muito tempo para serem executadas, ou mesmo
não serem executadas (SHYAM, 2014, p. 170).
2.5 RABBITMQ
RabbitMQ é um message broker e gerenciador de filas que permite o envio e
recebimento de mensagens entre aplicações, sendo uma implementação do protocolo
Advanced Message Queuing Protocol (AMQP). Segundo o RabbitMQ (2016), o RabbitMQ
foi desenvolvido na linguagem de programação Erlang, tendo seu desenvolvimento iniciado
em 2006, e a primeira versão liberada foi em 2007 através de uma licença Mozilla Public
License (MPL).
O RabbitMQ suporta o envio de mensagens através de fila e através de esquema de
publicação e inscrição, sendo que o RabbitMQ é composto basicamente por um servidora,
bibliotecas cliente para a comunicação com o broker que estão disponíveis em diversas
linguagens, incluindo Java, C# e Python, e uma plataforma para plug-ins adicionais para
complementar a utilização do RabbitMQ através de recursos adicionais. Estes plug-ins são
criados pela comunidade e são apenas publicados no site do RabbitMQ.
2.6 TRABALHOS CORRELATOS
Nesta seção são apresentados dois trabalhos correlatos ao trabalho proposto. Na seção
2.4.1 é apresentado o serviço da Amazon de controle de instâncias, o Amazon Auto Scaling
(AAS) e na seção 2.4.2 é apresentado a aplicação de previsão de instâncias da Netflix, o
Scryer.
2.6.1 AAS
O AAS é uma aplicação da Amazon que verifica as informações de utilização das
instâncias e aumenta/diminui a quantidade de instâncias do Amazon Elastic Compute Cloud
(EC2). Ele permite valores fixos de instâncias, permite ajustar quantidade de instâncias de
acordo com horários de pico e permite configurações de aumento automático de acordo com
as informações obtidas das instâncias (AMAZON WEB SERVICES, 2015).
27
A configuração de ajuste automático de instâncias se baseia em regras criadas que
aumentam ou diminuem as instâncias de acordo com gatilhos. Estes gatilhos são disparados
pelo WatchCloud de acordo com algumas regras que consideram algumas métricas padrões
como utilização de CPU, memória, dentre outros. Porém também é possível que a aplicação
publique métricas através do uso de anotações do Java.
É possível identificar na Figura 10 um exemplo de configuração de alerta, onde foi
configurado que caso a média de utilização de CPU seja maior ou igual a 80% por pelo menos
cinco minutos é disparado o alarme.
Figura 10 – Configuração de alerta
Fonte: Amazon Web Services (2015).
Na Figura 11 é possível verificar a criação de uma política que aumenta em 30% a
quantidade de instâncias, ou pelo menos uma instância, quando um determinado alarme for
disparado.
28
Figura 11 – Política de incremento de quantidade de instâncias
Fonte: Amazon Web Services (2015).
Uma das vantagens do AAS é a documentação existente da Amazon. Uma
desvantagem é o fato de ser exclusivo para o ambiente Amazon, isto é, para utilizar o AAS é
necessário ser cliente da Amazon e hospedar sua aplicação na Amazon. Embora em muitos
casos hospedar a aplicação em serviços como o da Amazon seja interessante por questões de
redução de custos e não precisar lidar diretamente com o hardware, também tem algumas
questões como o fato de fazer com que todas as informações do seu sistema estarão
disponíveis na Amazon e desta forma, é necessário confiança na empresa.
2.6.2 Scryer
O Scryer é um sistema que verifica qual a quantidade de instâncias Amazon Web
Services (AWS) que são necessárias para responder as requisições dos clientes da Netflix. O
Scryer utiliza o raciocínio baseado em casos para prever padrões de utilização, com isso, é
possível iniciar a diminuição da quantidade de instâncias antes de elas ficarem ociosas, ou
iniciar novas instâncias antes de as atuais estarem sobrecarregadas (JACOBSON; YUAN;
JOSHI, 2013).
Através de um histórico de utilização, foi possível observar vários fatores como os
horários do dia em que a taxa de utilização costuma aumentar ou diminuir e uma estimativa
de quanto costuma variar, bem como os dias da semana que costumam ter mais acessos, etc.
Com estes dados, a Netflix desenvolveu algumas fórmulas para que a previsão seja o mais
próximo do real (JACOBSON; YUAN; JOSHI, 2013). Na Figura 12 é possível observar que o
Scryer se mostrou muito efetivo e consegue ter uma previsão de utilização próxima da real
utilização.
29
Figura 12 – Comparação entre o consumo real e o previsto
Fonte: Jacobson, Yuan e Joshi (2013).
A Netflix está hospedada na Amazon e utiliza o AAS como rota de escape para o caso
de ocorrer algum evento não previsto ou caso as previsões estejam muito incorretas. É
possível observar na Figura 13 que a previsão do Scryer e a utilização conforme o AAS são
muito parecidas.
Figura 13 - Carga prevista e carga escalonada pelo AAS
Fonte: Jacobson, Yuan e Joshi (2013).
30
3 DESENVOLVIMENTO
Neste capítulo são apresentadas as etapas do desenvolvimento do aplicativo. Na seção
3.1 são enumerados os principais requisitos do projeto desenvolvido. A seção 3.2 apresenta a
especificação do projeto com diagramas de casos de uso e de atividades. A seção 3.3 detalha a
implementação do aplicativo, destacando os principais pontos. Por fim, a seção 3.4 apresenta
os experimentos realizados e resultados obtidos.
3.1 REQUISITOS
O middleware desenvolvido deve:
a) exibir informações sobre os dispositivos de uso como % de uso de CPU e memória
livre dos dispositivos que iniciam aplicações (Requisito Funcional - RF);
b) adicionar as mensagens em uma fila, onde mensagens que executam rápido podem
não ser adicionados no final da fila (RF);
c) armazenar informações sobre os serviços executados, como o tempo de execução
(RF);
d) considerar o estado atual das máquinas (consumo de memória e CPU) ao
selecionar a máquina que irá tratar a requisição(RF);
e) utilizar a linguagem de programação C# (Requisito Não Funcional - RNF);
f) permitir a comunicação com clientes em linguagem C# e Java (RNF);
g) utilizar a ferramenta RabbitMQ (RNF).
3.2 ESPECIFICAÇÃO
A especificação do projeto foi representada através de diagramas Unified Modeling
Language (UML), utilizando a ferramenta Enterprise Architect. Foram elaborados os
digramas de atividades, modelo entidade relacionamento (MER), diagrama de arquitetura e
diagrama de classes e pacotes, sendo apresentados nas próximas seções.
3.2.1 Diagrama de Arquitetura
O sistema consiste em cinco agentes. Os agentes são as aplicações cliente, que são
responsáveis por gerar requisições e enviar para o middleware, outro agente é o middleware,
há também um agente responsável por iniciar as aplicações servidoras, denominado
AppStarter, além deste, há as aplicações servidoras responsáveis por processas as requisições,
e por fim a aplicação de monitoramento, responsável por exibir os índices monitorados. Estes
agentes estão representados na Figura 14.
31
Figura 14 – Agentes do sistema e principais interações
3.2.2 Diagramas de atividades.
Foram criados diagramas de atividades pois são várias atividades e não é possível
visualizar tudo em apenas um diagrama. O primeiro diagrama é composto pelas atividades da
aplicação servidora, o segundo diagrama contêm as atividades da aplicação AppStarter, o
terceiro diagrama contém as atividades da aplicação Monitor, os demais diagramas contém as
atividades do Middleware.
32
Figura 15 – Diagrama de atividades da aplicação servidora
A aplicação servidora após iniciar aguarda uma requisição e ao receber verifica o tipo
da requisição, caso seja -1 então a aplicação servidora finaliza-se e notifica o middleware,
caso o tipo da requisição seja 1 então a aplicação trata a requisição e retorna ao middleware o
resultado do processamento.
33
Figura 16 - Diagrama de atividades da aplicação AppStarter
A aplicação AppStarter se registra, e após receber a confirmação inicia uma thread de
envio de informações de uso da máquina e outra thread de inicialização de aplicações. A
thread de inicialização de aplicações aguarda por uma mensagem para então verificar se o
tipo de aplicação está na lista de tipos válidos e então inicia uma aplicação do tipo. A
aplicação de inicializações implementada apenas inicia aplicações do tipo 1. Após isto
verifica se iniciou a aplicação retornando o status da inicialização ao middleware. A thread de
envio de informações de uso coleta as informações de uso da máquina e de quantidade de
processos executando e envia ao middleware.
34
Figura 17 – Diagrama de atividades da aplicação Monitor
A aplicação de monitoramento se registra no middleware e após receber a confirmação
cria os gráficos e aguarda por mensagens com novas informações para adicionar aos gráficos.
Ao receber novas mensagens a aplicação verifica se já existe uma série para a informação:
caso não exista, cria; caso exista apenas adiciona a informação na série.
Figura 18 – Diagrama de atividades do Middleware: Envio de dados estatísticos
A thread de envio de dados estatísticos consiste em carregar a lista de dados
estatísticos a ser enviada, iterar sobre a lista de monitores registrados e enviar para cada
monitor registrado.
35
Figura 19 – Recebimento de dados estatísticos
A thread de recebimento de dados estatísticos é composta por uma verificação se
recebeu alguma mensagem, ao receber a mensagem armazena os dados da mensagem, tanto
em memória, quanto na base de dados.
Figura 20 – Recebimento de novas requisições
A thread de recebimento de novas requisições verifica se recebeu alguma requisição,
após isto verifica se a requisição deve ser priorizada, caso deva ser priorizada adiciona na 10ª
posição da fila, caso contrário adiciona ao final da fila.
36
Figura 21 – Registro de AppStarter
A thread de registro de AppStarter verifica se recebeu alguma requisição, ao receber
adiciona o AppStarter na lista de AppStaters.
Figura 22 – Registro de Monitores
A thread de registro de monitores verifica se recebeu alguma requisição, e então ao
receber adiciona o monitor da requisição em uma lista de monitores registrados.
37
Figura 23 – Tratamento de requisições
A thread de tratamento de requisições consiste em uma verificação se recebeu alguma
mensagem, ao verificar que recebeu então retira a requisição da fila e verifica se existe
aplicação servidora iniciada, caso não existe então inicia uma e aguarda a mesma ficar ociosa.
Após ter uma aplicação servidora ociosa, marca ela como não ociosa e inicia uma nova thread
para tratar a requisição, enquanto a thread atual volta para o início do processo. A nova
thread envia a requisição para a aplicação servidora, aguarda o resultado do processamento e
após isto salva o tempo de execução na base e envia para a aplicação cliente o resultado do
processamento retornado pela aplicação servidora.
Figura 24 – Verificação da quantidade de aplicações servidoras
38
A thread de verificação da quantidade de aplicações servidoras verifica a quantidade
de aplicações servidoras por tipo de requisição, caso seja menor que o mínimo então inicia
novas aplicações servidoras, caso seja maior que o máximo então encerra aplicações
servidoras, os valores mínimos e máximos são configuráveis, porém no diagrama estão
exemplificados como 8 e 10.
3.2.3 Modelo Entidade Relacionamento
Na Figura 25 está representado o modelo entidade relacionamento (MER) do
middleware.
Figura 25 – Modelo entidade relacional
A entidade Statistics representa uma extração dos dados de utilização de uma
máquina, cada aplicação de iniciação de aplicações envia medições periodicamente para o
middleware, que são salvas na base de dados. A entidade ExecutionTime representa um
tempo de execução de um serviço em determinada máquina, o mesmo é salvo pelo
middleware após a execução de cada serviço. A entidade Device representa uma máquina, e
a entidade Service um serviço, ambas entidades apenas são utilizadas por outras entidades.
3.3 IMPLEMENTAÇÃO
A seguir são mostradas as técnicas e ferramentas utilizadas e a operacionalidade da
implementação.
39
3.3.1 Técnicas e ferramentas utilizadas
O middleware foi desenvolvido utilizando a linguagem de programação C#, no
ambiente de desenvolvimento Visual Studio 2015 Community. O Visual Studio é o ambiente
de desenvolvimento oficial da Microsoft, sendo a versão Community disponibilizada
gratuitamente para estudantes.
Foi utilizado o banco de dados SQL Server 2014 para armazenar dados estatísticos de
utilização. A versão Express do SQL Server foi escolhida por ser gratuita. Também foi
utilizada a biblioteca EntityFramework versão 6.1.3 da Microsoft para efetuar as operações no
banco de dados
Foi necessário efetuar uma instalação do produto RabbitMQ, para trabalhar como um
broker, e a versão escolhida foi a 3.6.0 por ser a última versão no início do período de
desenvolvimento. Esta biblioteca utiliza uma máquina virtual Erlang, por isto, é necessário
também instalar o software Erlang/OTP na versão 18.2.1. Além disto, foi necessário utilizar
bibliotecas nas aplicações que se comunicam com o broker, nos aplicativos desenvolvidos em
C# e Java foram utilizadas as bibliotecas do RabbitMQ cliente na versão 3.6.2, própria para
cada linguagem de programação.
Para abstrair a utilização da biblioteca nos aplicativos C# foi criada uma classe
auxiliar, denominada MessageUtils. Nessa classe foram abstraídas algumas funções, como o
envio e a leitura de mensagens. No Quadro 1 é apresentado o código responsável pelo envio
de mensagens e no Quadro 2 é apresentado o código responsável pelo recebimento de
mensagens.
40
Quadro 1 – Envio de mensagens 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void PublishMessage(IModel channel, string queueName,
byte[] message, string id = null)
{
IBasicProperties prop = channel.CreateBasicProperties();
if (id == null)
{
prop.MessageId = "1";
}
else
{
prop.MessageId = id;
}
prop.ReplyTo = Environment.MachineName;
channel.BasicPublish(exchange: "",
routingKey: queueName,
basicProperties: prop,
body: message);
}
Na linha 4 é criado o conjunto de propriedades da mensagem, e na linha 5 é verificado
se a propriedade id é preenchida. Caso tenha sido preenchida então na propriedade
MessageId é informado o valor da propriedade id, caso contrário é informado o valor 1.
Na linha 14 é chamado o método BasicPublish passando o parâmetro exchange vazio,
pois o mesmo não está sendo utilizado. No parâmetro routingKey é passado o nome da fila
que se deseja informar, na propriedade basicProperties é passado as propriedades
recém criadas, e no campo body é passado a mensagem em si, em uma sequência de bytes.
41
Quadro 2 – Recepção de mensagens 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
if (!timeOut.HasValue)
{
timeOut = 600000; // 10 minutos?
}
int tickStart = Environment.TickCount;
int tickCurrent;
BasicGetResult message = null;
do
{
message = channel.BasicGet(queueResponse, false);
if ((message == null) && (sleep.HasValue))
{
Thread.Sleep(sleep.Value);
}
tickCurrent = Environment.TickCount;
} while ((message == null) && (tickCurrent - tickStart <
timeOut.Value));
if(message == null)
{
return null;
}
channel.BasicAck(message.DeliveryTag, false);
Message response = new Message()
{
Msg = message.Body,
Id = message.BasicProperties.MessageId,
DeliveryTag = message.DeliveryTag,
header = (Dictionary<string,
object>)message.BasicProperties.Headers
};
return response;
Na linha 10 é chamado o método BasicGet. Este método verifica se existe alguma
mensagem para ser recebida, e caso não existe retorna null. Desta forma é feito um loop caso
o método BasicGet retorne null continua no loop aguardando a mensagem, e caso tenha
sido informado um valor para aguardar no loop, é chamado o método Thread.Sleep com
o valor informado, que é em milissegundos. Após receber a mensagem, é chamado o método
BasicAck, para informar ao broker que a mensagem foi lida e pode ser retirada da fila.
Após isto é criado um objeto que representa uma mensagem, com as propriedades do corpo da
mensagem, a id da mensagem, a tag de identificação da mensagem e o seu cabeçalho.
3.3.1.1 Aplicação cliente
A aplicação cliente é a aplicação que inicia o ciclo de processamento. A aplicação
monta uma requisição e envia para o middleware, que consiste em três informações: um
campo do tipo String MessageID, que serve para identificar qual o tipo de requisição; um
cabeçalho onde deve ser adicionado o parâmetro queue com uma string que é o nome da fila
para onde o middleware irá retornar a resposta da requisição; e do corpo da mensagem que é
42
um array de bytes. O Quadro 3 apresenta o código responsável pelo preenchimento destes
campos e publicação da requisição.
Quadro 3 – Preenchimento de campos para publicação da mensagem na aplicação cliente 1
2
3
4
5
6
7
8
9
10
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("queue", clientQueue);
BasicProperties prop = new AMQP.BasicProperties.Builder()
.messageId("1").replyTo(cpuName).headers(headers)
.build();
try {
channel.basicPublish("", queueName, prop, msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
Nas linhas 1 e 2 é montado o mapa do cabeçalho, e nas linhas 3, 4 e 5 é construído o
conjunto de propriedades do envio e na linha 7 é utilizada a função getBytes da própria
String para retornar os bytes da String, que contém o XML. Estes bytes são passadas para o
método basicPublish, responsável pela publicação da mensagem no canal.
O corpo da mensagem pode ser um arquivo XML ou JSON, entre outros padrões
existentes no mercado, pois o middleware não faz nenhum tratamento sobre o corpo da
mensagem, ou seja, a aplicação cliente deve enviar uma mensagem em um formato que a
aplicação servidora saiba tratar. Além disto, é possível que seja utilizada criptografia para
garantir a segurança da informação trafegada, porém, a encriptação e decriptação são
responsabilidades da aplicação cliente e servidora.
No sistema implementado, a aplicação cliente monta um objeto do tipo Nota, serializa
o mesmo para o formato XML através da API Marshaller do próprio Java, que retorna uma
String com o XML, descrito no Quadro 4, e transforma essa String em um array de bytes.
Com isto, na aplicação servidora torna-se necessário a partir dos array de bytes voltar ao
objeto serializado, isto foi implementado através da API Serialize do próprio .NET. É
importante que todo o ambiente utilize o mesmo charset para transformar a String em bytes, e
depois no servidor para transformar o array de bytes em String
43
Quadro 4 – Criação e Serialização de Nota em formato XML. 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Nota nota = new Nota();
nota.setAprovar(true);
nota.setCnpjDestinatario("123456789");
nota.setCnpjEmissor("987654321");
nota.setNomeDestinatario("Dest");
nota.setNomeEmissor("Emissor");
nota.setValue(100);
JAXBContext context = JAXBContext.newInstance(Nota.class);
Marshaller m = context.createMarshaller();
m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
StringWriter writer = new StringWriter();
m.marshal(nota, writer);
String msg = writer.getBuffer().toString();
Na linha 9 é iniciado um objeto do tipo JAXBContext, que é um ponto de entrada
para a utilização da API JAXB, passando o parâmetro com a classe que será serializada. Na
linha 11 é criado o Marshaller responsável pela serialização e na linha 15 é chamado o
método responsável pela serialização. Nota-se que a saída não ocorre em um String
diretamente, porém em um StringWriter. Na linha 16 é retornado o Buffer do
StringWriter e posteriormente transformado o Buffer em String através do método
toString.
O cliente envia as mensagens através da ferramenta RabbitMQ, onde é necessário se
conectar ao Broker e enviar uma mensagem na fila denominada NEW_TASK, além da
mensagem é necessário enviar ao middleware o nome da fila onde a aplicação cliente irá
aguardar resposta. Enquanto isto, outra thread busca por respostas na fila pré-definida. Ao
receber uma resposta a mesma é enviada para a saída padrão do sistema, no caso o console do
Eclipse. O Quadro 5 apresenta o código responsável pela criação do consumidor de
mensagens, conforme apresentado no Quadro 6.
Quadro 5 – Criação do consumidor utilizado na leitura de respostas 1
2
3
4
5
6
7
8
9
10
11
12
consumer = new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope, AMQP.BasicProperties properties,
byte[] body) throws IOException {
long deliveryTag = envelope.getDeliveryTag();
System.out.println(new String(body));
channel.basicAck(deliveryTag, false);
}
};
channel.queueDeclare(clientQueue, false, false, false, null);
Na linha 1 é criado o Consumer, e nas linhas 2 a 11 é sobrescrito o método
handleDelivery, para tratar a entrega das mensagens. Na linha 6 é retornado a
44
DeliveryTag da mensagem e na linha 9 é chamado o método basicAck passando a
DeliveryTag, este método envia ao broker uma notificação que a mensagem em questão
foi lida e pode ser retirada da fila.
Quadro 6 – Utilização do consumidor de mensagens 1 channel.basicConsume(clientQueue, consumer);
Na linha 1 é chamado o método basicConsume passando por parâmetros a fila que
será lida e o consumidor que irá tratar as mensagens. Este chamado irá retirar todas as
mensagens da fila no momento da chamada.
3.3.1.2 Aplicação servidora
Uma aplicação servidora é iniciada e recebe via parâmetro o nome da fila ao qual deve
ler requisições. Caso receba uma mensagem com o ID -1 e o texto STOP a aplicação
servidora deve retornar uma mensagem com o texto OK e se encerrar, caso contrário a
aplicação trata a requisição. No Quadro 7 é apresentado o código responsável pelo tratamento
das requisições pela aplicação servidora.
45
Quadro 7 – Tratamento da requisição pela aplicação servidora 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Message response = broker.ReadMessage(channel, ServerQueueName, 50,
null, queueDeclaring);
String resp;
String msg = Encoding.UTF8.GetString(response.Msg);
if ("-1".Equals(response.Id) && "STOP".Equals(msg))
{
serviceActive = false;
resp = "OK";
}
else {
var reader = XmlReader.Create(ToStream(msg.Trim()), new
XmlReaderSettings() { ConformanceLevel = ConformanceLevel.Document });
nota nota = new XmlSerializer(typeof(nota)).Deserialize(reader)
as nota;
Thread.Sleep(20);
if (nota.aprovar)
{
resp = "APROVADA";
}
else
{
resp = "REJEITADA";
}
}
byte[] body = Encoding.UTF8.GetBytes(resp);
broker.PublishMessage(channel, ServerQueueName + "_response", body,
null, queueDeclaring);
Na linha 6 é verificado se a mensagem é para a aplicação ser encerrada ou não. Nas
linhas 12 a 14 é serializada a nota fiscal para o objeto novamente. Na linha 29 é enviada a
resposta para o middleware.
3.3.1.3 AppStarter
As aplicações denominadas AppStarter são aplicações que são localizadas nas
máquinas que podem iniciar aplicações servidoras, e ao iniciarem se registram no middleware
através do envio uma mensagem na fila REGISTER_STARTER. A mensagem consiste em
uma string, com uma lista separada por ;, sendo que na primeira posição é informado o nome
da máquina, e após isto os tipos de aplicação que o AppStarter está apto a iniciar, sendo este
tipo um string qualquer. A partir deste momento a aplicação aguarda por solicitações do
middleware para iniciar aplicações. No Quadro 8 é apresentado o código responsável por
iniciar a aplicação servidora.
46
Quadro 8 – Inicialização de aplicação servidora 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new
System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
string serverName = System.Environment.MachineName + "|" + types + "|"
+ serverid;
serverid++;
startInfo.FileName = cmdLine;
startInfo.Arguments = "-name:" + serverName + param;
process.StartInfo = startInfo;
try
{
if (process.Start())
{
processes.Add(new ServerApp()
{
process = process,
name = serverName,
pid = process.Id
});
ret = serverName + "|" + process.Id.ToString();
}
else
{
ret = "Não foi possível iniciar a aplicação, nenhuma
exceção foi lançada porém a aplicação não iniciou.";
}
}
catch (Exception ex)
{
ret = "Não foi possível iniciar a aplicação, mensagem de erro
retornada: " + ex.Message;
LogUtils.Exception(ex);
}
Na Linha 1 é criado um novo processo, que é um recurso nativo, próprio para iniciar
processos, sendo apenas necessário informar o caminho do arquivo a ser iniciado e os
parâmetros, o que é feito nas linhas 8 e 9. Na linha 20 é verificada a propriedade Id do
processo, que retorna o PID do processo.
Esta aplicação também é responsável por retornar ao middleware periodicamente
informações sobre o status de utilização da máquina atual, como % de utilização de CPU,
memória RAM livre e o indicador Thread Queue Length do Processador. A partir destas
informações o middleware está apto a decidir se um AppStarter tem condições ou não de
iniciar uma nova aplicação servidora. O código fonte responsável pela inicialização dos
contadores de performance é apresentado no Quadro 9. No Quadro 10 é apresentado o código
responsável por calcular o valor dos contadores de performance.
47
Quadro 9 – Inicialização de contadores de performance 1
2
3
4
5
6
7
8
9
10
PerformanceCounter processorTime = new PerformanceCounter("Processor",
@"% Processor Time", @"_Total");
PerformanceCounter total_processor_queue = new
PerformanceCounter("System", "Processor Queue Length", "");
PerformanceCounter ram_avaliable = new PerformanceCounter("Memory",
"Available Bytes", "");
CounterSample a1 = processorTime.NextSample();
CounterSample b1 = ram_avaliable.NextSample();
CounterSample c1 = total_processor_queue.NextSample();
Nesta rotina é utilizada a classe PerformanceCounter, que é uma classe nativa
responsável por buscar valores dos contadores de performance publicados pelo Windows.
Estes contadores podem ser visualizados através do aplicativo "Monitor de Desempenho" no
Windows, sendo que todos os contadores utilizados são públicos e estão disponíveis pelo
menos desde o Windows 7.
Na Linha 1 é criado o contador de performance Processor\% Processor
Time\_Total, que é responsável por retornar a percentagem de utilização do processador,
uma média da utilização de todos os núcleos do processador. Na Linha 2 é criado o contador
de performance System\Processor Queue Length, que retorna o tamanho da fila do
processor, que é um indicador que complementa o uso do processador, pois por vezes o uso
do processador não está alto, porém a fila do processador está, isto geralmente é causado por
excesso de interrupções. Na Linha 3 é criado o contador Memory\Avaliable bytes,
que retorna a quantidade de memória livre.
São colhidas amostras iniciais através do método NextSample, utilizada
posteriormente para calcular os valores dos contadores de performance.
48
Quadro 10 – Cálculo dos contadores de performance 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Thread.Sleep(1000);
CounterSample a2 = processorTime.NextSample();
CounterSample b2 = ram_avaliable.NextSample();
CounterSample c2 = total_processor_queue.NextSample();
int a = (int)CounterSample.Calculate(a1, a2);
int b = (int)(CounterSample.Calculate(b1, b2) / 1024 / 1024);
int c = (int)CounterSample.Calculate(c1, c2);
string time = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss");
string statiscs = System.Environment.MachineName + "|" + time + "|" + a
+ "|" + b + "|" + c + "|" + pCount;
MessageUtils.PublishMessage(channel, MESSAGETYPE.STATISTICS.ToString(),
statiscs);
a1 = a2;
b1 = b2;
c1 = c2;
Nas linhas 3, 4 e 5 são colhidas novas amostras que são utilizadas para o cálculo do
valor real dos contadores, através do método Calculate, nas linhas 7, 8 e 9. Nas linhas 13
e 14 é formada o string de retorno, utilizando-se os valores calculados anteriormente.
A aplicação de inicialização também é responsável por verificar se as aplicações
servidoras ainda estão em execução, e caso não estejam mais deve informar ao middleware
(Quadro 11).
Quadro 11 – Verificação das aplicações ainda em execução 1
2
3
4
5
6
7
8
9
10
11
12
13
14
for (int i = 0; i < processes.Count(); i++)
{
processes[i].process.Refresh();
if (processes[i].process.HasExited)
{
MessageUtils.PublishMessage(channel,
MESSAGETYPE.UNREGISTER_SERVER.ToString(), processes[i].name);
processes.RemoveAt(i);
}
else
{
pCount++;
}
}
Na linha 3 é feito uma atualização das informações dos processos que estão em cache.
Na linha 4 é verificado se a aplicação ainda está executando e caso não esteja mais é enviado
ao middleware o nome da aplicação na fila UNREGISTER_SERVER. Após isto o processo é
retirado da fila. A variável pCount representa o somatório das aplicações que estão
inicializadas, e também é enviado ao middleware para ser monitorada.
49
3.3.1.4 Aplicação de monitoramento
A aplicação de monitoramento implementada utiliza a biblioteca JFreeChart para
exibir em gráficos os indicadores de utilização das máquinas. A iniciação dos objetos gráficos
é representada no Quadro 12.
Quadro 12 – Criação dos gráficos com JFreeChart 1
2
3
4
5
6
cpudataset = new TimeSeriesCollection();
cpuchart = ChartFactory.createTimeSeriesChart("Monitor de %CPU",
"Hora", "% CPU", cpudataset, true, false, false);
final XYPlot plotcpu = cpuchart.getXYPlot();
cpurendered = new XYLineAndShapeRenderer();
plotcpu.setRenderer(cpurendered);
Na Linha 1 é criado o objeto que representa uma coleção de séries. Na linha 2 é criado
um gráfico do tipo série de tempo, que possui medições de tempos em tempos. Na linha 4 é
criado um plot, utilizado pelo renderizador criado na linha 5. Para cada gráfico é necessário
repetir estes passos, por isto todo o código é repetido cinco vezes, para o gráfico de consumo
de CPU, memória RAM livre, thread queue length, contador de processos e quantidade de
mensagens do middleware.
Após a criação das coleções de séries é necessário criar as séries, nos gráficos de CPU,
RAM, thread queue length e contador de processos. Cada série representa uma máquina que
está executando a aplicação de inicialização. No Quadro 13 é exibido o código responsável
pela criação das séries.
Quadro 13 – Criação de séries 1
2
3
4
dev.cpuSeries = new TimeSeries(deviceName);
cpudataset.addSeries(dev.cpuSeries);
cpurendered.setSeriesPaint(series.size(), colors[series.size()]);
cpurendered.setSeriesStroke(series.size(), new BasicStroke(1.1f));
Na linha 1 é criada a série de consumo de CPU. Na linha 2 a série criada é adicionada
a lista de séries. Nas linhas 3 e 4 são informadas configurações de renderização da série,
como cor da linha e espessura dos pontos. No Quadro 14 é exibido o código responsável pela
adição dos pontos no gráfico.
Quadro 14 – Adição de pontos em gráfico utilizando JFreeChart 1
2
3
4
if (deviceSeries == null) {
deviceSeries = createDeviceSeries(series, counters[0]);
}
deviceSeries.cpuSeries.add(s, Integer.valueOf(counters[2]));
Na linha 1 é recuperado a série do dispositivo que está sendo adicionado no gráfico. Na
linha 2 é feito uma verificação se já existe uma série para o dispositivo, caso não existe então
cria uma nova série na linha 3, adiciona a mesma no dataset na linha 4 e na linha 6 e 7 são
50
configuradas opções de cor e espessura da linha. Na linha 11, independente de ser uma série
nova ou uma já existente, a medição atual é adicionada na série.
3.3.1.5 Middleware
O middleware é um aplicativo composto por um conjunto de threads, sendo que uma
delas é a thread de recepção de mensagens. A recepção das requisições consiste em se
conectar no broker e verificar se existe alguma nova mensagem na fila NEW_TASK. Quando o
broker retornar alguma mensagem, é verificada a fila e o tempo estimado de execução da
mensagem e a mesma é adicionada na fila. O Quadro 15 apresenta o código fonte responsável
pela adição da requisição na fila.
Quadro 15 - Adição de mensagens na fila 1
2
3
4
5
6
7
8
9
10
11
12
13
14
if ((msg.EstimedTime != 0) && (messages.Count > 20))
{
if ((messages[10].EstimedTime > msg.EstimedTime))
{
messages.Insert(10, msg);
}
else {
messages.Add(msg);
}
}
else
{
messages.Add(msg);
}
Nas linhas 1 e 3 são verificadas as condições para que a mensagem seja adicionada em
uma posição mais privilegiada da fila. Caso a fila de mensagens tenha mais de 20 mensagens,
o middleware verifica o tempo estimado de execução da requisição, que é composto pela
média do tempo que outras requisições do mesmo tipo levam para serem processadas. Caso
tenha algum tempo estimado e o mesmo seja menor que o tempo estimado de execução da 10ª
requisição, a requisição é coloca na 10ª posição da fila, caso contrário, a requisição é
adicionada ao final da fila. Desta forma, prioriza um pouco requisições cujo tempo estimado
de execução seja muito baixo, porém sem ficar sempre ordenando a lista de acordo com o
tempo de execução.
Outra thread do middleware é responsável por retirar mensagens da lista de
requisições, sempre na posição zero da lista, e encontrar uma aplicação servidora que esteja
apta a efetuar o processamento, e enviar para a mesma o devido processamento. No Quadro
16 é apresentado o código que verifica se uma máquina está apta a iniciar uma aplicação ou
executar uma requisição.
51
Quadro 16 – Verificação se a máquina pode executar requisição 1
2
3
4
5
6
private Boolean MachineMayExecuteApp(String deviceName)
{
var statisticsData = StatisticsStorage.findData(deviceName);
return (statisticsData == null) || (statisticsData.stats.cpu <
80);
}
Na linha 3 são carregados os dados estatísticos da máquina em que a aplicação
servidora está sendo executada e na linha 4 é feito uma validação sobre a % de uso de CPU da
máquina. Este método é chamado tanto para verificar se uma aplicação de uma máquina pode
receber nova requisição quanto para iniciar nova aplicação em uma máquina.
Caso nenhuma aplicação servidora esteja registrada, o mesmo verifica se existe alguma
aplicação AppStarter registrada que possa iniciar uma aplicação do tipo desejado e que a
máquina que está rodando o AppStarter não esteja sobrecarregada. Caso encontre, então
solicita ao AppStarter a inicialização da aplicação, que consiste em uma mensagem para a
aplicação AppStarter com o ID do tipo de aplicação servidora a ser iniciada, e o middleware
aguarda a resposta, que consiste em uma lista, separada por ; com o nome da máquina, o tipo
de aplicação servidora, o número da aplicação servidora e o PID do processo. Desta forma, o
middleware já registra internamente que essa aplicação servidora está livre e que a fila para
receber as mensagens é composta pela concatenação do nome da máquina, tipo de aplicação e
o número.
Após encontrar uma aplicação servidora para processar a requisição, o middleware
então envia a requisição e calcula o tempo de execução da requisição. Este processo é
apresentado no Quadro 17.
52
Quadro 17 – Envio de requisição e cálculo de tempo de execução 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int tickBefore;
try
{
tickBefore = Environment.TickCount;
server.StartChannel();
server.PublishMessage(server.name, message.message, message.id);
}
catch ( Exception ex)
{
System.Console.Out.WriteLine(ex.Message);
Queue(message);
server.iddle = true;
return;
}
Message resp = server.ReadMessage(server.name + "_response", 50);
int tickAfter = Environment.TickCount;
Thread t = new Thread(() => ExecutionTimeDAO.Insert(message.id,
server.machine, tickAfter - tickBefore));
t.Start();
using (IModel channel = message.Connector.StartChannel())
{
channel.BasicQos(0, 1, false);
message.Connector.PublishMessage(channel, message.sender,
resp.Msg, null);
}
server.iddle = true;
Na linha 1 é verificado o contador de Ticks do Windows, que é um número que é
incrementado a cada 1 milissegundo pelo próprio Windows. Na linha 17 é novamente
verificado o contador de Ticks e na linha 20 é subtraído o valor do segundo contador pelo
primeiro contador e isto resulta na quantidade de milissegundos que levou para executar a
operação. Este valor é salvo em uma tabela para depois ser utilizado para identificar o tempo
médio de outras requisições do mesmo tipo.
Após o retorno da resposta do processamento da requisição, o middleware verifica qual
o nome da fila que o cliente solicitou a resposta e então retorna ao cliente a resposta da
solicitação.
O middleware ao receber dados de utilização de máquinas armazena os mesmos na
base de dados e mantém o último registro de cada máquina em memória, para ser utilizado
pelas rotinas de iniciação de aplicações servidoras e de requisição. A cada 30 segundos, o
middleware lê a lista com os últimos dados de cada máquina e envia para as aplicações de
monitoramento abertas. As aplicações de monitoramento se registram através do envio de
uma mensagem com o texto REGISTERMONITOR, e a partir deste momento estão aptas a
receber as informações de monitoramento.
53
Para garantir que exista uma quantidade de aplicações servidoras condizente com a
quantidade de requisições, o middleware possui uma thread que a cada segundo verifica
quantas requisições de cada tipo entraram neste segundo e quantas aplicações existem de cada
tipo, e então, verifica se é necessário iniciar novas aplicações ou se é possível remover
aplicações. No Quadro 18 é apresentado o código responsável pela lógica.
Quadro 18 – Verificação de quantidade de aplicações servidoras ativas 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
foreach (KeyValuePair<string, int> entry in msgs)
{
int minServers = (int)Math.Truncate((double)(entry.Value /
minServersRatio));
int maxServers = (int)Math.Truncate((double)(entry.Value /
maxServersRatio));
minServers = minServers > 0 ? minServers : 1;
maxServers = maxServers > 0 ? maxServers : 1;
servers.TryGetValue(entry.Key, out serverNames);
if (serverNames != null)
{
if (serverNames.Count() < minServers)
{
StartServer(entry.Key, minServers -
serverNames.Count());
}
else if (serverNames.Count() > maxServers)
{
StopServer(entry.Key, serverNames.Count() -
maxServers);
}
}
}
Na linha 3 é calculado a quantidade mínima de aplicações necessárias, que consiste em
1 aplicação para cada n requisições na fila. Na linha 5 é calculado a quantidade máxima de
aplicações necessárias, que consiste em 1 aplicação para cada n requisições na fila. Os valores
para requisições mínimas e máximas estão configuradas no arquivo App.Config, nos
campos minServersRatio e maxServersRatio. Desta forma, enquanto a quantidade
de aplicações servidoras estiver dentro do limite não altera. Porém, se o número de aplicações
for menor que o mínimo é chamado o método StartServer. Caso o número de aplicações
seja maior que o máximo, é chamado o StopServer. Ambos os métodos recebem o tipo de
aplicação que deve iniciar ou encerrar e a quantidade.
A inicialização de uma aplicação servidora consiste na varredura dos AppStarter
registrados, onde é verificado se o AppStarter já está iniciando alguma aplicação e se os dados
de uso da máquina indicam que a máquina em questão está apta a iniciar uma nova aplicação
servidora. Este trecho de código é está ilustrado Quadro 19.
54
Quadro 19 – Verificação de AppStarter apto a iniciar aplicação 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
List<ServerInitiator> initiatorList;
ServerInitiator initiator = null;
initiators.TryGetValue(kind, out initiatorList);
if (initiatorList != null)
{
int i = 0;
while ((i < initiatorList.Count()) && (count != 0))
{
initiator = initiatorList[i];
if ((!initiator.initializing) &&
(MachineMayExecuteApp(initiator.machine)) &&
initiator.initializeServer(kind, this))
{
count--;
}
i++;
}
}
Na linha 3 é carregada a lista de AppStarter que iniciam aplicações do tipo que se
deseja abrir, então, na linha 7 a lista é iterada, e na linha 11 é chamado um método
responsável pela verificação se uma máquina pode ou não executar uma nova aplicação
servidora, que está representado no Quadro 16. Na linha 12 é chamado o método
initializeServer responsável por enviar uma mensagem ao AppStarter informando o
tipo de aplicação que o mesmo deve iniciar.
O encerramento de uma aplicação servidora consiste na varredura da lista de
aplicações servidoras abertas ociosas e então enviar uma mensagem com o id -1 e o texto
STOP.
3.4 RESULTADOS E DISCUSSÕES
Nesta seção são apresentados os resultados obtidos através dos experimentos
realizados com o middleware. A seção 3.4.1 apresenta os resultados obtidos quando
executado todo o ambiente em uma máquina, na seção 3.4.2 apresenta os resultados obtidos
quando executando o ambiente de forma dividida em duas máquinas. A seção 3.4.3 apresenta
uma discussão sobre os principais problemas enfrentados durante os testes de desempenho.
3.4.1 Execução em uma máquina
A execução em uma máquina foi realizado em um notebook pessoal. Inicialmente foi
inicializado o middleware e após isto o AppStarter e a aplicação de monitoramento, e em
seguida a aplicação cliente. Após a inicialização da aplicação cliente foi aumentado a
quantidade de threads gerando XML de notas fiscais de produto, e o resultado pode ser visto
na Figura 26 e na Figura 27.
55
Figura 26 – Primeira parte dos gráficos de informações de uso do primeiro teste
56
Figura 27 – Segunda parte dos gráficos de informações de uso do primeiro teste
Na Figura 26 é possível identificar que pouco após as 18h38min houve um aumento no
número de requisições novas e requisições processadas, da mesma forma, na Figura 27 há o
aumento na quantidade de processos. A quantidade de requisições processadas é raramente
igual á quantidade de novas requisições e sempre acabam ficando algumas mensagens na fila.
Conforme se aumenta a quantidade de requisições, é aumentada a quantidade de processos
tratando as requisições, mantendo os valores de novas requisições e requisições tratadas
sempre próximos.
Ao passar de 200 novas mensagens nota-se que o gráfico de Thread Queue Length
começa a sair do zero e oscilar, da mesma forma a quantidade de novas mensagens oscila
muito. Os dois fatores estão ligados, pois, embora a percentagem de uso da CPU esteja baixa,
existem threads que estão prontas para receber tempo de processamento, porém estão
aguardando na fila, ou seja, este parâmetro acaba influenciando negativamente na
performance do sistema.
Após passar de 250 novas mensagens, o middleware não consegue mais receber todas
as mensagens que o broker recebe, com isto, embora o número de novas mensagens não
aumente, o broker começa a acumular mensagens na sua fila, isto pode ser visto na Figura 28.
57
Figura 28 – Fila de mensagens no broker e taxa de mensagens do primeiro teste
Na Figura 28 é possível identificar dois quadros: o quadro com a fila de mensagens e o
quadro com a taxa de mensagens. No primeiro quadro é possível verificar que existem
mensagens que acabam ficando na fila do broker, no caso do primeiro teste pouco mais de
100 mensagens acabaram ficando na fila do broker. No segundo quadro é possível identificar
as taxas de mensagens publicadas, mensagens entregues, mensagens reconhecidas e
mensagens recebidas.
Mensagens publicadas é o total de mensagens que foram enviadas para o broker do
RabbitMQ a partir de qualquer uma das aplicações. As mensagens entregues são as
mensagens que foram recebidas a partir do modo de subscrição, onde é registrado um
consumidor de mensagens e o mesmo consome mensagens em determinada fila. Já as
mensagens recebidas são mensagens obtidas através de uma chamada específica ao método
Get. As mensagens reconhecidas são todas as mensagens que a aplicação envia para o broker
confirmando o recebido de alguma mensagem, de qualquer um dos métodos.
No teste foram publicadas aproximadamente 200 mensagens por segundo, além disto,
são entregues 96 por segundo através dos métodos consumidores, 104 mensagens são
recebidas através dos métodos de recepção individual, e 207 mensagens tem o seu
recebimento confirmado. Os recebimentos confirmados consistem em uma mensagem de
confirmação sobre cada mensagem recebida, que é necessária tanto para mensagens recebidas
58
através de consumidores como parar mensagens recebidas individualmente. O total de
mensagens trafegadas chega a aproximadamente 600 mensagens por segundo.
A taxa de mensagens baixa se deve, principalmente, pela utilização de uma máquina de
baixo processamento e pelo fato de utilizar várias filas diferentes, com mais de uma forma de
envio recebimento de mensagens e pelo fato de necessitar retornar a confirmação do
recebimento das mensagens. Além disto, com todas as aplicações na mesma máquina
sobrecarrega o cliente do broker.
No experimento realizado, a memória RAM teve pouca influência no resultado final,
pois havia memória sobrando e as aplicações utilizavam pouca memória, porém, dependendo
das aplicações sendo executadas, a memória disponível pode passar a ser um fator com maior
relevância, por isto a mesma foi mantida sobre monitoramento.
3.4.2 Execução em duas máquinas
A execução do ambiente em duas máquinas foi realizado em dois notebooks pessoais.
Inicialmente foi iniciado o middleware e o broker em um com nome PAULO-NOTE, após
isto, no mesmo notebook foi iniciada a aplicação AppStarter, porém, no arquivo de
configurações foi alterado o tipo de server que a aplicação inicia para um tipo não utilizado,
desta forma, a aplicação não iria iniciar nenhuma aplicação servidora, porém iria retornar os
dados de utilização da máquina. Foi iniciado também o aplicativo de monitoramento.
Com os preparativos em uma máquina feitos, foi iniciado na outra máquina, de nome
NOTE-REGINA, a aplicação AppStarter e a aplicação cliente. Após as aplicações iniciadas,
foram aumentadas as threads que simulam a geração de XML de nota fiscal de produto e os
resultados são exibidos na Figura 29 e na Figura 30.
59
Figura 29 - Primeira parte dos gráficos de informações de uso do segundo teste
60
Figura 30 – Segunda parte dos gráficos de informações de uso do segundo teste
No início do teste, a quantidade de threads foi sendo aumentada e conforme
aumentava mais mensagens eram recebidas pelo middleware e mais mensagens eram
processadas. A quantidade de mensagens processadas é, na maioria dos casos, igual á
quantidade de novas requisições, por isto não fica visível no gráfico, porém, em alguns
momentos é possível identificar os pontos azuis próximos aos amarelos, ou linhas azuis
entrelaçadas às amarelas.
Para os parâmetros “minQueueRatio” e “maxQueueRatio” foram configurados os
valores cento e cinquenta e cento e trinta, respectivamente. Isto significa que a cada cento e
cinquenta requisições deveria ter pelo menos uma aplicação servidora para processar, e a cada
cento e trinta poderia ter no máximo uma aplicação servidora. É possível ver na Figura 29 a
quantidade de requisições novas e na Figura 30 a quantidade de aplicações servidoras, desta
forma, consegue-se perceber que pouco após as 05h47min a quantidade de requisições
ultrapassou cento e cinquenta, e, pouco após as 05h47min a quantidade de processos subiu de
um para dois.
Pouco após as 05h48min, na Figura 30 é possível identificar que a quantidade de
processos caiu de três para dois e na Figura 29 é possível ver que a quantidade de novas
requisições ficou abaixo de duzentos e sessenta, ou seja, menos que duas vezes o valor
61
configurado em “maxQueueRatio”, por isso, a quantidade de processos foi diminuída de 3
para 2.
Após 05h50min é possível verificar que a quantidade de novas requisições e de
requisições sendo tratadas manteve-se, isto pois mesmo aumentando significativamente a
quantidade de threads enviando requisições, não foi possível enviar mais requisições. Porém,
mesmo neste cenário já foi possível verificar uma quantidade relativamente boa de
requisições sendo enviadas e tratadas pelo broker. A Figura 31 apresenta a página de
gerenciamento do RabbitMQ, onde é apresentada uma visão geral do funcionamento do
broker.
Figura 31 – Fila de mensagens do broker e taxa de mensagens do segundo teste
Na Figura 31 é possível verificar que a fila de mensagens do broker fica quase vazia,
pois as mensagens estão sempre sendo lidas. Também é possível verificar a taxa de
mensagens. Neste teste foram publicadas em média 392 mensagens por segundo, 197
mensagens foram entregues através de consumidores, 196 foram recebidas através do método
de recepção individual e 393 mensagens de confirmação de recebimento de mensagens,
totalizando 1178 mensagens por segundo.
O resultado da taxa de mensagens no segundo teste é superior ao primeiro, devido à
divisão da carga de interações com o broker entre duas máquinas e ao fato de que o Thread
Queue Length não afetou tanto este ambiente, conforme pode ser visto na Figura 29.
62
3.4.3 Principais dificuldades ao utilizar o RabbitMQ
Para utilizar o RabbitMQ, esperava-se que cada aplicação tenha apenas uma conexão
com o broker, e cada thread possua um canal de comunicação aberto a partir desta conexão
central. Porém, mesmo utilizando o RabbitMQ desta forma ainda ocorriam vários erros
incomuns. Um exemplo é o erro “The AMQP operation was interrupted: AMQP close-reason,
initiated by Application, code=200, text="Goodbye", classId=0, methodId=0, cause= /”, que
ocorria principalmente ao chamar o método IModel.basicGet ou
Connection.CreateModel.
Para tratar esta situação foi necessário implementar uma camada intermediária
responsável por verificar se houve exceções do RabbitMQ no canal de comunicação ou na
própria conexão, e caso houvesse então reiniciava a conexão ou canal de comunicação. Isto
em parte solucionou os erros na aplicação, porém aumentou a complexidade da aplicação e
comprometeu o desempenho. Embora não tenham sido gerados dados estatísticos dos erros,
foi percebido que era mais comum que os erros ocorressem quando a utilização de CPU
estava muito alta.
Em versões anteriores do middleware, um grande problema foi a utilização de CPU,
pois em alguns pontos não fazia sentido criar um Consumer para receber uma única
mensagem, desta forma, era necessário aguardar uma requisição individual. Nestes momentos
geralmente é realizado um loop sem chamar o método Thread.Sleep, ou passando um
valor muito baixo, pois é necessário que a resposta assim que disponível seja recebida.
Não chamar o método Sleep faz com que o processador continue executando sempre
as operações de verificação de condição (recebeu resposta ou mudança no estado de objeto) e
pula para próxima linha que volta para a verificação de condição, este processo fica
executando até que haja a mudança na condição, e isto demanda processamento e acaba
elevando o consumo de CPU.
Chamar o método Sleep dentro dos laços de verificação de condição com um valor
mais alto possibilita que o processador pare o processamento da thread em execução e atenda
novas threads ou processos, diminuindo o consumo de CPU, porém, se a resposta chegar
enquanto o método Sleep está executando é necessário aguardar o final da execução do
método, aumentando o tempo de tratamento da requisição. Com isso, foi identificado que em
alguns pontos foi necessário trocar performance por diminuição do consumo de CPU.
63
CONCLUSÕES
Os resultados alcançados foram satisfatórios. A aplicação de envio de NF-e para
validação foi implementada, gerando um XML de uma nota e enviando para o middleware. O
middleware atingiu o objetivo de iniciar e encerrar aplicações de acordo com a demanda e
balancear a utilização entre as máquinas disponíveis, isto foi implementado impondo um
limite no uso de CPU das máquinas, e ao chegar neste limite o middleware não inicia novas
aplicações nesta máquina e envia para outra.
A aplicação de monitoramento também atingiu o objetivo, pois ajuda a acompanhar o
desempenho do sistema como um todo, não apenas do middleware, ajudando a compreender
os gargalos e pontos a melhorar do ambiente, exibindo informações de utilização da máquina
e informações de mensagens processadas, novas mensagens e mensagens que já estão na fila.
A utilização da biblioteca RabbitMQ no middleware trouxe ganhos ao agilizar e retirar
a necessidade de preocupação com as questões do tráfego de dados na rede, porém, gerou
vários problemas em relação a confiabilidade: a grande maioria das interações com o
RabbitMQ necessitou de tratamento de exceções.
Como limitação, destacam-se as oportunidades de melhoria apontadas no ítem 4.1,
como a previsão de períodos de pico e antecipação quanto ao levantamento de aplicações de
processamento, além disto, não há tratamento para mais de um middleware executando no
mesmo ambiente.
Conclui-se que o desenvolvimento deste trabalho possibilitou uma alternativa para a
utilização de middleware para escalonar aplicações e gerenciar uma fila de mensagens, em
conjunto com o RabbitMQ.
3.5 EXTENSÕES
Para extensões deste trabalho propõem-se:
a) a partir da quantidade de requisições prever períodos de pico de utilização e
inicializar aplicações antecipadamente;
b) ao encerrar um middleware, suas mensagens não devem ser perdidas e outro(s)
middleware(s) devem assumí-las;
c) melhoria no algoritmo de priorização de requisições;
d) implementar tempo máximo de uma requisição na fila;
e) implementação de segurança, garantindo a integridade e autenticidade das
mensagens trafegadas, além de garantir que somente aplicações permitidas possam
interagir com o mesmo.
64
REFERÊNCIAS
AMAZON WEB SERVICES. (n.d.) 2015. Disponível em:
<http://docs.aws.amazon.com/AutoScaling/latest/DeveloperGuide/policy_creating.html>.
Acesso em: 19/10/2015
ABIRAMI, S.P.; RAMANATHAN, Shalini. Linear Scheduling Strategy for Resource
Allocation in Cloud Environment. International Journal on Cloud Computing: Services
and Architecture(IJCCSA),v.2, n.1, p. 9-17. Fev. 2012
BAKKEN, Dave. Middleware. Encyclopedia of Distributed Computing, Kluwer Academic
Press, 2003.
BERNSTEIN, Philip A. Middleware An architecture for Distributed System Services. CLR
93/6. Disponível em: <http://www.hpl.hp.com/techreports/Compaq-DEC/CRL-93-6.pdf>.
Acesso em: 03 abr. 2016
BLAIR, G; COULOURIS, G; DOLLIMORE, J; KINDBERG, T. Sistemas Distribuídos:
Conceitos e Projeto. São Paulo: Bookman Editora Ltda, 2013
CHAPPELL, David A. Enterprise Service Bus: Theory in Practice. O’Really Media Inc.
2004
EUGSTER, PATRICK TH.; FELBER, PASCAL A.; GUERRAOUI, RACHID;
KERMARREC, ANNE-MARIE. The Many Faces of Publish/Subscribe. ACM Computing
Surveys, Vol. 35, No. 2, June 2003, pp. 114–131.
HOHPE, Gregor; WOOLF, Bobby. Enterprise Integration Patterns: Designing, Building,
and Deploying Messaging Solutions. Addison-Wesley Professional, 2003
ISHII, Renato P. NBSP: Uma política de escalonamento network-bound para aplicações
paralelas distribuídas. 2004. Dissertação (Mestrado em Ciências da Computação e
Matemática Computacional) – Ciências de Computação e Matemática Computacional,
Instituto de Ciências Matemáticas e de Computação - ICMC-USP, São Carlos.
JACOBSON, Daniel; YUAN, Danny; JOSHI, Neeraj. Scryer: Netflix: The Netflix Tech Blog.
2013. Disponível em: <http://techblog.netflix.com/2013/11/scryer-netflixs-predictive-auto-
scaling.html>. Acesso em: 18 set. 2015
KRAKOWIAK, S. Middleware Architecture with Patterns and Frameworks. 2009.
Disponível em: <http://proton.inrialpes.fr/~krakowia/MW-Book/main-onebib.pdf>. Acesso
em: 18/09/2015
MACHADO, Rivaldo R. J. Desenvolvimento de um middleware para comunicação via
web services e sua aplicação em sistemas de aquisição de dados industriais. 2014. 37 f.
Dissertação (Mestrado em Ciências) – Curso de Pós-Graduação em Engenharia Elétrica e de
Computação,Universidade Federal do Rio Grande do Norte, Natal.
MACIEL, Rita S. P.; ASSIS, Semírames R. Middleware: uma solução para o
desenvolvimento de aplicações distribuídas. CienteFico. v.4, n.1, p. (n.d.). janeiro-junho.
2014. Disponível em: <
http://www.cin.ufpe.br/~dmrac/infras%20de%20software/I.8.Semiramis.Middleware.pdf>.
Acesso em: 22 set 2015
MAHMOUD, Qusay H. Middleware for communications. John Wiley & Sons Ltd,
England. 2004
65
MONSON-HAEFEL, Richard; CHAPPELL, David A. Java Messaging Service. O’Reilly
Media Inc. 2000
NF-E. (n.d.) 2015. Disponível em:
<http://www.nfe.fazenda.gov.br/portal/perguntasFrequentes.aspx?tipoConteudo=E4+tmY+O
Df4=>. Acesso em: 31 ago. 2015
NOVELL Java Message Service. 2004. Disponível em:
<http://www.novell.com/documentation/extend52/Docs/help/MP/jms/concepts/jms.html>.
Acesso em: 03 abr. 2016
RABBITMQ (n.d.) 2016 Disponível em: <http://www.rabbitmq.com/documentation.html>.
Acesso em: 29 mar. 2016
ROSENBAND, Daniel L. A Remote Procedure Call Library. 1997. Disponível em:
<http://web.mit.edu/6.033/1997/reports/dp1-danlief.html>. Acessado em: 03 abr. 2016
SENGER, Luciano J. Obtenção e Utilização do Conhecimento sobre Aplicações Paralelas
no Escalonamento em Sistemas Computacionais Distribuídos. 2002. Monografia
(Doutorado em Ciências) – Ciências de Computação e Matemática Computacional, Instituto
de Ciências Matemáticas e de Computação - ICMC-USP, São Carlos.
SENGER, Luciano J. Escalonamento de processos: uma abordagem dinâmica e incremental
para a exploração de características de aplicações paralelas. 2004. Tese (Doutorado em
Ciências) – Ciências de Computação e Matemática Computacional, Instituto de Ciências
Matemáticas e de Computação - ICMC-USP, São Carlos.
SHYAM, Radhe; NANDAL, Sunil K. Improved Mean Round Robin with Shortest Job First
Scheduling. International Journal of Advanced Research in Computer Science and
Software Engineering. v.4, n.7, p. 170-179. Jul. 2014
SORDI, José O. de; MARINHO, Bernadete de L. Integração entre Sistemas: Análise das
Abordagens Praticadas pelas Corporações Brasileiras. RBGN, São Paulo, v. 9, n. 23, p.78-93,
jan./abr. 2007
THIRUVATHUKAL, George K; KAYLOR, Joe. [2013]. Disponível em:
<http://books.cs.luc.edu/distributedsystems/issues.html>
VARELA, Matías Leandro. Congresso Argentino de Ciencias de la Computacion. 1, 2007,
Correntes e Resistência. Conceptos fundamentales de un Middleware y razones de su
importancia em el mundo de hoy. Argentina: Universidad Nacional del Nordeste. v. 1.