Arquivo da categoria: Programação

Clusters de aplicações Java

Ao criar sistemas utilizados por milhares (ou milhões) de usuários diariamente, o arquiteto da solução precisa sempre se fazer algumas perguntas (ou pelo menos deveria):

Minha solução é capaz de suportar o número esperado de usuários?

O que fazer se o sistema não der conta da demanda de usuários?

E se um servidor sair do ar enquanto usuários estão acessando o sistema?

Não pensar nestas questões pode custar muito caro quando o sistema entra em produção. Se o sistema não aguentar a demanda de usuários e a equipe responsável não pensou em alguma forma para aumentar a capacidade do sistema de forma rápida, só resta colocar uma plaquinha “Em manutenção” e aguentar os esporros do chefe. Se o servidor tem algum problema e sai do ar enquanto estava em produção, isso pode gerar desde um aborrecimento com os clientes até perda de dados importantes. De qualquer forma, isso se traduz em prejuízos.

O quanto os seus servidores aguentam?

O quanto os seus servidores aguentam?

Já houve um tempo em que os sistemas rodavam apenas em um servidor, e para aumentar sua capacidade bastava adicionar mais memória RAM, ou colocar uma CPU mais turbinada. Mas hoje a ordem de grandeza para quantidade de usuários que podem acessar um sistema Web, por exemplo, é muito maior. E difícil de prever com exatidão. Desse jeito você tem como alternativas:

  • comprar um mega servidor caríssimo, que pode receber grandes quantidades de RAM e CPU. Não é a solução ideal porque servidores caríssimos são… bem, caríssimos. Ou seja, você pode acabar gastando muito mais do que precisaria de verdade. Ou mesmo comprando um, ele pode não dar conta do recado, porque mesmo mega servidores caríssimos tem limite máximo de memória e CPU. Mas a maior desvantagem é que se você tem apenas um servidor, e ele quebra, pode começar a enviar currículos, porque você está ferrado.
  • colocar seu sistema para rodar em cluster. Em um cluster você pode rodar seu sistema em vários servidores, pode dividir a carga entre esses servidores. Se a carga aumenta acima do esperado, não é coisa do outro mundo adicionar outro servidor para aumentar a capacidade do cluster. Se um servidor cai, você tem vários outros para continuar atendendo os usuários. O maior problema dessa brincadeira é que a complexidade para montar, configurar e manter essa arquitetura é bem maior.

A discussão a seguir se aplica a clusters de aplicações Java, mas clusters em outros ambientes não são tão diferentes. A limitação da discussão a Java se deve apenas ao fato de que quero falar um pouco sobre uma solução que encontrei para este ambiente. Se conhecerem soluções boas para outros ambientes, por favor, comentem neste post.

Clusters para Java

Todos os servidores de aplicação J2EE do mercado possuem uma implementação de cluster. O grande problema é que não existe um padrão para isso, então cada um implementa de uma forma. Isso é chato para os desenvolvedores que precisam absorver muito conhecimento complicado para configurar um servidor com que não estão acostumados a lidar. É mais caro, porque a empresa precisa investir muito mais para que os arquitetos de soluções conheçam bem como funcionam as soluções de cluster de cada fornecedor com quem trabalham. Para um panorama sobre clusters de aplicações J2EE, dê uma olhada neste artigo do TheServerSide.

Além de serem complicados, eles não são nem um pouco “Plug ´n Play”. Se você não desenhou seu sistema para funcionar na arquitetura de cluster proposta pelo seu fornecedor, provavelmente terá que redesenhar muita coisa. Imagine, por exemplo, aquela sua classe Singleton, que coordena várias operações. Ela não vai mais ser Singleton com o cluster, porque você acaba com uma instância em cada servidor, o que significa que você vai ter que bolar uma forma desses objetos funcionarem da mesma forma que o seu Singleton fazia. Ter que rearquitetar partes grandes de um sistema é uma tarefa trabalhosa e bastante arriscada. Se você está nesta situação, meus pêsames.

Essas arquiteturas de cluster também acrescentam muito overhead na performance do sistema como um todo. A comunicação que eles precisam fazer entre si e com bancos de dados, só para manter o conjunto consistente, pode tornar as respostas do sistema muito mais demoradas. Você sempre vai ter algum overhead quando utiliza um cluster, mas dependendo da arquitetura, o remédio pode sair pior que a doença.

Outro ponto de overhead de performance é o banco de dados. Quando o banco de dados começa a ser utilizado para persistir os dados de um servidor de aplicação apenas para que os outros servidores tenham acesso a esses dados (que neste caso seria mais para manter a consistência do cluster), o cluster começa a abusar demais do banco de dados. O banco de dados deveria servir para armazenar os dados da aplicação, e não passar a maior parte do tempo cuidando do estado do cluster. Armazenar esse tipo de informação em um banco de dados relacional é como tentar almoçar utilizando uma metralhadora AR-15 no lugar de garfo. É uma ferramenta com muito mais recurso do que o necessário para a tarefa, o que acaba acrescentando muito mais overhead. Mas é uma das soluções preferidas, por pura falta de solução mais segura, rápida  e mais simples (ou desconhecimento de uma solução assim).

Seus problemas acabaram… acho

A melhor solução que encontrei até agora foi o Terracotta. Terracotta é um software open-source e gratuito para facilitar o trabalho de tornar uma aplicação Java mais escalável. É o tipo de coisa que eu não entendo como pode ser de graça (bom, eu entendo, eles vendem serviços em cima disso, mas preciso dizer que não entendo para que vocês tenham uma noção do quanto eu acho esse negócio FODA). Definitivamente é uma solução que eu indicaria para quem precisa de mais escalabilidade com menos custo (e alguém quer menos escalabilidade com mais custo?). E não é um projeto open-source de garagem, é uma empresa financiada, com alguns clientes na lista da Fortune 500. Entre seus clientes estão a JPMorgan, a Adobe, a BBC e a Hitachi.

Console de administração

Console de administração

O Terracotta pode ser visto mais simplesmente como uma solução para compartilhar objetos entre servidores. Cada nó de aplicação em um cluster Terracotta pode acessar todos os objetos que estão no pool de objetos do cluster. Quando um objeto é criado em um servidor, e está configurado para ser compartilhado no cluster, os outros servidores podem enxergá-lo e acessá-lo, como se estivesse na memória local. É como se cada servidor estivesse fornecendo mais memória ao cluster, uma arquitetura conceitual muito mais simples do que os quebra cabeças complicados que chamamos de cluster de aplicações.

Se você só tirava notas de C para cima no colégio, deve estar pensando “mas se todos os servidores tem acesso a todos os objetos compartilhados, essa arquitetura não é escalável nem aqui, nem na China! Para cada servidor acrescentado eu ia precisar de muito mais memória em cada nó do cluster!“. Calma, que os arquitetos do Terracotta pensaram nisso. Cada nó pode acessar todos os objetos do cluster, mas ele só carrega esses objetos na memória quando necessário (o que é milhares de vezes mais rápido do que pegar essas informações em um banco de dados). Assim cada servidor acaba utilizando apenas a memória para os objetos que realmente está utilizando.

Quem tirava C+ na maioria das matérias deve perguntar “mas qual a diferença, em termos de performance, entre essa forma de sincronização para os clusters tradicionais?“. Em comparação a arquiteturas que fazem persistência dessas informações em banco de dados, não há nem o que discutir. Como o Terracotta armazena as informações em memória, ele responde muito mais rápido do que um banco de dados pensando em fazer a query. Além disso, quando dados de um objeto são atualizados em um servidor, apenas os campos que foram alterados são propagados para o cluster. Em clusters tradicionais, objetos inteiros são trafegados, mesmo que só 1% deles tenham sido alterados.

Os alunos nota B sempre me perguntam “E isso é seguro?“. O framework reforça que os acessos aos dados compartilhados tenham algum controle para sincronizar o acesso, garantindo consistência entre os nós. Um servidor pode tentar acessar um objeto, mas enquanto outro servidor estiver alterando esse objeto, o primeiro servidor deve esperar sua vez. Além da redundância de dados que existe entre os nós do cluster, em um sistema com Terracotta você deverá ter um ou mais servidores Terracotta, que fazem a sincronização da memória dos servidores de aplicação. Além disso, o servidor Terracotta armazena em disco as informações do cluster, de forma mais eficiente quen um banco de dados, mas tão seguro quanto. É possível ainda configurar facilmente vários servidores Terracotta em um mesmo cluster, assim, se um servidor desses sai do ar, o cluster continua operando normalmente.

Tudo parece resolvido, mas os alunos nota A com certeza não vão sossegar se eu não responder “Quanto trabalho vou ter usando isso?“. Perceba que é a pergunta mais esperta de todas. Esse, acho que é o ponto mais positivo da solução. Dependendo do que você usa em sua aplicação, é possível que não precise mexer em nenhuma linha de seu código para fazê-lo funcionar em cluster com Terracotta. Basta a configuração. Nem mesmo a sincronização de acesso aos dados vai exigir uma alteração de código, pois além de reconhecer a sintaxe de sincronização de threads do Java, os acessos podem ser configurados no arquivo de configuração do Terracotta. Nem mesmo as classes precisam ser serializáveis para participar do cluster (exigência normal em outras soluções). E existem diversos pacotes de integração para frameworks existentes (como Hibernate, Struts, Spring, etc.).

Nem tudo são flores

Umas dicas para enfrentar o caminho das pedras:

  • O processo para instrumentalizar as classes para o cluster pode ser meio doloroso. O ideal é colocar como classe instrumentalizada, no arquivo de configuração, apenas as classes que devem ser utilizadas pelo cluster. Se você não conhece muito bem sua aplicação, o processo consiste em rodar sua aplicação com o Terracotta, até que ocorra um erro dizendo que você deveria colocar a classe X na sua configuração. Se você tiver um sistema muito grande, esse processo manual pode tornar inviável ficar escolhendo as classes certas (você pode mandar instrumentalizar tudo de uma vez). E é bom que você tenha bons testes funcionais do seu sistema, que exercitem todo seu código, porque você não quer descobrir que deixou de instrumentalizar alguma classe depois que o sistema estiver em produção.
  • Se não existir o pacote de integração para algum framework que seu sistema utiliza, e esse framework precise de acesso ao cluster, você pode ter um trabalho extra para fazê-lo funcionar com o Terracotta. O que exige que você tenha um conhecimento um pouco maior sobre esse framework (o que normalmente não é o caso).

Ainda fiz poucos testes com o Terracotta. Até onde vi, me agradou bastante. Não chega ainda a ser uma solução perfeita, mas está em um bom caminho. Ainda preciso fazer um teste de carga, para sentir a performance. Vou continuar acompanhando o progresso da ferramenta. E acho que para quem está precisando de uma solução para aumentar a escalabilidade, disponibilidade e performance de um sistema, vale a pena dar uma olhada no Terracotta. Quem sabe você nem precise mais usar aquelas licenças de Oracle RAC que você precisou se prostituir para conseguir.

Gostaria muito de ver soluções parecidas para .Net ou outros ambientes. Agora com a computação em nuvem na moda, algo muito desejável é simplificar um pouco a loucura que é um sistema distribuído.

Atualização: Postei uma pergunta no fórum do Terracotta e em 2 horas tive respostas de 2 engenheiros! Um deles me respondeu em menos de 30 minutos. Acho que nem com suporte técnico via telefone fui atendido tão rápido.