Caelum | Ensino e Inovação - Cursos de Java, Scrum, Ruby on Rails


O novo desafio dos servidores web

Por Fabio Kung em 30/08/06

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.

  • Share/Bookmark

Visitando uma Collection em ordem inversa

Por Paulo Silveira em

As vezes troco algumas mensagens bem técnicas com alguns colegas pelo GTalk, e aí aparecem algumas charadas interessantes.

O Orseni Campos, um desenvolvedor que eu admiro muito, passou o seguinte problema: “Paulo, como percorrer uma coleção qualquer na ordem inversa de maneira elegante?“. Quando ele disse maneira elegante, pensei que ele queria evitar criar uma lista e acessá-la via lista.get(indice) de trás para frente. Respondi o trivial: que o Collections.reverse resolveria:

public void imprimeInvertidoClassico(Collection<? extends Object>
           colecao) {
  List<Object> invertida = new ArrayList<Object>(colecao);
  Collections.reverse(invertida);
  for (Object objeto : invertida) {
    System.out.println(objeto);
  }
}

Ele não gostou, queria uma maneira de percorrer a coleção invertida sem criar uma coleção temporária, nem mexer na ordem dos objetos da coleção dada.

Desisti. Ele respondeu: “basta visitar a coleção recursivamente, realizando a ação como pós processamento!“. Perfeito. Uma técnica das linguagens funcionais e adorada pelos professores de conceitos de linguagens. Aliás, universidades importantes como MIT utilizam linguagens funcionais no curso introdutório de computação, acreditam que é mais fácil do aluno assimilar os conceitos dos algoritmos (algumas dessas linguagens são dos mesmos criadores do Java).

Então traduzi a idéia para código:

public void imprimeInvertido(Collection<? extends Object> colecao) {
  imprimeInvertido(colecao.iterator());
}

private void imprimeInvertido(Iterator<? extends Object> iterator) {
  if (iterator.hasNext()) {
    Object object = iterator.next();
    imprimeInvertido(iterator);
    System.out.println(object);
  }
}

Aqui, o método imprimeInvertido rapidamente delega a responsabilidade para o que recebe um Iterator como argumento. Este faz uma invocação recursiva no caso de existir um próximo elemento a ser visitado. O importante aqui é que a referência trazida pelo iterator.next() só é processado depois que seus elementos vizinhos forem, realizando o processo na ordem contrária da determinada pelo Iterator. (repare que considerando que a ordem definida pelo Iterator define uma lista, e que toda lista é uma árvore, estamos percorrendo essa ávore de maneira pós-fixa).

Ok, funciona e não cria coleção temporária! Mas se você parar para analisar bem, cada invocação recursiva empilha mais um stackframe, e em cada um temos uma variável temporária (a Object object declarado dentro do if) para ser recuperada quando a invocação retornar. Pensando na última invocação (base da recursão, quando o iterator chegar ao final e não entrar no if (iterator.hasNext())) teremos uma pilha com colecao.size() stackframes, alocando uma memória proporcional ao tamanho da coleção, muito semelhante a ter alocado a coleção temporária para o Collections.reverse.

Você ainda corre o risco de um StackOverflowError por estar gastando memória da pilha em vez do heap! Outra desvantagem é a legibilidade: nós programadores java não estamos acostumados a utilizar recursão para resolver problemas que poderiam ser facilmente resolvidos de maneira iterativa.

O exercício vale para relembrar que é importante manter contato com outras linguagens, para não ficar viciado no mesmo paradigma, o que te limita na hora de encontrar soluções. Em alguns casos a solução recursiva pode ser muito mais trivial de encontrar, desde que você esteja com o paradigma afiado!

Alguma outra maneira interessante de percorrer uma coleção na ordem inversa?

  • Share/Bookmark

Ei! Como é o seu DAO? Ele é tão abstraído quanto o meu?

Por Paulo Silveira em 26/08/06

Essa é uma pergunta comum entre os desenvolvedores. Alguns acham que há uma fórmula única, que DAO é um pattern fechado e que possui seu diagrama de classes bem definido. Eu discordo.

Na minha humilde opinião, DAO é uma maneira de você encapsular o seu acesso a dados. Não importa se é através de uma factory de objetos que recebem uma Session do hibernate por injeção de dependências, ou se é um montão de métodos estáticos cheio de SQL dentro deles (argh!). Obviamente a última opção envolve diversos outros antipatterns (como singleton e métodos estáticos, que já criticamos anteriormente), porém ela consegue isolar seu acesso a dados, cumprindo o papel do DAO e de alguma maneira organizando sua camada.

Lendo a definição de Data Access Object no site da Sun, podemos chegar a um molde comum, mas nada impede que possamos incrementa-lo e modifica-lo de acordo com nossas necessidades. Por exemplo, com o Java 5, podemos tirar proveito dos generics. Na Caelum costumamos usar uma interface para os nossos DAOs parecida com a seguinte:

interface Dao<T> {
  T search(Serializable id);
  void persist(T t);
  void remove(T t);
  // ...
}

A nossa implementação de DAO genérico para hibernate fica parecida com:


class GenericHibernateDao<T> implements Dao<T> {
  private Session session;
  private Class persistentClass;

  public GenericHibernateDao(Session session, Class persistentClass) {
    this.session = session;
    this.persistentClass = persistentClass;
  }

  public T search(Serializable id) {
     return session.load(persistentClass, id);
  }

  // outros metodos da interface Dao
}

Repare que ele recebe session como argumento, que poderia ser injetada por um container. Outra opção seria buscar a session de um singleton (o clássico HibernateUtil), mas a primeira maneira é bem mais elegante e testável. Repare que ainda temos os problemas de controle de transações: onde abrimos e comitamos? dentro do Dao? de dentro do injetor de dependências? de dentro da sua lógica de negócios? Nós costumamos fazer esse controle em um interceptador ou mesmo em um Filter de servlets, dependendo do framework MVC usado. Nosso filtro é semelhante com o recomendado pela documentação do hibernate.

E então podemos utilizar nosso DAO:

Dao<Cliente> clienteDao = new GenericHibernateDao<Cliente>(session);
clienteDao.persist(cliente);
Cliente c5 = clienteDao.search(5);

Para as entidades que você precisa de mais que um simples cria/le/atualiza/deleta, criamos uma nova interface:

interface ClienteHibernateDao extends Dao<Cliente> {
   List<Cliente> buscaTodosClientesQueDevemDinheiro();
}

E teríamos uma classe ClienteHibernateDao que estende GenericHibernateDao<Cliente> e implementa ClienteDao.

Meus colegas da easytech preferem abstrair também o tipo da chave primária:

interface Dao<T, I> {
  T search(I id);
  // ...
}

Dessa maneira você realmente ganha mais tipagem, já que no DAO anterior poderíamos passar qualquer objeto serializável como chave. Podemos refinar muito mais nossas classes de acesso a dados, mas até que ponto chegar?

Ficar abstraindo demais a sua camada de persistência pode levar a proliferação de centenas de pequenas classes que simplesmente não fazem nada, só delegam e delegam. Claro que existem casos em que abstrair para apenas delegar é interessante, diminuindo o acoplamento entre suas classes, porém isso pode chegar a um ponto que vai atrapalhar o desenvolvimento.

Nesta entrada do blog dos desenvolvedores do hibernate, o uso de camadas para abstrair as ferramentas de mapeamento objeto relacional são duramente criticadas.

E você, qual é a receita de bolo para o seu DAO? Até onde você o refina?

  • Share/Bookmark

Trabalhando com coleções usando JSP e JSTL

Por Guilherme de Almeida Moreira em 22/08/06

Sou novo na Caelum e estou em um projeto usando JSP 2 e JSTL como view. Tenho um problema extremamente simples, e que deve ser comum a todos: como descobrir o size() de uma Collection. E como posso chamar o contains() dela, ou outro método qualquer, sem ser um getter?

O Paulo Silveira disse que é aí que ele sente saudades do velocity, que lá você poderia escrever algo como $colecao.contains(x) e $colecao.size que tudo funcionava. Usando a expression language em um JSP isso não funciona, pois você só pode invocar getters. $colecao.size iria chamar colecao.getSize(), que obviamente não funciona. No Velocity e outros engines de template (como o freemarker), ele vai tentar arrancar o size de várias maneiras, inclusive fazendo .size() por reflection.

Ele ainda disse que muita gente que fez a especificação da expression language eram desenvolvedores do velocity, mas que esse tipo de recurso eles não colocaram no JSP pois você estaria colocando regras de negócios dentro da sua camada View, quebrando o MVC.

O Paulo costuma criar uma taglib com funçõezinhas estáticas para chamar do jsp, criando um .tld dentro do seu WEB-INF, por exemplo:

<?xml version="1.0" encoding="UTF-8"?>
<taglib version="2.0">
  <tlib-version>1.1</tlib-version>
  <uri>http://www.caelum.com.br/taglib</uri>
  <function>
    <name>contains</name>
    <function-class>br.com.caelum.util.TagLibrary</function-class>
    <function-signature>
      boolean contains(java.util.Collection , br.com.caelum.nomedoprojeto.Option)
    </function-signature>
  </function>
</taglib>

Aí, você cria uma classe cheia de métodos estáticos (realmente não é elegante!):

public class TagLibrary {
  public static boolean contains(Collection options, Option option) {
    return options == null false : options.contains(option);
  }
}

E acaba usando na página:

<%@ taglib uri="http://www.caelum.com.br/taglib" prefix="m" %>

<c:if test="${m:contains(listOfSelected, option)}">checked</c:if>

Já o Sérgio Lopes disse que você poderia colocar esse linha dentro de um scriptlet, desde que ela esteja dentro de um .tag. Depois o Paulo e o Sérgio começaram a brigardiscutir sobre as duas opções, e no fundo me parece que nenhum deles gosta muito de nenhuma das duas.

Uma outra saída seria ter alguns métodos que fazem esse trabalho nos seus value objects, como por exemplo ter getTamanhoDaColecao() . Mas isso nem sempre é possível e começa a gerar uma quantidade de métodos enorme que são totalmente redundantes.

Então eu pergunto, o que fazer para resolver esse tipo de problema com JSP e JSTL?

  • Share/Bookmark

Dijkstra, Orkut e Cursinho

Por Paulo Silveira em 21/08/06

O cursinho da poli costuma ministrar palestras sobre as diversas carreiras para os seus alunos, e este último domingo foi a 7a Jornada de Trajetórias Profissionais.

O professor Carlinhos do deparatamento de Ciência da Computação da USP não poderia comparecer, então ele me obrigou indicou a participar em seu lugar. Lá fui eu, tentar recrutar nossos futuros colegas de pesquisa e de trabalho. Tentei bolar algo que fosse interessante aos jovens em idade de cursinho.

Comecei a apresentação com uma citação que eu acho perfeita para quebrar a ilusão de que nosso ramo é dominar o computador (ilusão a qual nossos parentes e amigos insistem em mater pedindo nossa ajuda na instalação de uma webcam ou scanner):

Ciência da Computação tem tanto a ver com o computador como a Astronomia com o telescópio, a Biologia com o microscópio, ou a Química com os tubos de ensaio. A Ciência não estuda ferramentas, mas o que fazemos e o que descobrimos com elas.

É de Edsger Dijkstra. E foi através de seu famoso algoritmo para resolver o problema do caminho mínimo que eu comecei a apresentar o que estudamos no curso. Introduzi o problema de encontrar a menor rota entre duas cidades dado o mapa do Brasil e a distância entre cada uma delas. Tentei fazer com que eles percebessem que uma solução qualquer, testando todos os caminhos, chegaria a n! combinações, um número astronômico (e.g., 15 fatorial = 1307674368000). Também mostrei que a solução gulosa, sempre procurando pelo vizinho mais próximo, da solução errada. Por último apenas disse que poderíamos fazer algo muuuuito rápido e rodei uma simulação, sem dizer nada sobre nlogn.

Mas a platéia reagiu mesmo quando fiz a comparação com o Orkut: “sabe quando você entra no perfil de uma pessoa do Orkut, e o site te mostra o caminho entre você e a outra pessoa? Então, o Orkut Büyükkokten, criador do site, estudou computação e aplicou esses conhecimentos para poder fornecer esse tipo de informação”, todos balançaram a cabeça, de maneira afirmativa. Ok, confesso que menti um pouco. Na verdade o Orkut não precisa de caminho mínimo pois as arestas não tem peso, basta uma busca em largura. Além disso o Orkut não vai disparar uma busca em largura em um grafo com 10 milhões de nós a cada vez que você acessar um perfil diferente, ele na verdade mantém (penso eu) uma estrutura de dados para realizar operações em grafos dinâmicos.

Com isso espero ter angariado um ou outro novo cientista da computação. E quem sabe mais programadores Java!

  • Share/Bookmark



Caelum | Ensino e Inovação
São Paulo: Rua Vergueiro, 3185, cj. 87, próximo ao Metrô Vila Mariana   |   Tel. (11) 5571-2751
Rio de Janeiro: Rua Senador Dantas, 80, cj. 307/308 - Centro   |   Tel. (21) 2220-4156 ou 2297-0033
Brasília: SCS Qd. 8 Bl. B-50, Sala 521 - Ed. Venâncio 2000   |   Tel. (61) 3039-4222