Melhorando o GUJ: Jetty, NIO e load balancing

Por Fabio Kung em 27/06/08

GUJ2

Durante boa parte da vida do GUJ.com.br, na sua segunda versão (screeshot acima), o site sofreu diversas quedas e passou por muitos períodos de lentidão, mesmo depois de ter migrado para um servidor dedicado. A grande verdade é que por um bom tempo ficamos devendo a devida atenção ao deployment do GUJ. Sempre que um problema acontecia alguém simplesmente reiniciava o servidor, sem investigar as causas reais do problema com profundidade.

Dada a relação próxima que a Caelum sempre teve com o site, já que dois dos fundadores do GUJ são também os fundadores da Caelum, resolvemos assumir de vez a posição de criadores do GUJ. A Caelum agora é a patrocinadora e mantenedora oficial do GUJ. Recentemente andamos gastando algum tempo, tentando acabar de vez com estes problemas que o GUJ a tanto tempo sofre. Felizmente, a melhora já é bem perceptível!

Há algum tempo atrás, o GUJ ficava esporadicamente muito lento. Para resolver estes problemas de lentidão, o primeiro passo foi conseguir um servidor dedicado, pago pelos anúncios espalhados pelo site. Já faz tempo que o GUJ roda neste servidor dedicado e desde então a performance se tornou quase sempre aceitável.

Porém, o servidor dedicado não resolveu todos os problemas, já que java.lang.OutOfMemoryError sempre foi o principal problema enfrentado pelo GUJ. Sempre desconfiamos que o culpado poderia ser o código do próprio GUJ, ou até do JFórum, que deveriam conter algum vazamento de memória.

Alguns desenvolvedores da Caelum já tiveram ótimas experiências passadas com o Jetty, que é um excelente servidor web e servlet contêiner. Foi, inclusive, um dos servidores Java pioneiros a usar conectores NIO (java.nio). O Jetty foi desenhado para ser embutido em outras aplicações Java e portanto é extremamente leve. Consome bem menos memória que o Tomcat, seu concorrente mais conhecido, e não deixa nada a desejar nas outras características.

O servidor dedicado do GUJ tem 2GB de memória RAM e o Tomcat estava configurado inicialmente para ter um heap máximo de 768MB (-Xmx768M). A primeira tentativa foi aumentar o heap máximo (-Xmx1024M), mas mesmo assim o temível OutOfMemoryError insistia em aparecer.

Resolvemos então dar uma chance ao Jetty. Já que ele consome menos memória, acreditamos que os OutOfMemoryError demorariam mais a aparecer. Logo ao subir, o jetty ocupa 4% da memória do servidor. O impressionante é que em duas semanas no ar, o uso total de memória não passou de 12%. Na verdade, o uso de memória estabilizou em 9% do total, porém recentemente fizemos alguns testes de carga no servidor do guj, com mais do que o dobro do número de conexões que o guj recebe hoje nos períodos de pico. Isto fez com que o uso de memória do Jetty pulasse para 12%. Uma diferença enorme da quantidade usada pelo Tomcat, que chegava facilmente a 80%.

uso de memória do Jetty

Já temos algum tempo rodando, sem problemas de memória e com o jetty estável usando 9-12% do total da memória do servidor. Já estamos até pensando em descartar a possibilidade de existir um vazamento de memória no código do GUJ ou do JForum. Isto tornaria o Tomcat culpado pelos problemas de memória!

A possibilidade de vazamentos de memória no Tomcat (estávamos com a versão 6.0.14, sem o APR e com Linux Kernel 2.6.22) deste tamanho é um pouco assustadora. Com a base de usuários que o Tomcat tem, muito possivelmente alguém já teria pego este problema muito antes de nós. Provavelmente o problema é de alguma configuração mal feita no Tomcat do GUJ.

Fato é que o Jetty foi uma tentativa que deu certo e o problema está aparentemente resolvido. O Jetty mostrou um uso mais alto de CPU que o Tomcat, chegando a picos de 60% da capacidade total de processamento do servidor, que possui 2 processadores. Através dos testes de carga que fizemos, temos percebido que o Jetty usa bastante CPU para responder diversas requisições simultâneas. Isso não chega a ser um problema, já que os dois processadores que o servidor possui são mais do que suficientes para atender a quantidade de requisições por segundo que o GUJ recebe hoje.

Apesar de termos algumas suspeitas, ainda não investigamos a razão do alto uso de CPU. Este é um ponto a ser abordado, caso o GUJ tenha problemas com disponibilidade de processamento algum dia.

Mesmo não representando um problema hoje, esta situação preocupa já que o MySQL rodando na mesma máquina também costuma usar bastante CPU. Isto pode se tornar um problema em algum dia que tenha um pouco mais de requisições por segundo do que o comum, já que o uso de CPU chegava as vezes perto do limite. Felizmente, grande parte das requisições ao servidor do GUJ são para conteúdo estático: imagens, JavaScripts, arquivos CSS, download de pdfs (dos artigos), entre outros. Todo esse conteúdo estático era servido pelo Jetty, contribuindo para o alto uso de CPU. Resolvemos então tentar um proxy reverso na frente do Jetty, especificamente para servir este conteúdo estático.

proxy reverso servindo conteúdo estático

Existem diversas alternativas de proxy reverso e a primeira a ser considerada quase sempre é o conhecido servidor web Apache Httpd, com a adição do mod_proxy. É uma excelente solução e existe bastante documentação para fazer tudo funcionar. No entanto, faz um tempo que eu já estava querendo testar o servidor russo Nginx, tão falado pelo pessoal da Engine Yard.

A desvantagem do Nginx para o Apache Httpd é a documentação não tão extensa. Esse é um grande problema para a comunidade do Nginx, que tem se esforçado em traduzir grande parte do que está escrito em russo. Apesar disso, o Nginx é impressionante e superou todas as nossas expectativas. Além de ser extremamente rápido, consome pouquíssimos recursos. Cada um dos processos (1 master + 5 workers) consome na maior parte do tempo apenas 1% de CPU e 0.2% da memória disponível do nosso servidor. Incrível!

O conteúdo estático do GUJ agora é todo servido pelo Nginx; as requisições nem chegam ao Jetty. Além disso, o Nginx, como um bom proxy reverso, oferece diversas otimizações. Uma essencial para o GUJ é o preenchimento automático dos cabeçalhos HTTP de cache, sugerindo aos browsers que façam cache do conteúdo estático. Agora o consumo de CPU diminuiu consideravalmente.

uso de recursos do nginx no GUJ

Não fizemos nenhum comparativo científico de performance, mas a melhora dos tempos de resposta do GUJ está visível. Frequentemente tenho a sensação de que o site está “voando”.

Configuramos também no Nginx a exibição de algumas estatísticas de acesso; tão valiosas ao GUJ. É assustador como o padrão de acesso se repete a cada dia, e a cada semana. Repare como o gráfico de requisições por segundo é praticamente idêntico para cada dia (com exceção do sábado e domingo, que são parecidos entre si).

requisições por segundo no GUJ, em uma semana

Temos também o interesante gráfico de conexões por segundo, que mostra relação média aproximada de 3 requisições por conexão.

conexões por segundo no GUJ, em uma semana

Note que a legenda do gráfico está errada, já que deveria mostrar “connections/sec”. O pequeno pico que dá para ver no gráfico aparece por causa de alguns testes de carga que fizemos neste dia. Pode ser desconsiderado.

Fizemos ainda algumas experiências com múltiplos servidores Jetty por trás do Nginx, funcionando também como balanceador de carga. Espero em breve postar sobre nossa experiência em geral com balanceamento de carga e alguns outros truques que pudemos testar nesta experiência do GUJ e em alguns clientes.

Aproveitando o post, o GUJ recentemente comemorou a marca de meio milhão de mensagens. É um orgulho poder fazer parte desta comunidade, e mais ainda por poder torná-la cada vez melhor.

Os 7 hábitos dos desenvolvedores de WebServices altamente eficazes

Por Paulo Silveira em 18/02/08

Esta semana tive o prazer de palestrar no Café Com Tapioca, evento do CeJUG realizado em Fortaleza. Estavam presentes dois conhecidos evangelistas da Sun: Reggie Hutcherson e Simon Ritter.


DSC01766 DSC01773
DSC01770 DSC01763

O agradecimento fica ao Rafael Carneiro, do PortalJava e do CeJUG, pela organização geral do evento. Agradeço também à equipe do grupo Fortes pela minha vinda ao Ceará: ao Clavius Tales, Igo Coelho, Antônio Israel, Ronaldo Moreira, Tiago Moraes, Rodrigo Maia e tantos outros. Vale reparar como a comunidade java cearense é ativa nos blogs! Nesse sábado também ministrei umworkshop sobre Arquitetura e Design Java aqui em Fortaleza.

Durante esses dias aqui, muitas pessoas perguntaram bastante sobre webservices: em especial JSON e Rest. Compilei alguns dos pontos que foram recorrentes nas discussões e considerados como boas práticas:

Cuidado com a granularidade - a granularidade do seu serviço não pode ser muito fina, caso contrário seus seviços sofrerão dos mesmos problemas que o EntityBean do EJB2 exposto remotamente: uma enorme quantidade de requisições serão disparadas para executar pequenas tarefas, como getters! Seus serviços devem realizar uma quantidade significativa de tarefas, para evitar um número alto de roundtrips!

Exponha serviços, não dados - é comum ouvir a frase “Vou criar webservices para expor os meus dados“. O grande perigo aqui é deixar toda a lógica de negócio na mão do cliente, o que descentralizará seu serviço e forçará o cliente a realizar muitas requisições ao servidor. Devemos expor serviços, e não dados em sua forma mais crua.

Nunca parsear WSDL/SOAP manualmente - antes das ferramentas que trabalham com webservices terem atingido sua maturidade, era muito comum ver por aí as pessoas gerando o SOAP manualmente, através de bibliotecas XML ou às vezes até mesmo concatenando String com o uso de StringBuffer! O SOAP é o protocolo de comunicação, e assim como quando você usa RMI/CORBA e você não enxerga absolutamente nada do JRMP/IIOP, você nunca deveria ter contato direto com o protocolo de comunicação! O SOAP e o WSDL devem ser utilizados por ferramentas, e não pela sua própria aplicação. Hoje em dia qualquer ferramenta como o Apache Axis, Apache CXF, ou mesmo o wsimport, que já vem no JDK 6, auxilia nossa tarefa de gerar Stubs que sabem trabalhar com o SOAP, sem que você nem mesmo precise ve-lo um dia. Se você está usando um servidor de aplicação, esta tarefa é ainda mais fácil, até mesmo para fazer o deploy do serviço.

Não enviar XML dentro de XML - outra prática comumente encontrada em aplicações antigas que usam Webservices: dentro do SOAP é enviado um outro XML como um dos parâmetros, então mesmo usando ferramentas para gerar os stubs você fica com uma String que dentro dela há um XML e este precisa ser parseado por sua própria aplicação. Isto é uma má interpretação do conceito que “Webservices é comunicação via XML“. Concordo que em alguns raros casos isso possa fazer sentido, como por exemplo se o valor do que você quer realmente é um outro XML, mas no geral isto é feito sem necessidade nenhuma, como é o famoso caso do WebService MS Office dos Correios do Brasil.

Considerar protocolo binário - são muitas as reclamações de que o SOAP acaba sendo um XML grande e pesado para ser transportado. Hoje em dia há muitas formas de contornar isso, como o uso do padrão Fast InfoSet para compressão do XML. Uma outra forma seria o uso de protocolos proprietários, como o AMF da Adobe, que é uma opção comum no uso do Flex.

Considerar não usar WSDL/SOAP - pelas diversas críticas a burocracia exagerada do WSDL/SOAP, muitas pessoas estão optando por usar algo mais simples, como o bom e velho XML (POX), JSON ou até mesmo uma forma qualquer de estruturar dados. Caso você precise de simplicidade e velocidade no desenvolvimento com outras plataformas, essa é uma boa opção. Cuidado: muita gente está considerando qualquer webservice que não use WSDL/SOAP como sendo REST. Os webservices do flickr e da Amazon são um bom exemplo: tudo é via GET (as vezes POST) e não há recurso identificado pela URI. Na verdade ele usa apenas um esquema HTTP+XML (POX), considerado apenas em parte como RESTful. Você pode ver que esses webservices são muito diferentes do modelo RESTful do Atom Publishing Protocol. Criar um protocolo realmente REST como este não é fácil: porém teremos esse trabalho bastante simplificado com a JAX-RS (JSR 311), e já podemos ver isso através do Glassfish Jersey.

Considerar usar JSON - JSON é um formato que tem ganho muita popularidade. Não é a toa: além de ser um simples para debugar, parsear e gerar, com javascript basta fazer um eval em um resultado JSON que ele já estará pronto para usar. É uma excelente opção para o consumo via AJAX e criação de mashups. JSON tem ganho bastante força na comunidade Flex, tornando-o uma ótima opção como ponte entre Flex e Java. A escolha por JSON abre portas para muitos tipos diferentes de clientes, e em especial o browser, que é nossa plataforma global.

Java 6, as APIs de XML, Webservices e classloaders

Por Paulo Silveira em 17/12/07

A Sun vem há muito tempo fazendo esforços para facilitar a manipulação de XML e de webservices na plataforma. São tantos projetos, subprojetos e especificações que podemos facilmente nos encontrar perdidos no meio de tantas siglas. Elas são:

JAXP (pacote java.xml no geral) - Processamento geral de XML, com os já antigos SAX e DOM, além de transformadores (XLST) e XPath.

JAXB (pacote javax.xml.bind) - Assocaição/mapeamento de classes java para XML.

JAX-WS (pacote javax.xml.ws) - Criação e consumo de webservices. Aliada a especificação de metadados para webservices (pacote javax.jws), previamente já vista aqui no blog, a JAX-WS tornasse poderosa e fácil de usar.

JAXR (pacote javax.xml.registry) - para acesso aos registros de serviços XMLs, como UDDI.

JAX-RPC (pacote javax.xml.rpc) - era o nome antigo do atual JAX-WS. O JAX-WS mudou de nome e já apareceu como 2.0, essa mundaça foi justificada pelo fato dessa API passar a trabalhar bem mais próxima da API do JAXB, além do óbvio marketing.

E a Sun não para por aí, temos mais especificações: a duvidosa JSR 267 que possibilita um JSP acessar diretamente um webservice (!) através de taglibs e a esperada JSR 311 para trabalhar com serviços de maneira RESTful, oferecendo simples anotações para expor métodos Java através de URI + métodos HTTP.

Não são todos subprojetos de manipulação de xml com java que viraram especificações, e as que viraram nem todas estão no Java SE. A Fast Infoset é uma especificação ISO para representação binária do padrão XML (economizando assim espaço e banda, além de melhorar performance do parsing) possui uma implementação Java, utilizada dentro do Metro, projeto que fornece os recursos de webservices do Glassfish.

Muitas dessas APIs, juntamente com implementações de referência (RIs), agora estão presentes no java SE 6.0, que antes eram opcionais. Qual é o problema disso? Até então diversos servidores de aplicação e frameworks traziam embutido implementações do JAXB, JAX-WS, etc. Ao rodar essas aplicações com o Java 6 o sistema de classloading da plataforma vai primeiro carregar as classes da api padrão, mesmo que você tenha implementações dessas APIS de XML no classpath. O ruim aqui é que muitos servidores de aplicação acabam se amarrando a detalhes de sua própria implementação e versão, como é esse caso do JBoss com o JAX-WS. Quando rodado com o Java 6, temos a seguinte exception quando você tenta acessar um webservice que está implantando no servidor:

java.lang.UnsupportedOperationException: setProperty must be overridden by all subclasses of SOAPMessage
at javax.xml.soap.SOAPMessage.setProperty(SOAPMessage.java:424)

Enfrentamos esse problema recentemente, e para resolve-lo usamos o sistema de endosso de jars (endorsed jars) do Java: determinados diretórios podem ser configurados para que alguns pacotes específicos possam ser carregados destes antes do Classloader tentar chegar ao rt.jar.

Um outro problema comum é com o JAXB: o Java 6 vem com a versão 2.0, se você precisar usar a 1.1 ou a 2.1, vai ter problemas. O interessante é que a JAXB do Java SE já foi projetada para ela mesma detectar se o classloading foi correto, ou se partiu de uma versão posterior/anterior a ela, mostrando uma mensagem de erro amigável. Para outras bibliotecas esse problema pode ser muito sutil: o classloader pode acabar carregando parte da biblioteca de uma versão recente, já que algumas classes novas só existem nesse jar, e o restante de uma outra antiga, resultando exceptions como NoSuchMethodError, que não mostram claramente que o problema é a existência de dois jars de versões diferentes no classpath daquela aplicação.

Podemos ver que mesmo seguindos boas práticas, isolando bibliotecas e não usando a terrível variável de ambiente CLASSPATH, acabamos sempre enfrentando o classloader hell.

i18n: Internacionalização com JSTL

Por Fabio Kung em 13/08/07

A origem do termo i18n é bastante curiosa. Pessoas preguiçosas (como eu) odeiam ter que ficar escrevendo palavras grandes como internationalization. Adivinha quantas letras existem entre o i e o n?

A mesma idéia foi usada para localization: l10n.

Internacionalizar aplicações web é cada vez mais uma tarefa corriqueira de todo desenvolvedor web. Comumente as pessoas me perguntam como fazer para ter aquelas bandeiras no topo do site, que mudam a lingua do sistema inteiro (exemplo na aplicação demo do VRaptor).

A maioria dos frameworks web tem a sua maneira particular de prover esse mecanismo, mas o que muita gente desconhece é que existe uma forma padrão de fazer isso, definida na especificação do Java EE, através da JSP Standard TagLibs.

1. Escolha uma implementação

JSTL é apenas uma especificação, portanto é necessário o uso de uma de suas implementações. A mais usada é a Jakarta Standard, que você pode baixar diretamente do site da Apache.

Certifique-se de que esteja baixando alguma implementação da versão 1.1 da JSTL (ou superior). Não garanto que irá funcionar com versões mais antigas. Essa versão 1.1 da JSTL requer o uso de, no mínimo, a versão 2.4 da especificação de Servlets. Portanto certifique-se de configurar corretamente a versão no seu web.xml. Perceba o version="2.4" no exemplo abaixo:

<?xml version="1.0" encoding="UTF-8"?>

<web-app id="MinhaWebApp" version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
         http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <display-name>Minha aplicação</display-name>

  <!-- Resto das coisas ... -->

</web-app>

Basta descompactar o zip que foi baixado e colocar o jstl.jar (jar da especificação) e o standard.jar (implementação) no diretório WEB-INF/lib da sua aplicação web.

2. Configure um arquivo de mensagens

Para dizer à JSTL qual será o arquivo que contém as mensagens do seu sistema, basta adicionar a seguinte configuração ao seu web.xml:

<web-app id="MinhaWebApp" version="2.4"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
         http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

  <display-name>Minha aplicação</display-name>
  
  <context-param>
    <param-name>
      javax.servlet.jsp.jstl.fmt.localizationContext
    </param-name>
    <param-value>messages</param-value>
  </context-param>
  
  <!– Resto das coisas … –>
</web-app>

Este arquivo de mensagens deve estar no classpath da sua aplicação web e deve possuir a extensão .properties. Existem várias formas de se fazer isso. A mais simples, é criar o arquivo messages.properties no diretório onde estão os fontes (*.java) da sua aplicação (comumente esse diretório se chama src).

Certifique-se também de não colocar este arquivo dentro de nenhum pacote. Se fosse mesmo necessário deixá-lo dentro de algum pacote, a configuração no web.xml mudaria um pouco:

<param-value>br.com.empresa.pacote.messages</param-value>.

3. Deixe todas as mensagens (e labels) do seu sistema no arquivo de mensagens

Comece colocando as mensagens no arquivo messages.properties:

site.titulo = Sistema com i18n
saudacao = Bem vindo ao sistema

campo.nome = Nome:
campo.email = Email:
campo.rua = Rua:
campo.cidade = Cidade: 

botao.enviar = Enviar
botao.cancelar = Cancelar

erro.campo.obrigatorio = Por favor, preencha o campo
# etc ...

Agora, para usar essas mensagens nas suas páginas JSP, basta usar a taglib fmt da JSTL:

<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>

<html>
<head>
<title><fmt:message key="site.titulo"/></title>
</head>
<body>
  <h1><fmt:message key="site.titulo"/></h1>
  <p><fmt:message key="saudacao"/></p>

  <form>
    <fmt:message key="campo.nome"/>
    <input type="text" name="nome" /><br/>

    <fmt:message key="campo.email"/>
    <input type="text" name="email" /><br/>

    <fmt:message key="campo.rua"/>
    <input type="text" name="rua" /><br/>

    <fmt:message key="campo.cidade"/>
    <input type="text" name="cidade" />
    <p><input type="submit"
              value='<fmt:message key="botao.enviar"/>'></p>
  </form>
</body>
</html>

4. Traduza o sistema para mais línguas

Para fazer com que o sistema possa funcionar com diferentes línguas, basta criar novos arquivos de mensagens, um para cada lingua diferente. Por exemplo, para traduzir o sistema para o alemão:

# arquivo messages_de.properties
site.titulo = System mit i18n
saudacao = Herzlichen Willkommen

campo.nome = Name:
campo.email = Email:
campo.rua = Strasse:
campo.cidade = Stadt: 

botao.enviar = Senden
botao.cancelar = Abbrechen

erro.campo.obrigatorio = Das ist ein benötige Feld
# etc ...

E para ter uma versão em inglês do sistema:

# arquivo messages_en.properties
site.titulo = i18n system
saudacao = Welcome

campo.nome = Name:
campo.email = Email:
campo.rua = Street:
campo.cidade = City: 

botao.enviar = Send
botao.cancelar = Cancel

erro.campo.obrigatorio = Required field
# etc ...

Estes arquivos devem estar no mesmo diretório do arquivo de mensagens padrão: messages.properties.

A tag fmt:message sempre procura o arquivo de mensagens mais adequado para o Locale associado ao usuário. No início, o Locale do usuário é aquele que está configurado no browser. Os navegadores enviam no cabeçalho das requisições quais são as línguas preferidas pelo usuário. Experimente mudar essas configurações no seu navegador e ver que o sistema passa a ser exibido em línguas diferentes.

Se o Locale associado usuário for en_US (inglês dos EUA), a tag fmt:message irá tentar buscar as mensagens nos seguintes arquivos, em ordem:

  • messages_en_US.properties
  • messages_en.properties
  • messages.properties

O primeiro a ser encontrado será usado. Portanto, a boa prática é ter o arquivo messages.properties com a língua padrão do sistema e um arquivo específico para cada língua adicional.

5. Mude a língua do sistema a força

Para fazer com que o sistema passe a ser exibido em uma língua diferente da que o browser pediu, basta trocar o Locale associado ao usuário.

Para tal existe uma tag da JSTL, a fmt:setLocale, que pode ser usada junto com um c:if em uma página mudaLingua.jsp, por exemplo:

<c:if test="${not empty param.lingua}">
  <fmt:setLocale value="${param.lingua}" scope="session"/>
</c:if>

Basta agora chamar essa página passando o locale adequado. Experimente chamar mudaLingua.jsp?lingua=de e mudaLingua.jsp?lingua=en e ver o sistema ser exibido em outra lingua.

Parece um pouco estranho ter um jsp responsável pela tarefa de mudar a língua do sistema, já que os jsps deveriam ser responsáveis apenas pela apresentação (geração de html, no nosso caso). A funcionalidade parece se encaixar melhor dentro de uma classe responsável por mudar a lingua do sistema.

A classe javax.servlet.jsp.jstl.core.Config serve justamente para controlar as configurações da JSTL programaticamente. Com isso, você pode ter uma servlet, uma action do struts, uma lógica do vraptor, ou algo similar para mudar a lingua do seu sistema.

Exemplo com uma servlet:

public class MudaLinguaServlet extends HttpServlet {
  @Override
  protected void service(HttpServletRequest request, HttpServletResponse response)

      throws ServletException, IOException {
        String language = request.getParameter("lingua");
        Locale locale = new Locale(language);

        Config.set(request.getSession(), Config.FMT_LOCALE, locale);
        Config.set(request.getSession(), Config.FMT_FALLBACK_LOCALE, locale);

        
        response.sendRedirect("index.jsp");
  }
}


Basta agora chamar a servlet passando o parâmetro lingua. Experimente chamar mudaLingua?lingua=en e mudaLingua?lingua=de. Ou você pode ainda deixar bandeiras para diferentes países com links para essa Servlet que muda a língua.