1.Tomcat集群
多个 Tomcat 服务器构成了一个集群(Cluster)系统,共同为客户提供服务。集群系统具有以下优点:
- 高可靠性
- 高性能计算
- 负载平衡
图1-1显示了由 JK插件和两个 Tomcat服务器构成的集群系统。集群系统的正常运作离不开以下两个组件:
- JK插件的 loadbalancer(负载平衡器):负责根据在 workers.properties 文件中预先配置的 lbfactor(负载平衡因素),为集群系统中的 Tomcat服务器分配工作负荷,实现负载平衡。(Tomcat提供了专门的JK插件来负责Tomcat和HTTP服务器的通信。应该把JK插件安置在对方的HTTP服务器上。)
- 每个 Tomcat服务器上的集群管理器(SimpleTcpCluster):每个 Tomcat服务器上的集群管理器通过 TCP 连接与集群系统中的其他 Tomcat服务器通信,以实现HTTP会话的复制,以及把 Web应用发布到集群系统中的每个 Tomcat 服务器上。
图1-1 Tomcat集群系统
1.1 配置集群系统的负载平衡器
假定在Windows中,把 Apache服务器和两个 Tomcat服务器集成。为了方便,把这三个服务器都运行在同一台物理机上,Tomcat1 使用的 AJP 端口为8009,Tomcat2 使用的 AJP端口为8109。当然如果两个 Tomcat的服务器运行在不同的物理机上,那么他们可以使用相同的 AJP端口。
以下是把 Apache和这两个 Tomcat的服务器集成,以及配置负载平衡器的步骤。
(1)把 mod_jk.so 复制到 <APACHE_HOME>/modules目录下。
(2)在<APACHE_HOME>/conf目录下创建如下的 works.properties文件:
worker.list=worker1,worker2,loadbalancer
worker.worker1.port=8009 #工作端口,若没占用则不用修改
worker.worker1.host=localhost #Tomcat服务器的地址
worker.worker1.type=ajp13 #类型
worker.worker1.lbfactor=100 #负载平衡因数
worker.worker2.port=8109 #工作端口,若没占用则不用修改
worker.worker2.host=localhost #Tomcat服务器的地址
worker.worker2.type=ajp13 #类型
worker.worker2.lbfactor=100 #负载平衡因数
worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=worker1, worker2
worker.loadbalancer.sticky_session=false
worker.loadbalancer.sticky_session_force=false
以上文件创建了两个监听 AJP 端口的worker:worker1 和worker2。worker1 和 worker2分别代表两个 Tomcat服务器,他们有负载平衡器来进行调度。由于本例中这两个服务器运行在同一个物理机上,所以应该是worker1.port 和 worker2.port 指向不同的端口。worker1 和 worker2 的 lbfactor 属性设定工作负荷,在本例中,两者的 lbfactor 属性都为100,因此会分担同样的工作负荷。
以上文件还配置了一个名为 loadbalancer 的 worker,他是负载平衡器,它有一个 sticky_session 和sticky_session_force 属性。
如果 sticky_session 的值为true,就表示会话具有 “粘性”,“粘性” 意味着当用户通过浏览器A与 Tomcat1 开始了一个会话后,以后用户从 浏览器A 中发出的请求只要处于同一个会话中,负载平衡器就会始终让 Tomcat1 来处理请求。且集群系统不会进行会话复制,如果希望集群系统能够进行会话复制,从而使得一个 浏览器 能与多个Tomcat服务器展开同一个会话,则应该把sticky_session设为false。sticky_session默认值为true。
当sticky_session 的值为true时,这个sticky_session_force没啥影响。但假定sticky_session设为true,当浏览器已经与集群系统中的Tomcat1服务器 ”黏“在一起,展开了会话后,如果这个Tomcat1的服务器异常终止。此时会出现什么情况?如果sticky_session_force为true,那么服务器端的会向客户端的返回状态代码为500的错误;如果sticky_session_force为false,那么负载平衡器会把请求转发给集群系统中的其他Tomcat2服务器,假如tomcat2服务器中不存在同一个会话的信息,当 Web 组件试图访问会话中的有关数据时可能会导致异常。sticky_session_force的默认值为false。
(3)修改 <APACHE_HOME>/conf/httpd.conf 文件,在文件末尾加入如下内容:
# Using mod_jk.so to redirect dynamic calls to Tomcat
LoadModule jk_module modules/mod_jk.so
<IfModule jk_module>
JkWorkersFile conf/workers.properties
JkLogFile logs/mod_jk.log
JkLogLevel debug
JkMount /*.jsp loadbalancer
JkMount /helloapp/* loadbalancer
</IfModule>
当客户请求 “ /.jsp ” 或 “ /helloapp/ ” 形式的 URL,该请求都由 loadbalancer 来负责转发,它根据在 works.properties 文件中为 worker1 和 worker2 设定的 lbfactor 属性,来决定如何调度他们。
(4)分别修改两个 Tomcat的服务器的 conf/server.xml 文件中 AJP 连接器的端口,确保它们和 works.properties 文件中的配置对应:
# Tomcat服务器1:
<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
# Tomcat服务器2:
<Connector port="8109" protocol="AJP/1.3" redirectPort="8443" />
此外,在使用了 loadbalancer 后,要求 worker 的名字和 Tomcat的 server.xml 文件中的 <Engine> 元素的 jvmRoute 属性一致。
所以应该分别修改两个 Tomcat 的 server.xml 文件。把它们的<Engine> 元素的 jvmRoute 属性分别设为 worker1 和 worker2。以下是修改后的两个 Tomcat服务器的 <Engine> 元素:
# Tomcat服务器1:
<Engine name="Catalina" defaultHost="localhost" jvmRoute="worker1">
# Tomcat服务器2:
<Engine name="Catalina" defaultHost="localhost" jvmRoute="worker2">
(5)在完成以上步骤后,分别启动两个 Tomcat 服务器和 Apache 服务器,然后访问 http://localhost/index.jsp,就会出现 Tomcat服务器的默认主页。
注意!在进行以上实验时,两个 Tomcat服务器都运行在同一台物理机上,应该修改其中一个Tomcat服务器的默认端口:
<Server port="8105" shutdown="SHUTDOWN" >
<Connector port="8180" ... />
<Connector port="8109" ... />
此外,把 Tomcat和其他HTTP服务器集成时,Tomcat 主要负责处理HTTP服务器转发过来的客户请求,通常不会直接接受HTTP请求。因此,为了提高Tomcat 的运行性能,也可以关闭Tomcat 的HTTP连接器,方法为在server.xml中把 <Connector … /> 的配置注释掉。
1.2 配置集群管理
需要将 Web应用,如 helloapp应用分别复制每个 Tomcat服务器的webapps目录下。
为了解决多个 Tomcat服务之间会话不同步问题,需要启动Tomcat集群管理器(SimpleTcpCluster),步骤如下:
(1)修改每个Tomcat的server.xml配置文件,在<Engine>元素中加入<Cluster>子元素,使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"
bind="127.0.0.1"
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.MessageDispatchInterceptor"/>
</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.ClusterSessionListener"/>
</Cluster>
关于<Cluster>元素的详细用法可以参考以下文档:
<CATALINA_HOME>/webapps/docs/cluster-howto.html
<CATALINA_HOME>/webapps/docs/config/cluster.html
(2)分别修改每个Tomcat服务器的helloapp应用的 web.xml 文件,在其中加入<distributable>元素:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0" >
<distributable/>
</web-app>
在集群系统中,如果Tomcat服务器中的一个Web应用的web.xml 设置了<distributable/> 元素,那么当Tomcat服务器启动这个Web应用时,会为他创建由server.xml文件中的<Cluster>元素的<Manager>子元素指定的会话管理器。在本例中,会话管理器为DeltaManager,它能够把一个服务器节点中的会话信息复制到集群系统中的所有其他服务器节点中。
(3)分别启动两个 Tomcat 服务器和 Apache 服务器,然后一个浏览器多次访问 http://localhost/helloapp/test.jsp,就会看到 test.jsp页面中的 <%= session.getId() %> 结果始终保持不变。
(3.1)为了保证在集群系统中,会话数据能够在所有 Tomcat服务器上正确的复制,应该保证存放在会话范围内的所有属性都实现了java.io.Serializable 接口。
(3.2)集群系统中的Tomcat服务器之间通过组播的形式来通信。
(3.3)DeltaManager会话管理器不适合于规模很大的集群系统中,因为他会大大增加网络通信的负荷,对于规模很大的集群系统,可以采用BackupManager会话管理器,他只会把一个服务器节点中的会话信息备份到集群系统中其他单个服务器节点。
(3.4)<Membership>元素的address属性设定组播地址。本实验中把它设为228.0.0.4,运行本实验时,因确保Tomcat所在的主机连在Internet上,否则无法访问该组播地址。