Nginx + Tomcat 7 + Java: Cluster com replicação de sessão

No post Load balancing: simulando um cluster com NGinx e Tomcat em localhost foi mostrado como configurar o NGinx para fazer o balanceamento de carga de três instâncias do Tomcat rodando na mesma máquina.

Imagine que tenhamos uma aplicação que faz uso de sessão para armazenar dados do usuário e de navegação. Como essa aplicação se comportaria em um cluster?

Considere a seguinte aplicação que consiste em dois arquivos JSP, o put.jsp e o get.jsp. O put.jsp coloca um dado na sessão e o get.jsp pega o dado da sessão.

<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Cluster test</title>
</head>
<body>

	<%		
		session.setAttribute("sessionAtribute", "Hello!");	
	 %>

	 Atribute added: <%= session.getAttribute("sessionAtribute") %>

</body>
</html>
<%@ page language="java" contentType="text/html; charset=ISO-8859-1"
    pageEncoding="ISO-8859-1"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<title>Cluster test</title>
</head>
<body>

	 Atribute retrieved: <%= session.getAttribute("sessionAtribute") %>

</body>
</html>

Se fizermos o deploy desta aplicação no cluster com as três instâncias Tomcat  que foi configurado no post sobre Nginx, cada vez que acessarmos a aplicação teremos uma resposta de um servidor diferente. Sendo assim, se acessarmos put.jsp e em seguida get.jsp, o resultado será:

Atribute retrieved: null

Somente após a terceira tentative acessando get.jsp teremos como resultado:

Atribute retrieved: Hello!

Esse comportamento se deve ao fato da sessão não estar sendo replicado entre os nós do cluster, além disso, o algoritmo de balanceamento de carga que o Nginx utiliza por padrão consiste em direcionar cada requisição para um nó do cluster de forma cíclica. Por exemplo, para o cluster com as três instâncias do Tomcat a primeira requisição vai para o nó 1, a segunda para o nó 2, a terceira para o nó 3, a quarta para o nó 1 e assim sucessivamente…

O que precisamos é que não importa para qual nó seja enviada a requisição, o nó deve ter as informações necessárias. Vejamos como configurar o Tomcat para replicar as sessões entre as instâncias de um cluster.

A configuração padrão para o Tomcat é a seguinte:

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
		channelSendOptions="8">

		<Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>

          <Channel className="org.apache.catalina.tribes.group.GroupChannel">
            <Membership className="org.apache.catalina.tribes.membership.McastService"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"
                      port="4000"
                      autoBind="100"
                      selectorTimeout="5000"
                      maxThreads="6"/>

            <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
              <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
            </Sender>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpFailureDetector"/>
            <Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>
          </Channel>

          <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
                 filter=""/>
          <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"
                    deployDir="/tmp/war-deploy/"
                    watchDir="/tmp/war-listen/"
                    watchEnabled="false"/>

          <ClusterListener className="org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener"/>
          <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
	  </Cluster>

Essa configuração deve ser feita no arquivo CATALINA_HOME/conf/server.xml dentro do elemento Engine. Essa mesma configuração deve ser feita em todas as instâncias do Tomcat que fazem parte do cluster.

Além disso, o deploy da aplicação deve ser feito em todas as instâncias do Tomcat e no arquivo web.xml deve-se adicionar o elemento distributable.

Pronto, temos um cluster com três instâncias de Tomcat rodando uma aplicação Java com sessão replicada.

Para testar, altere o arquivo get.jsp em cada instância para algo parecido com o código abaixo:

Atribute retrieved from server1: <%= session.getAttribute("sessionAtribute") %>

Atribute retrieved from server2: <%= session.getAttribute("sessionAtribute") %>

Atribute retrieved from server3: <%= session.getAttribute("sessionAtribute") %>

Desta forma, quando acessar o get.jsp você saberá qual instância do Tomcat respondeu a requisição.

Acesse primeiro o put.jsp e depois acesse quantas vezes quiser o get.jsp. Em cada requisição a instância que responde a requisição muda, mas o atributo adicionado à sessão aparece em todas as requisições, pois foi replicado em todas as instâncias.

Veja mais sobre clustering e replicação de sessão na documentação oficial do Tomcat.

Leave a Reply

Your email address will not be published. Required fields are marked *