Zero-Downtime Deploy com Tomcat

247O termo “downtime”é usado na engenharia de software para referir-se a períodos em que um sistema não está disponível. O tempo de inatividade ou duração da interrupção refere-se a um período de tempo que um sistema não fornece ou executa sua função principal. A indisponibilidade é a proporção de um período de tempo que um sistema não está disponível ou off-line. Isso geralmente é um resultado do sistema deixando de funcionar por causa de um evento não planejado, ou por causa da manutenção de rotina (um evento planejado). O termo é geralmente aplicado a redes e servidores. Os motivos mais comuns para interrupções não planejadas são falhas do sistema (como um acidente) ou falhas de comunicações (vulgarmente conhecido como falha de rede). Foi então que se criou-se o tempo “Zerodowntime deploy” que é ato de se atualizar um nova versão de um sistema sem interromper o serviço.

Como usar no Tomcat?

Desde a versão do tomcat7x existe esse recurso disponível para se atualizar um novo war, sem parar e atrapalhar as pessoas que estão naquele exato momento penduras em sessões HTTP. O tomcat gerencia as versões através do próprio nome do arquivo usando a seguinte sintaxe: “##001”. Muito fácil, ao invés de fazer o deploy um “sistema.war”, faça como “sistema##001.war”. O contexto de acesso na URL vai continuar o mesmo, ou seja, tudo que estiver depois do ## não sera considerado como parte da nomeação, mas internamente o tomcat estará gerenciando as versões.

É ai que vem a mágica, quando chegar a hora de atualizar uma nova versão da solução, faça o deploy usando a versão 2: “sistema##002.war” e assim sucessivamente. O tomcat vai subir a versão 2 da solução simultaneamente a versão 1. Todos os clientes que partir desse momento, acessar a solução, sera automaticamente redirecionado para a nova versão, e todos os clientes que estavam pendurados na versão 1 vão continuar usando como se nada tivesse acontecido. A medida que esses clientes pendurados na versão 1 tem sua sessão HTTP encerradas, eles irão automaticamente sendo redirecionados para a versão 2. Seguindo este modelo, é só uma questão de tempo para a versão 1 ir perdendo sessões ativas, e a versão 2 ir assumindo toda a demanda. Depois que esse tempo passar e a versão 1 estiver sem nenhuma sessão, você já pode parar e remover a versão 1, deletando o “sistema##001.war” diretamente da pasta ou parando o contexto via manager mesmo. Veja alguns artigos sobre:

Como o tomcat controla isso?

Quando é feito o deploy de uma solução gerenciado versões com #00x, o tomcat vai acrescentar o novo  header HTTP em todas as requisições chamado de VERSION que indicará qual é a versão da sessão pendurada. Podemos ter quantas versões simultâneas de war for necessário, desde que os wars estejam devidamente versionadas e o tomcat tenha recursos para rodar todas.

Dicas de Otimização

Já faz um tempo que tenho usado esse recurso em ambiente de produção com sucesso e assim desenvolvi algumas manhas bem interessantes para melhorar e otimizar todo o processo. Veja a seguir:

1. Global DataSource

Não use DataSource local, pois a cada deploy de uma nova versão de war, sera criado um novo DataSource a toa, gastando memoria desnecessariamente. Para contornar isso, use DataSource globais que são criados e carregados apenas 1 vez, reusando dentro de todas as versões que venham a ser simultaneamente executados.

2. Global Classpath

Não use jars de terceiros na lib do projeto, pois a cada deploy de uma nova versão de war, todos os jars serão recarregados a toa, gastando memoria desnecessariamente. Para contornar isso, coloque todos os jars que a solução na lib global do tomcat no qual sera carregado apenas 1 vez e reutilizado dentro de todas as versões que venham a ser simultaneamente executados.

3. Semáforo nos Jobs

Caso a solução tenha jobs java e venha a ter versões sendo executados simultaneamente, isso resultara em processamento duplicado. Para contornar isso, é necessário implementar um algoritmo de semáforo responsável por impedir processamento de jobs simultâneas. Existem 3 opções:

1. Look SGBD: use bloqueio pessimistas de banco de dados.

2. Look de Arquivo: use bloqueio de arquivos.

3. Look de Objeto: use bloqueio de objetos JNDI.

Depois de usar todos, a melhor opção para mim foi a 2, usando NIO o código funciona super rápido e é portável para qualquer servidor. A opção 1 é legal, mas tem o round-trip no banco que apresenta um tempo maior de processamento, a opção 3 é muito boa, mas você acaba tendo que registrar os beans no JNDI que tem uma implementação diferente para cada servidor.

Confiabilidade, disponibilidade, recuperação e indisponibilidade são conceitos relacionados aos eventos “não planejados” que eu deixarei de fora, uma vez que o foco do post é a atualização da solução, sendo um “evento planejado”.

Conclusão

Mesmo se você precisar alterar seu produto para se adaptar a essa operação, eu tenho certeza que valerá a pena. Experiência própria! Imagine você dizendo ao seus clientes e ou seu gestor que o serviço da solução não sera mais interrompido por atualização de novas versões? Bem conversado, fazendo um bom marketing, é motivo de um bom aumento de salário da equipe.

“Assim diz o SENHOR, o teu Redentor, o Santo de Israel: Eu sou o SENHOR, o teu Deus, que te ensina o que é útil e te guia pelo caminho em que deves andar.” Isaías 48:17