在上一篇文章中简要介绍了如何经过简单的配置来实现tomcat集群,本文意在介绍对tomcat集群进行更深刻详细的配置以知足特定需求。 css
对于WEB应用集群的技术实现而言,最大的难点就是如何能在集群中的多个节点之间保持数据的一致性,会话(Session)信息是这些数据中最重要的一块。 html
要实现这一点,大致上有两种方式, 前端
一种是把全部Session数据放到一台服务器上或者数据库中,集群中的全部节点经过访问这台Session服务器来获取数据; linux
另外一种就是在集群中的全部节点间进行Session数据的同步拷贝,任何一个节点均保存了全部的Session数据。 web
两种方式都各有优势, 数据库
第一种方式简单、易于实现,可是存在着Session服务器发生故障会致使全系统不能正常工做的风险; apache
第二种方式可靠性更高,任一节点的故障不会对整个系统对客户访问的响应产生影响,可是技术实现上更复杂一些。 编程
常见的平台或中间件如microsoft asp.net和IBM WAS都会提供对两种共享方式的支持,tomcat也是这样,可是通常采用第二种方式。 windows
当采用tomcat默认集群配置(<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>)时,配置的细节实际上被省略了, api
对于大多数应用而言,使用默认配置已经足够,完整的默认配置应该是这样:
<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>
下面笔者对这里的配置项做详细解释,如下内容均是笔者阅读了tomcat官方文档后本身的理解,有些可能不对,但愿读者能带着批判的眼光阅读,并欢迎指正笔者错误。
tomcat集群各节点经过创建tcp连接来完成Session的拷贝,拷贝有同步和异步两种模式。
在同步模式下,对客户端的响应必须在Session拷贝到其余节点完成后进行;
异步模式无需等待Session拷贝完成就可响应。
异步模式更高效,可是同步模式可靠性更高。同步异步模式由channelSendOptions参数控制,默认值是8,为异步模式,4是同步模式。
在异步模式下,能够经过加上拷贝确认(Acknowledge)来提升可靠性,此时channelSendOptions设为10。
Manager用来在节点间拷贝Session,默认使用DeltaManager,DeltaManager采用的一种all-to-all的工做方式,
即集群中的节点会把Session数据向全部其余节点拷贝,而无论其余节点是否部署了当前应用。
当集群中的节点数量不少而且部署着不一样应用时,可使用BackupManager,BackManager仅向部署了当前应用的节点拷贝Session。
可是到目前为止BackupManager并未通过大规模测试,可靠性不及DeltaManager。
Channel负责对tomcat集群的IO层进行配置。Membership用于发现集群中的其余节点,
这里的address用的是组播地址(Multicast address,了解更多组播地址详情请参见http://baike.baidu.com/link?url=bskqRGqtKOVFM69eP71TxILdevuawZQtOtoQ-QOSkvUjarXBa7JURIAEC4Qtad1y2wKU2zD8ZS7ddxVq2u5v-a),
使用同一个组播地址和端口的多个节点同属一个子集群,所以经过自定义组播地址和端口就可将一个大的tomcat集群分红多个子集群。
Receiver用于各个节点接收其余节点发送的数据,在默认配置下tomcat会从4000-4100间依次选取一个可用的端口进行接收,
自定义配置时, 若是多个tomcat节点在一台物理服务器上注意要使用不一样的端口。
Sender用于向其余节点发送数据,具体实现经过Transport配置,
PooledParallelSender是从tcp链接池中获取链接,能够实现并行发送,即集群中的多个节点能够同时向其余全部节点发送数据而互不影响。
Interceptor有点相似下面将要解释的Valve,起到一个阀门的做用,在数据到达目的节点前进行检测或其余操做,如TcpFailureDetector用于检测在数据的传输过程当中是否发生了tcp错误。
关于Channel的编程模型,请参见http://tomcat.apache.org/tomcat-6.0-doc/api/org/apache/catalina/tribes/Channel.html。
Valve用于在节点向客户端响应前进行检测或进行某些操做,
ReplicationValve就是用于用于检测当前的响应是否涉及Session数据的更新,若是是则启动Session拷贝操做,filter用于过滤请求,如客户端对图片,css,js的请求就不会涉及Session,
所以不需检测,默认状态下不进行过滤,监测全部的响应。
JvmRouteBinderValve会在前端的Apache mod_jk发生错误时保证同一客户端的请求发送到集群的同一个节点,
tomcat官方文档并未解释如何实现这一点,并且笔者认为这一设置彷佛并没有多大实用性。
Deployer用于集群的farm功能,监控应用中文件的更新,以保证集群中全部节点应用的一致性,
如某个用户上传文件到集群中某个节点的应用程序目录下,Deployer会监测到这一操做并把这一文件拷贝到集群中其余节点相同应用的对应目录下以保持全部应用的一致。
这是一个至关强大的功能,不过很遗憾,tomcat集群目前并不能作到这一点,开发人员正在努力实现它,这里的配置只是预留了一个接口。
Listener用于跟踪集群中节点发出和收到的数据,也有点相似Valve的功能。
在大致了解了tomcat集群实现模型后,就能够对集群做出更优化的配置了,
tomcat推荐了一套配置,使用了比DeltaManager更高效的BackupManager,而且对ReplicationValve设置了请求过滤,
注意在一台服务器部署多个节点时须要修改Receiver的侦听端口,另外,为了更高效的在节点间拷贝数据,全部tomcat节点最好采用相同的配置,
具体配置以下:
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6">
<Manager className="org.apache.catalina.ha.session.BackupManager"
expireSessionsOnShutdown="false"
notifyListenersOnReplication="true"
mapSendOptions="6"/>
<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="5000"
selectorTimeout="100"
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"/>
<Interceptor className="org.apache.catalina.tribes.group.interceptors.ThroughputInterceptor"/>
</Channel>
<Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
filter=".*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
<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>
Tomcat集群除了能够进行Session数据的拷贝,还可进行Context属性的拷贝,经过修改context.xml的Context配置能够实现,
使用<Context className="org.apache.catalina.ha.context.ReplicatedContext"/>替换默认Context便可,固然也可再加上distributable="true"属性。
下面经过假想的一组场景来描述tomcat集群如何工做,集群采用默认配置,由t1和t2两个tomcat例程组成,场景按照时间顺序排列。
1. t1启动
t1按照标准的tomcat启动,当Host对象被建立时,一个Cluster对象(默认配置下是SimpleTcpCluster)也同时被关联到这个Host对象。
当某个应用在web.xml中设置了distributable时,Tomcat将为此应用的上下文环境建立一个DeltaManager。
SimpleTcpCluster启动membership服务和Replication服务(用于创建tcp链接)。
2. t2启动(待t1启动完成后)
首先t2会执行和t1同样的操做,而后SimpleTcpCluster会创建一个由t1和t2组成的membership。
接着t2向集群中已启动的服务器即t1请求Session数据,若是t1没有响应t2的拷贝请求,t2会在60秒后time out。
在Session数据拷贝完成以前t2不会接收客户端的http或mod_jk/ajp请求。
3. t1接收http请求,建立Session s1
t1正常响应客户请求,可是在t1把结果发送回客户端时,ReplicationValve会拦截当前请求(若是filter中配置了不需拦截的请求类型,这一步就不会进行,默认配置下拦截全部请求),
若是发现当前请求更新了Session,调用Replication服务创建tcp链接把Session拷贝到membership列表中的其余节点即t2,
返回结果给客户端(注意,若是采用同步拷贝,必须等拷贝完成后才会返回结果,异步拷贝在数据发送到tcp链接就返回结果,不等待拷贝完成)。
在拷贝时,全部保存在当前Session中的可序列化的对象都会被拷贝,而不只仅是发生更新的部分。
4. t1崩溃
当t1崩溃时,t2会被告知t1已从集群中退出,而后t2就会把t1从本身的membership列表中删除,
发生在t2的Session更新再也不往t1拷贝,同时负载均衡器会把后续的http请求所有转发给t2。
在此过程当中全部的Session数据不会丢失。
5. t2接收s1的请求
t2正常响应s1的请求,由于t2保存着s1的全部数据。
6. t1从新启动
按步骤一、2同样的操做启动,加入集群,从t2拷贝全部Session数据,拷贝完成后开放本身的http和mod_jk/ajp端口接收请求。
7. t1接收请求,s1失效
t1继续接收来自s1的请求,把s1设置为过时。
这里的过时并不是由于s1处于非活动状态超过设置的时间,而是执行相似注销的操做而引发的Session失效。
这时t1并不是发送s1的全部数据而是一个相似s1 expired的消息,t2收到消息后也会把s1设为过时。
8. t2接收请求,建立Session s2
和步骤3同样。
9. t1 s2过时
对于因超时引发的Session失效t1无需通知t2,由于t2一样知道s2已经超时。
所以对于tomcat集群有一点很是重要,全部节点的操做系统时间必须一致!
否则会出现某个节点Session已过时而在另外一节点此Session仍处于活动状态的现象。
由于tomcat的session同步功能须要用到组播,
windows默认状况下是开通组播服务的,
可是linux默认状况下并无开通,
能够经过指令打开route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0,
若是须要服务器启动时即开通组播需在/etc/sysconfig/static-routes文件内加入
eht0 net 224.0.0.0 netmask 240.0.0.0。