Tomcat集群+Apache的实现

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用的是组播地址, 使用同一个组播地址和端口的多个节点同属一个子集群,因此通过自定义组播地址和端口就可将一个大的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 配置(ver 6.0.18)
在大体了解了tomcat集群实现模型后,就可以对集群作出更优化的配置了,tomcat推荐了一套配置,使用了比DeltaManager更高效的BackupManager,并且对ReplicationValve设置了请求过滤,注意在一台服务器部署多个节点时需要修改Receiver的侦听端口,另外,为了更高效的在节点间拷贝数据,所有tomcat节点最好采用相同的配置,server.xml cluster部分配置具体配置如下
DeltaManager方式配置集群和组播, Receiver prot范围配置为4000-4100
<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"
                        bind="192.168.1.184"      #绑定网络接口
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"                   #tomcat 不在同一台机器上,要将auto改为对外网络接口IP
                      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>   
BackupManager方式配置集群和组播,Receiver prot范围未规定
 <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
                 channelSendOptions="6">
          <Manager className="org.apache.catalina.ha.session.BackupManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"
                   mapSendOptions="6"/>
          <!--
          <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="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>
说明:1. jvmRoute=“xxx” 要启用,与apache 集群配置中用到的名称一致。
      2. 要在web.xml的display-name后面 下加上<distributable/>标签。
      3. 如在一台主机上多个tomcat 节点要确认每个Receiver prot的唯一性。
         4. linux 环境下要开启组播,命令route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0。
5. 以上两中配置方法都实用。
 
第二部分 apache 配置(ver 2.2.3)
 
在Apache 2.2版本之前,一般有mod_jk2和mod_jk两个模块可供选择,mod_jk2模块是比较早的一种连接器,在动、静页面过滤上可以使用正则表达式,因此使用配置灵活。但是mod_jk2模块现在已经没有开发人员支持了,版本更新也就此停止。继承jk2模块的是mod_jk模块,mod_jk模块支持Apache 1.x和2.X系列版本,现在一般都使用mod_jk做Apache和Tomcat的连接器。
在Apache 2.2版本以后,又出现了两种连接器可供选择,就是http-proxy和proxy-ajp模块。Apache的proxy(代理)模块可以实现双向代理,功能非常强大,从连接器的实现原理看,用http-proxy模块实现也是很自然的事情,只需打开Tomcat的http功能,然后用Apache的proxy代理功能将动态请求交给Tomcat处理,而静态数据交给Apache自身就可以了。proxy-ajp模块是专门为Tomcat整合所开发的,通过ajp协议专门代理对Tomcat的请求。根据官方的测试,proxy-ajp的执行效率要比http-proxy高,因此在Apache 2.2以后的版本,用proxy-ajp模块作为Apache和Tomcat的连接器是个不错的选择。
A: Apache 采用ajp方式连接tomcat
1.     apache 采用系统默认安装和编译安装都可以,apache 2.2后版本默认安装都支持mod_proxy  mod_proxy_ajp mod_proxy_http mod_proxy_balancer mod_proxy_connect等模块,只要确认httpd.conf 配置中未注释即可(默认安装后全部模块支持都已打开,如下:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
LoadModule proxy_http_module modules/mod_proxy_http.so
LoadModule proxy_connect_module modules/mod_proxy_connect.
2.在/etc/http/conf.d 下新增虚拟主机文件proxy_ajp.conf(有些版本系统自带,内容如下)
<VirtualHost *:80>
   ServerAdmin admin@coob.cn
   DocumentRoot /home/gov/www/fg_gov/root
   DirectoryIndex index.shtml
ServerName  node2.12398.gov.cn
   ErrorLog logs/gov.error_log
   CustomLog logs/dummy-host.example.com-access_log common
#################目录权限配置###############
<Directory "/home/gov/www/fg_gov/root">
Options Indexes FollowSymLinks Includes
AllowOverride None
     Order allow,deny
Allow from all
</Directory>
#################代理配置,静态对象由apache处理###############
ProxyRequests Off
ProxyPass /p_w_picpaths !
ProxyPass /pic !
ProxyPass /js !
ProxyPass /css !
ProxyPass /html !
ProxyPass /index.shtml !
#################集群配置###############
ProxyPass / balancer://fg_gov/ stickysession=JSESSIONID|jsessionid nofailover=Off
ProxyPassReverse / balancer://fg_gov/
#ProxyPass / balancer://fg_gov/ stickysession=jsessionid nofailover=On
<Proxy balancer://fg_gov>
         BalancerMember ajp://127.0.0.1:5109 loadfactor=1 route=s1
          BalancerMember ajp://127.0.0.1:6109 loadfactor=1 route=s2
</Proxy>
</VirtualHost>
说明:stickysession=jsessionid nofailover=On(点开每个页面都轮循)
      stickysession=JSESSIONID|jsessionid nofailover=Off(关闭浏览器后才轮循)
 
B:   采用mod_jk方式连接tomcat  
 
1.    在/etc/httpd/conf.d/目录下新增mod_jk.conf workers.properties文件,
[root@node conf]# cat mod_jk.conf
LoadModule jk_module modules/mod_jk.so
JkWorkersFile "conf.d/workers.properties"
JkLogLevel info
JKLogFile "logs/mod_jk.log"
JkLogStampFormat "[%a %b %d %H:%M:%S %Y]"
JkRequestLogFormat "%w %V %T"
NameVirtualHost *:80
<VirtualHost *:80>
      ServerAdmin admin@coob.cn
      DocumentRoot /home/gov/www/fg_gov/root
      Alias /login "/home/gov/www/bg_gov/root"
      Alias /admin "/home/gov/www/admin_gov/root"
      DirectoryIndex index.shtml index.jsp
      ServerName node2.12398.gov.cn
      ErrorLog logs/gov.error_log
      CustomLog logs/dummy-host.example.com-access_log common
#################目录权限配置###############
   <Directory "/home/gov/www/fg_gov/root">
      Options FollowSymLinks Includes
      AllowOverride None
      Order allow,deny
      Allow from all
   </Directory>
      JkMount /jkstatus status
      JkMount /* loadbalancer
      JkUnmount /*.jpg loadbalancer
      JkUnmount /*.css loadbalancer
      JkUnMount /*.gif loadbalancer  
      JkUnMount /*.png loadbalancer  
      JkUnmount /*.js loadbalancer
      JkUnmount /*.html loadbalancer
      JkUnmount /*.htm loadbalancer
      JkUnmount /*.shtml loadbalancer
      JkUnmount /*.swf loadbalancer
      JkUnmount /*.flv loadbalancer
      JkUnmount /*.xml loadbalancer
      JkUnmount /*.ico loadbalancer
</VirtualHost>
说明:JkMount和JkUnmount的写法
JkMount把匹配的转发到指定服务器. 
JkUnMount把匹配的不转发到指定服务器. 
JkUnMount选项的级别高于JkMount
单独有JkMount规则有效,但单独有JkUnMount无效,JkUnMount与JkMount要成对出现.
[root@node conf.d]# cat workers.properties
########worker list#################
worker.list=loadbalancer,status
############server名为s1############
worker.s1.port=5109
worker.s1.host=localhost
worker.s1.type=ajp13
worker.s1.lbfactor=50
worker.s1.socket_timeout=300
worker.s1.cache_timeout=750
worker.s1.socket_keepalive=1
worker.s1.redirect=s2
#worker.s1.fail_on_status=-500,-503,404
############server名为s2#############
worker.s2.port=6109
worker.s2.host=localhost
worker.s2.type=ajp13
worker.s2.lbfactor=50
worker.s2.socket_timeout=300
worker.s2.cache_timeout=750
worker.s2.socket_keepalive=1
worker.s2.redirect=s1
#worker.s1.fail_on_status=500,503,-404
############oadbalancer负载均衡########
worker.loadbalancer.type=lb
worker.loadbalancer.balanced_workers=s1,s2
worker.loadbalancer.sticky_session=1
#worker.loadbalancer.sticky_session_force=1
worker.status.type=status
说明:
如果要想改变apache 和tomcat处理的静态元素,不重启apache就生效的话,可以在mod_jk.conf中使用JkWorkFile “conf.d/uriworkermap.properties”参数,这时需在/conf.d下新增uriworkermap.properties文件,就可以把上述文件中的JkMount 和JkUnmout 单独写入这个文件中,写法要用apache 官方文档中的另外一种写法(这是可取消上文中的JkMount JkUnMout部分,文件内容如下
[root@node httpd]# cat /etc/httpd/conf.d/uriworkermap.properties
/*=loadbalancer
ps=/
/jkstatus=status
!/*.gif=loadbalancer
!/*.jpg=loadbalancer
!/*.png=loadbalancer
!/*.css=loadbalancer
!/*.js=loadbalancer
!/*.htm=loadbalancer
!/*.html=loadbalancer
!/*.shtml=loadbalancer
!/*.swf=loadbalancer  
!/*.flv=loadbalancer  
!/*.xml=loadbalancer  
!/*.ico=loadbalancer