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.