O novo desafio dos servidores web

A evolução dos servidores web é um assunto bem interessante. Toda essa evolução até hoje aconteceu para atingir um aumento na vazão (throughput). Ou seja, conseguir atender mais requisições por segundo.

Algumas das primeiras idéias para geração de conteúdo dinâmico vieram com o CGI (Common Gateway Interface), que define a interface de comunicação entre o servidor web e o processo que gera o conteúdo dinâmico (escrito em PHP, ASP, perl ou C/C++, por exemplo). O grande problema do CGI é justamente a necessidade de criação de um novo processo a cada nova conexão; o que não é nada leve.

Foi aí então que vieram as Servlets. A grande diferença é que agora não é criado um novo processo a cada conexão, e sim uma nova thread. Este conceito é conhecido como thread per connection pattern e resolveu bem os problemas, trazendo um aumento significativo na vazão dos servidores web que podem agora atender um número bem maior de conexões/requisições.

Existe hoje um novo desafio, que veio com o uso intensivo de requisições assíncronas nos sistemas web (ou se preferir, AJAX). Isto fez o volume de requisições aumentar muito, fazendo com que muitas conexões sejam abertas e existam muitas threads executando no servidor. O novo problema agora é o grande número de threads, que limitam a vazão do servidor. Iniciar uma nova thread é muito mais leve do que iniciar um novo processo, mas mesmo assim, o servidor gasta recursos para manter uma thread executando.

Uma solução seria manter as conexões http (cada uma associada a uma thread no servidor) abertas por mais tempo. Assim o cliente pode aproveitar sempre a mesma conexão para enviar diversas requisições assíncronas e o servidor usa sempre a thread associada a esta conexão para tratar as requisições. Isto pouparia o servidor de abrir e fechar muitas threads e possivelmente diminuiria o número de threads em execução, mas traz um outro problema: existem diversas threads no servidor que ficam a maior parte do tempo ociosas, enquanto não há requisições na conexão associada, desperdiçando recursos.

E então os servidores web começam a evoluir denovo. O uso do java.nio (New IO) aqui é a chave para o sucesso. Através da IO não bloqueante que esta API fornece, os novos servidores conseguem reaproveitar threads ociosas. Não é mais necessário associar cada thread a uma conexão: a diferença é que a thread só é associada a uma conexão, quando esta tem uma requisição pendente (a ser tratada). É assim que surgiu o novo conceito de thread per request pattern. Cada thread serve para atender uma requisição e quando termina, volta a estar disponível para atender outras requisições. Não importa se a conexão foi fechada ou não.

Um exemplo interessante de implementação (acompanhado de um comparativo de performance) pode ser visto neste artigo no developerWorks da IBM.

Já existem alguns servidores que evoluíram, como por exemplo o Jetty, os servidores de aplicação Glassfish e o BEA WebLogic. O pessoal do Tomcat também está preparando um connector baseado em java.nio para o Tomcat 6.0.

Já tem até bastante gente querendo que a especificação de Servlets evolua para se beneficar de java.nio! É um assunto polêmico. Não são todas as aplicações que precisam de uma vazão tão grande que justifique a técnica apresentada.

ps.: Sim, o antigo problema do CGI hoje é super atenuado com o uso de FastCGI.

Tags:

5 Comentários

  1. Luiz Claudio 31/08/2006 at 12:59 #

    Vc diz…
    Foi aí então que vieram as Servlets. A grande diferença é que agora não é criado um novo processo a cada conexão, e sim uma nova thread.
    Não entendo, uma thread não é um processo?

  2. Fabio Kung 31/08/2006 at 14:33 #

    Dê uma olhada aqui.

    Threads são como se fossem versões mais leves dos processos, o termo é comumente traduzido como linha de execução.

    Um processo pode ser dividido em (possuir) diversas Threads. O overhead trazido pela criação e existência de uma Thread é significativamente menor do que o overhead trazido por um processo.

    Outra grande diferença entre Threads e processos é que as Threads compartilham o espaço de memória (threads pertencentes a um mesmo processo). Os processos têm espaços de memória independentes e quase sempre não têm acesso ao espaço de outros processos.

  3. Tiago Silveira 01/09/2006 at 11:08 #

    Durante algum tempo algumas JVMs criavam um processo para cada java.lang.Thread também. Os SOs também evoluíram de lá pra cá… 🙂

    A especificação de Servlets usando java.nio não tem muito a ver com threads. Quem tem que fazer a mágica é o Servlet Container. Afinal, Servlets são multithreaded por default há tempos:

    http://java.sun.com/products/servlet/2.1/api/javax.servlet.SingleThreadModel.html

    Pra mim, o mais interessante do post do Greg é justamente a transformação do Servlet numa entidade que coopera com o ambiente: em vez de dar wait(), ele sinaliza uma pausa; em vez de um acesso garantido à sessão e outros recursos, ele faz polling.

  4. Guilherme Silveira 01/09/2006 at 11:17 #

    Além disso, existe o pool de threads, que no cgi mais inocente poderia enfileirar logo a segunda pessoa (ASP!?!?!) e nas linguagens mais recentes só enfileira depois de N requisições…

  5. Fabio Kung 02/09/2006 at 08:39 #

    A especificação de Servlets usando java.nio não tem muito a ver com threads. Quem tem que fazer a mágica é o Servlet Container.

    Isso! É baseada no fato da entrada e saída não ser bloqueante!

    O problema do pool é o número limitado de threads que ele pode guardar. É isso que limita a vazão dos servidores hoje.

Deixe uma resposta