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.