Tomcat优化--自带集群的配置Clustering

这篇文章对tomcat所自带集群功能的研究笔记。 

使用route命令查询下是否有组播的地址,如果没有可使用下面命令添加组播地址:

route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0

route add -net 224.0.0.0 netmask 240.0.0.0 dev lo

测试环境

Tomcat7.0.57 ,文章中使用tomcat1、tomcat2、tomcat3分别标识三个实例

nginx 1.6  负载均衡到三个实例,轮询模式

集群方案

方案一:All-To-All,也即是全复制模式,一个节点的session会复制到其他所有的节点中。优点是配置简单,缺点是性能方面稍低,适合少量实例集群。

方案二:BackupManager,节点备份模式,一个节点的session默认只会备份到集群中的另外一个节点。优点是相对方案一性能更好,缺点是配置复杂点。

集群要求

  1. 所有的session中属性都必须实现java.io.Serializable接口。

  2. 取消注释server.xml中的<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>

  3. 如果自定义了集群配置,务必确认添加了ReplicationValve元素的配置。

  4. 如果tomcat实例都允许在同一台机器上面,注意端口冲突的情况。

  5. 确认添加了<distributable/>到web.xml中。

  6. 如果使用的是mod_jk,确认设置了<Engine name="Catalina" jvmRoute="">中的jvmRoute值,并且必须和workers.properties中相同。

  7. 确认所有节点的系统时间相同,最好做NTP service同步。

  8. 配置负载均衡的为粘性session模式,经测试即使不配置为粘性session模式也没问题,只有是节点备份模式下会受影响。

集群的一些默认值

  1. 集群复制采用组播的方式,默认组播地址228.0.0.4,端口45564。

  2. IP组播的地址获取是java.net.InetAddress.getLocalHost().getHostAddress(),不要使用127.0.0.1,这是错误的地址。

  3. 消息复制的监听端口是取范围在4000-4100直接第一个检测可用的端口。

  4. 两个监听器被启用:JvmRouteSessionIDBinderListener和ClusterSessionListener。

  5. 两个拦截器倍启用:TcpFailureDetector 和 MessageDispatch15Interceptor。

集群方案一

配置内容: 参考上面的集群要求 1、2、5项

依次启动三个tomcat的实例,然后观察tomcat的日志,如果显示下面内容则表示集群启动正常。

Apr 16, 2015 5:52:48 PM org.apache.catalina.ha.session.DeltaManager startInternal
INFO: Starting clustering manager at localhost#
INFO: Replication member added:org.apache.catalina.tribes.membership.MemberImpl[tcp://{10, 129, 19, 88}:4001,{10, 129, 19, 88},4001, alive=1024, securePort=-1, UDP Port=-1, id={-67 63 -57 71 29 -48 67 4 -99 -73 -109 -37 104 121 50 97 }, payload={}, command={}, domain={}, ]
Apr 16, 2015 4:49:34 PM org.apache.catalina.ha.tcp.SimpleTcpCluster memberAdded
INFO: Replication member added:org.apache.catalina.tribes.membership.MemberImpl[tcp://{10, 129, 19, 88}:4002,{10, 129, 19, 88},4002, alive=1028, securePort=-1, UDP Port=-1, id={47 -46 104 -96 106 124 67 96 -84 80 -96 -121 -87 113 -12 104 }, payload={}, command={}, domain={}, ]

日志表示收到2个节点的集群信息,分别是10.129.19.88:4001和10.129.19.88:4002。

经测试任意停止其中两个tomcat后再重新启动,不管访问的是哪个tomcat所输出的sessionid还是和为停止前相同。

测试结果

第一次访问页面

输出tomcat-2: 2DABF13F420E3159300B21130310A0C6

可以看到请求的是tomcat2,然后看下日志

tomcat1:

Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.tcp.SimpleTcpCluster messageReceived
FINE: Assuming clocks are synched: Replication for 2DABF13F420E3159300B21130310A0C6-1429181500940 took=17 ms.
Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.session.DeltaManager messageReceived
FINE: Manager [localhost#]: Received SessionMessage of type=(SESSION-MODIFIED) from [org.apache.catalina.tribes.membership.MemberImpl[tcp://{10, 129, 19, 88}:4001,{10, 129, 19, 88},4001, alive=80063, securePort=-1, UDP Port=-1, id={-87 9 -77 101 -8 -55 67 67 -125 32 34 16 -96 40 39 49 }, payload={}, command={}, domain={}, ]]
Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.session.DeltaManager handleSESSION_CREATED
FINE: Manager [localhost#]: received session [2DABF13F420E3159300B21130310A0C6] created.

tomcat2:

Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.session.JvmRouteBinderValve getManager
FINE: Found Cluster DeltaManager org.apache.catalina.ha.session.DeltaManager[] at
Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.session.JvmRouteBinderValve handlePossibleTurnover
FINE: No engine jvmRoute attribute configured!
Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.session.DeltaManager sendCreateSession
FINE: Manager [localhost#] send new session (2DABF13F420E3159300B21130310A0C6)
Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.session.DeltaManager createSession
FINE: Created a DeltaSession with Id [2DABF13F420E3159300B21130310A0C6] Total count=1
Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.tcp.ReplicationValve sendSessionReplicationMessage
FINE: Invoking replication request on /session.jsp

tomcat3:

Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.tcp.SimpleTcpCluster messageReceived
FINE: Assuming clocks are synched: Replication for 2DABF13F420E3159300B21130310A0C6-1429181500940 took=25 ms.
Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.session.DeltaManager messageReceived
FINE: Manager [localhost#]: Received SessionMessage of type=(SESSION-MODIFIED) from [org.apache.catalina.tribes.membership.MemberImpl[tcp://{10, 129, 19, 88}:4001,{10, 129, 19, 88},4001, alive=80063, securePort=-1, UDP Port=-1, id={-87 9 -77 101 -8 -55 67 67 -125 32 34 16 -96 40 39 49 }, payload={}, command={}, domain={}, ]]
Apr 16, 2015 6:51:40 PM org.apache.catalina.ha.session.DeltaManager handleSESSION_CREATED
FINE: Manager [localhost#]: received session [2DABF13F420E3159300B21130310A0C6] created.

可以看出tomat2生成新的session后,把session同步给了tomcat1和tomcat3。

经过多次访问,从日志也看出每次都会同步session。

集群方案二

配置内容: 参考上面的集群要求 1、2、5项取消server.xml中的<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>注释并修改为如下内容

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
         channelSendOptions="8">
  <!-- 启用单节点备份模式 -->
  <Manager className="org.apache.catalina.ha.session.BackupManager"
           expireSessionsOnShutdown="false"
           notifyListenersOnReplication="true"/>
  <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"  
  	filter=".*\.ico|.*\.gif|.*\.js|.*\.jpeg|.*\.jpg|.*\.png|.*\.htm|.*\.html|.*\.css|.*\.txt"/>
</Cluster>

启动后的tomcat日志如下:

Apr 16, 2015 5:23:31 PM org.apache.catalina.tribes.tipis.AbstractReplicatedMap init

INFO: Initializing AbstractReplicatedMap with context name:localhost#-map

从日志可以看到使用了AbstractReplicatedMap去管理复制的信息。

测试结果

第一次访问测试页面

输出tomcat-2: 41AD3D105387122C4FDF9A50CD3E38E1

可以看到请求的是tomcat2,然后看下日志

tomcat1:  日志无内容

tomcat2:

Apr 16, 2015 6:32:30 PM org.apache.catalina.ha.session.DeltaSession writeObject
FINE: writeObject() storing session [41AD3D105387122C4FDF9A50CD3E38E1]
Apr 16, 2015 6:32:30 PM org.apache.catalina.ha.tcp.ReplicationValve sendSessionReplicationMessage
FINE: Invoking replication request on /session.jsp

tomcat3:

Apr 16, 2015 6:32:30 PM org.apache.catalina.ha.session.DeltaSession readObject
FINE: readObject() loading session [41AD3D105387122C4FDF9A50CD3E38E1]

可以看出,在tomcat2上面生成了sessionid,并且同步到了tomcat3的上面,因为是单一节点备份模式所以tomcat1未收到同步信息。

第二次访问测试页面

输出tomcat-3: 41AD3D105387122C4FDF9A50CD3E38E1

请求的是tomcat3,实例日志如下

tomcat1: 日志无内容

tomcat2:

Apr 16, 2015 6:32:30 PM org.apache.catalina.ha.session.DeltaSession writeObject
FINE: writeObject() storing session [41AD3D105387122C4FDF9A50CD3E38E1]
Apr 16, 2015 6:32:30 PM org.apache.catalina.ha.tcp.ReplicationValve sendSessionReplicationMessage
FINE: Invoking replication request on /session.jsp
Apr 16, 2015 6:34:09 PM org.apache.catalina.ha.session.DeltaSession readObject
FINE: readObject() loading session [41AD3D105387122C4FDF9A50CD3E38E1]

tomcat3:

Apr 16, 2015 6:32:30 PM org.apache.catalina.ha.session.DeltaSession readObject
FINE: readObject() loading session [41AD3D105387122C4FDF9A50CD3E38E1]
Apr 16, 2015 6:34:09 PM org.apache.catalina.ha.session.DeltaSession writeObject
FINE: writeObject() storing session [41AD3D105387122C4FDF9A50CD3E38E1]
Apr 16, 2015 6:34:09 PM org.apache.catalina.ha.tcp.ReplicationValve sendSessionReplicationMessage
FINE: Invoking replication request on /session.jsp

可以看出是在tomat3上面执行的请求,因为本地已经存在session所以id不变,处理完成后又把session同步给了tomcat2。

第三次访问测试页面

输出tomcat-1: 41AD3D105387122C4FDF9A50CD3E38E1

请求的是tomcat1,实例内容如下

tomcat1:

Apr 16, 2015 6:39:34 PM org.apache.catalina.ha.session.DeltaSession readObject
FINE: readObject() loading session [41AD3D105387122C4FDF9A50CD3E38E1]
Apr 16, 2015 6:39:34 PM org.apache.catalina.ha.tcp.ReplicationValve sendSessionReplicationMessage
FINE: Invoking replication request on /session.jsp

tomat2:

Apr 16, 2015 6:32:30 PM org.apache.catalina.ha.session.DeltaSession writeObject
FINE: writeObject() storing session [41AD3D105387122C4FDF9A50CD3E38E1]
Apr 16, 2015 6:32:30 PM org.apache.catalina.ha.tcp.ReplicationValve sendSessionReplicationMessage
FINE: Invoking replication request on /session.jsp
Apr 16, 2015 6:34:09 PM org.apache.catalina.ha.session.DeltaSession readObject
FINE: readObject() loading session [41AD3D105387122C4FDF9A50CD3E38E1]
Apr 16, 2015 6:39:34 PM org.apache.catalina.ha.session.DeltaSession writeObject
FINE: writeObject() storing session [41AD3D105387122C4FDF9A50CD3E38E1]

tomcat3:

Apr 16, 2015 6:32:30 PM org.apache.catalina.ha.session.DeltaSession readObject
FINE: readObject() loading session [41AD3D105387122C4FDF9A50CD3E38E1]
Apr 16, 2015 6:34:09 PM org.apache.catalina.ha.session.DeltaSession writeObject
FINE: writeObject() storing session [41AD3D105387122C4FDF9A50CD3E38E1]
Apr 16, 2015 6:34:09 PM org.apache.catalina.ha.tcp.ReplicationValve sendSessionReplicationMessage
FINE: Invoking replication request on /session.jsp

可以看出,tomcat1本地没有session而是从集群中获取的sessionid,然后处理完成后把session同步给了tomcat2。

最后的总结

好吧,下面才是最终要说的结果。

tomcat的集群是基于组播的方式,如果不支持组播那就不用考虑了。理论来说方案二的性能肯定会优于方案一特别是集群实例越多表现越明显,但是方案二的不足是如果集群中的两个互相备份的节点同时挂掉,那么这部分session肯定会丢失。方案二配合粘性session的负载均衡理论上表现的性能会更好,如果使用轮询负载则差不多变成方案一的方式,但还是比方案一性能更好。

另外要说一下:集群中的tomcat启动时如果检测到集群配置,则会把集群中的session复制到本地,等复制完毕后才会开始处理请求,所以如果session太多也会造成tomcat启动较慢。


另外附上输出集群日志的调试配置,在tomcat的conf/logging.properties文件末尾追加

org.apache.catalina.tribes.MESSAGES.level = FINE
org.apache.catalina.tribes.level = FINE
org.apache.catalina.ha.level = FINE


输出sessionid的jsp页面代码

<!DOCTYPE html>
tomcat-1: <%=session.getId()%>

提交评论