Integrando Amazon Web Services (AWS) com Spring Framework

Este artigo aborda de forma didática como utilizar recursos da Amazon Web Services (AWS) integrados ao framework Spring Integration.

Fique por dentro
Vamos aproveitar toda a capacidade e elasticidade da Amazon Web Services, principalmente por meio do uso dos serviços de fila, banco de dados e Auto Scaling. Começaremos com a preparação do ambiente para construção da aplicação e vamos até a implantação na nuvem. A arquitetura proposta maximiza a utilização dos recursos computacionais no processamento massivo de mensagens. Com um grande volume de mensagens acumuladas, adicionamos máquinas automaticamente, e quando o volume acumulado diminui, reduzimos o número de máquinas. Desta maneira, conseguimos processar grandes volumes e utilizar a infraestrutura necessária de maneira pontual, otimizando as despesas e a performance do sistema.

Muitos motivos podem levar uma aplicação a processar grandes volumes de dados subitamente. Desde uma propaganda no horário nobre da televisão, sucesso repentino ou até mesmo um vídeo viral feito por um funcionário descuidado, porém criativo.

Quando isso acontece, a primeira coisa que pensamos é se os servidores ou a aplicação vão suportar o aumento súbito de demanda. Seria bom se tivéssemos uma chave mágica, que quando girada, aumentasse a capacidade da infraestrutura de atender mais demanda. Mas ainda assim teríamos que virar a chave. Já pensou a correria! “Girem a chave, rápido!”. Seria ainda melhor se pudéssemos automatizar a virada da chave.

Felizmente essa “brincadeira” já é uma realidade. Estamos falando de Cloud Computing, um conceito muito utilizado para definir a adoção flexível de infraestrutura como um serviço. O conceito é relativamente recente, porém a Amazon Web Services já oferece este tipo de serviço desde 2006, quando ainda não se tinha uma definição clara do que estava por vir.

Com base nisso, neste artigo vamos propor uma aplicação utilizando o Spring Integration implantada sobre uma arquitetura naturalmente elástica, ou seja, quando a aplicação entender que precisa de mais recursos computacionais, serão adicionados novos servidores automaticamente para processarem as informações. Este recurso, conhecido como elasticidade, é fornecido pelos serviços da Amazon Web Services (AWS), que serão descritos ao longo do artigo.

Sobre os serviços da AWS, vale ressaltar que para a maioria deles existe uma faixa de uso gratuito, durante o período máximo de um ano, contando da data de criação da conta. Para entender mais sobre esta faixa de serviços gratuitos, acesse o endereço na seção de referências.

Voltando à arquitetura que será proposta, a aplicação conseguirá atender prontamente ao aumento súbito de requisições, além de diminuir drasticamente o risco de indisponibilidade e manter o tempo de resposta mais estável.

Sobre os benefícios de se adotar uma solução em Nuvem, veja o artigo “Leve sua aplicação para a Nuvem em minutos”, publicado na edição 115 da Java Magazine, no qual discutimos alguns benefícios, como custos, flexibilidade de infraestrutura e velocidade para inovação.

Desafio

Considere o seguinte cenário: final do ano, intervalo do jogo final do Campeonato Brasileiro de Futebol. Veiculamos uma promoção muito bem direcionada ao público do jogo. Para participar desta promoção, as pessoas terão que enviar um SMS para se inscreverem. A quantidade de mensagens que chegarão para processamento será gigantesca e o volume poderá passar da previsão estimada, deixando a infraestrutura subestimada, causando erros ou sobrecarga em todo o sistema.

Neste artigo vamos criar a aplicação responsável em processar este grande volume de mensagens utilizando recursos de mensageiro da Amazon Web Services. Cada mensagem recebida representará um pagamento processado e uma ou várias regras de negócio deverão ser executadas sobre cada mensagem.

O desafio que queremos atender está no volume de mensagens e na grande variação do número de transações, já que a quantidade de pagamentos pode sofrer aumentos súbitos.

Sendo assim, teremos que nos preocupar em organizar as mensagens de pagamento/inscrição a fim de garantir que todas elas sejam processadas e que o processamento seja idempotente, ou seja, se a mesma mensagem entrar na fila de processamento por mais de uma vez, o resultado final será o mesmo.

Assim como a garantia de processamento é importante, o tratamento de erros também será. Precisamos saber qual a mensagem que gerou erro, os motivos e talvez enfileirar novamente para segunda tentativa e posterior análise manual. Uma mensagem de aviso aos administradores sobre erros no processamento das mensagens também seria muito bem vinda.

Outro desafio será reduzir os custos desta infraestrutura, já que o número de servidores para atender os momentos de pico será muito maior que o número de servidores durante a carga regular da aplicação. Desta maneira, adicionar mais servidores pontualmente, baseado na demanda, será muito melhor do que investir adiantado em uma infraestrutura que poderá, ou não, atender a demanda exigida.

Além da adição pontual de recursos computacionais, também tentaremos usar o máximo de CPU de cada servidor, o que poderá reduzir o número de máquinas necessárias, reduzindo também o custo.

Arquitetura proposta

A arquitetura que vamos propor usará uma fila de mensagens como porta de entrada das transações. Esta fila deve ser capaz de receber grandes volumes de mensagens e garantir a sua própria disponibilidade, independente da quantidade de conexões.

O uso de filas nos permitirá processar as transações de maneira assíncrona, ou seja, iremos primeiramente registrar as mensagens, assegurando seu recebimento. Logo após, servidores de retaguarda consumirão a fila à procura de mensagens para processar.

Neste cenário, ainda não evitaremos o grande acúmulo de mensagens na fila. Para eliminar este problema de acúmulo, adicionaremos novos servidores automaticamente baseados no número de mensagens enfileiradas, aumentando a velocidade de processamento de mensagens e fazendo com que a fila se esvazie mais rapidamente.

Existem vários produtos no mercado para a criação e gerenciamento de filas, entre eles temos o IBM WebSphere MQ (pago), o RabbitMQ (Open Source) e Apache ActiveMQ (Open Source). Quando você compra ou baixa um destes produtos, você terá que instalar, configurar e manter o ambiente rodando. No caso de grandes volumes e que necessite de alta disponibilidade, você ainda deverá construir um ambiente mais complexo, com mais máquinas para manter e configurar, e assim assegurar que as filas conseguirão receber e armazenar todas as mensagens.

Devido à complexidade que tal implantação pode alcançar, o uso de um serviço de fila se torna extremamente atraente, principalmente pelo custo de criação e manutenção. Usaremos então o serviço de fila da Amazon Web Service, o SQS (Simple Queue Service). Este serviço garante a alta disponibilidade e recebimento de grandes volumes de dados, escalando naturalmente para responder à demanda. Como usaremos toda a infraestrutura da AWS para construir nossa arquitetura e aplicação, a comunicação entre os componentes ficará ainda mais rápida, já que estarão implantadas no mesmo ambiente.

Desta maneira, colocaremos nossos servidores de retaguarda lendo as mensagens da fila, porém, para estes servidores usaremos uma estratégia de alta disponibilidade, distribuindo os servidores em dois datacenters geograficamente separados, para garantir a continuidade da operação mesmo que aconteça algum problema físico em um datacenter ou região do país. Para o serviço de fila não precisamos nos preocupar com isto, já que o serviço da AWS já possui tal característica.

Vamos dar uma pausa aqui e refletir sobre a arquitetura proposta até o momento. Pense se estivéssemos falando desta estratégia há 10 anos. Será que teríamos tamanha coragem para propor tal arquitetura para nossos chefes? Como tornaríamos esta operação viável do ponto de vista financeiro? Como implantaríamos uma aplicação distribuída deste porte em poucos minutos? Graças aos serviços da Amazon Web Services, hoje temos esta facilidade e viabilidade de custos para propor tal arquitetura.

A Figura 1 exibe os componentes propostos e suas interações. No desenho já usamos os nomes devidos dos serviços da AWS. São eles:

  • SQS (Simple Queue System): serviço de armazenamento de mensagens;
  • DynamoDB: serviço totalmente gerenciado de banco de dados NoSQL;
  • SNS (Simple Notification Service): serviço para notificações, fácil de configurar e operar. Disponibiliza vários canais e protocolos para envio de mensagens, como: HTTP/HTTPS, Email/Email-JSON, SMS e o próprio SQS;
  • Auto Scaling: serviço que permite aumentar ou diminuir o número de servidores baseado em métricas pré-configuradas.
Figura 1. Arquitetura proposta para a aplicação

Spring Integration e processamento paralelo

Como queremos reduzir o custo da solução, tentaremos usar o máximo de CPU disponível em cada servidor (instâncias no mundo da AWS). Sendo assim, um servidor deverá ser capaz de processar uma ou mais mensagens da fila, de forma paralela, utilizando recursos de multithread da JVM. Para facilitar a criação de threads e canais para troca de mensagens, usaremos o Spring Integration.

O Spring Integration fornece uma extensão ao modelo de desenvolvimento do framework Spring. Ele provê a capacidade de criação de canais de comunicação que podem ser utilizados internamente na aplicação ou como porta de entrada e saída de mensagens. Seu objetivo é fornecer uma maneira leve e rápida para a implementação dos Enterprise Integration Patterns-EIP (mais informações na seção Links no final do artigo).

O principal conceito que o Spring Integration abstrai e ajuda a implementar é o de canal (channel). Com um canal podemos abstrair a tecnologia utilizada entre quem gera a mensagem e quem recebe. Devido o desacoplamento que ele proporciona entre as pontas do canal, fica extremamente fácil o uso de multithread para processamento das mensagens, já que ambas as pontas não se enxergam e não mantêm nenhuma relação de dependência. Além disso, proporciona regras de rota entre canais, filtros e transformadores de mensagens.

A Figura 2 mostra uma abstração simples entre o criador da mensagem e seu receptor. Entre as duas pontas, temos um canal que abstrai a tecnologia usada por ambos.

Figura 2. Desacoplamento das pontas utilizando-se canais – Fonte: http://www.eaipatterns.com/

Seguindo este conceito, leremos as mensagens da fila e colocaremos em um canal definido pelo Sprint-Integration. Para ler as mensagens da fila, usaremos outro padrão definido pelo EIP, chamado Channel Adapter. Como o próprio nome diz, criaremos um adaptador para a tecnologia de fila que estamos usando, que neste caso será a Amazon SQS. Este adaptador buscará as mensagens da fila e colocará em um canal interno para processamento. A Figura 3 ilustra o seu funcionamento.

Figura 3. Usando um Channel Adapter para ler as mensagens da fila

Como queremos otimizar o uso de CPU, usaremos várias threads para buscar as mensagens na fila, ou seja, teremos vários adaptadores executando o polling (leitura de mensagens da fila).

Como estamos trabalhando com processamento distribuído, tanto dos servidores como da fila, devido ao grande de volume de mensagens e do tempo para sincronização dos nós (estamos falando de milissegundos), pode ocorrer de uma mesma mensagem entrar no canal de processamento em servidores concorrentes. Para garantir que processemos apenas uma vez a mensagem, usaremos outro padrão, o Message Filter. Desta maneira, assim que a mensagem for encaminhada para os servidores, verificaremos se ela se encontra em processamento ou se já foi processada. Desta maneira, garantimos que o processamento das mensagens seja idempotente. A Figura 4 mostra visualmente como funciona este padrão.

" [...] continue lendo...

Artigos relacionados