Tomcat Cluster官网:https://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html(tomcat7.0)
场景:
tomcat1 tomcat2
| |
--------|---------
nginx
tomcat版本:7.0.59
1.后端应用为2台tomcat容器,并且配置了Tomcat Cluster;
2.nginx为前端web服务器,负责反向代理和负载均衡(简单轮训),配置如下:
upstream tomcat { server 192.168.70.130:8080; server 192.168.70.131:8080; }
3.在应用代码中将一个map对象存放在session中,即:
Map<String, Object> map = new HashMap<String, Object>(); map.put("email", "12345"); req.getSession().setAttribute(Constants.KEY_SESSION_MAP, map);
问题:
t1: 请求1经过nginx访问到tomcat1,此时将session中保存的map对象取出来,同时更改map对象保存的<key:value>值:
Map<String, Object> map = (Map<String, Object>) req.getSession().getAttribute(Constants.KEY_SESSION_MAP); map.put("email", "67890");
t2: 请求2经过nginx访问到tomcat2,取出session中保存的map对象,并取得其中的<key:value>值:
Map<String, Object> map = (Map<String, Object>) req.getSession().getAttribute(Constants.KEY_SESSION_MAP); map.get("email");
这时发现,在tomcat2中取得的map对象<key:value>与在tomcat1中修改后存放的<key:value>不一致!
具体来说,在tomcat1中map对象修改后存放的<key:value>值为:<"email":"67890">,但是在tomcat2中map对象取出来的<key:value>依然为之前的值:<"email":"12345">。
看起来,tomcat的集群session同步失效了?!
原因:
Data is only replicated if the session has changed (by calling setAttribute or removeAttribute on the session).
即:只有当明确调用session.setAttribute()或者session.removeAttribute()时才会同步session数据。
详见:https://tomcat.apache.org/tomcat-7.0-doc/cluster-howto.html Cluster Information
Tomcat集群配置:
<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=".*\.gif|.*\.js|.*\.jpeg|.*\.jpg|.*\.png|.*\.htm|.*\.html|.*\.css|.*\.txt"/> <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>
如上所示,Tomcat集群使用org.apache.catalina.ha.session.DeltaManager管理session同步,追踪源码:
@Override public Session createEmptySession() { return getNewDeltaSession() ; } /** * Get new session class to be used in the doLoad() method. */ protected DeltaSession getNewDeltaSession() { return new DeltaSession(this); }
显然,DeltaManager创建的Session实例为org.apache.catalina.ha.session.DeltaSession对象实例。
DeltaSession保存数据时序图:
如上图所示,org.apache.catalina.ha.session.DeltaSession在保存数据时将发送集群消息,以实现session数据同步。
解决办法:
当session中保存的数据发生改变时,需要重新调用session.setAttribute(),这样才会在集群中同步最新的session数据,即:
// 修改session中map对象保存的值 Map<String, Object> map = (Map<String, Object>) req.getSession().getAttribute(Constants.KEY_SESSION_MAP); map.put("email", "67890"); req.getSession().setAttribute(Constants.KEY_SESSION_MAP, map); // session保存的值发生改变时,必须重新调用session.setAttribute()触发session数据同步
总结:
在使用Tomcat Cluster进行session同步时,保存在session中的数据如果发生了变化,则必须重新调用session.setAttribute()进行保存。这样才能触发Tomcat发送集群消息,将最新修改的session数据同步到其他节点上。其实不难理解,Tomcat Cluster之所以采用这样的数据同步机制,就是希望当session发生变化(通过保存或删除数据)时才进行同步,减少不必要的集群同步消息。