今天终于搞定了Apache和tomcat7整合集群以及负载均衡的问题,在此做个记录。
客户的网站有时候会因为一个tomcat6的错误down掉,又比较难查出示什么原因,而且是没什么规律的。所以狠下心来做了个tomcat6到7的升级,并使用了tomcat7的集群。
要说Tomcat7和6的配置确实有些不同,以前我在Server.xml的Engine节点内增加一个Listener就能自动生成一个mod_jk.conf-auto配置文件,包含到apache/conf/httpd.conf里面,然后修改一下就行了。现在就不同了,以下是我Tomcat6的server.xml配置。
<Listener className="org.apache.jk.config.ApacheConfig" modJk="D:/Apache2.213/modules/mod_jk-1.2.26-httpd-2.2.4.so" />
虽然提示我什么workers.properties找不到,但是照样能用。
Tomcat7里面这招不好用了,写上这句就会报错误。无语了。只能狠下心重新查资料,看了网上的一些文章和tomcat自己带的文档。终于搞出来了,下面就跟大家分享一下我的经验,本人水平确实有限,望大家拍砖。
首先说一下我的服务器端软件
1.Apache,我采用带SSL模块的,因为我以后还要做CAS配置用,如果不用SSL版本也一样。httpd-2.2.17-win32-x86-openssl-0.9.8o.msi
2.Tomcat版本是7.0.12,解压版的,要做集群,自动安装服务的有点不爽。
一、配置apache
1.加载链接器so文件
安装就不说了,装好后,打开apache_home/conf/httpd.conf,找到如下文本:
#LoadModule jk_module modules/mod_jk.so
把它的注释去掉,从apache官网上找对应的Connector,我这个版本apache对应的是mod_jk-1.2.28-httpd-2.2.3.so。对于这块不熟的朋友我简单说一下:其中 1.2.28是Connector的版本,后面的2.2.3是对应的apache版本。其中主要是apache的版本要对应,没有正好匹配的要找相近的。我把 LoadModule jk_module modules/mod_jk.so改为: LoadModule jk_module modules/mod_jk-1.2.28-httpd-2.2.3.so。当然你也可以把so文件修改文件名为 mod_jk.so。当然这个so文件要放在 apache_home/modules里了,否则得写绝对路径了。
这一步就算做好了,重启apache,如果没有错误说明OK。有错误无非2个。一个是文件路径写错了;另一个是so的版本与apache不匹配。
2.增加apche与tomcat的连接
在httpd.conf文件的末尾增加一句:Include conf/mod_jk.conf
包含mod_jk的配置文件,当然这个文件我们还得自己建立。
以下是我的mod_jk.conf内容:
JkWorkersFile conf/workers.properties
JkLogFile logs/mod_jk.log
JkLogLevel info
JkRequestLogFormat "%w %V %T"
JkMount /* controller
Alias /cas "D:/apache-tomcat-7.0.12/webapps/cas"
<Directory "D:/apache-tomcat-7.0.12/webapps/cas">
#Options FollowSymLinks
#AllowOverride None
</Directory>
Alias /cas "D:/apache-tomcat-7.0.12/webapps/cas"和它下面的配置是我做cas的,不必理会,可以不要。
JkWorkersFile conf/workers.properties指明worker的配置文件名,这也需要自己写。
JkMount /* controller 中的“controller”是在workers.properties中配置的,目前先这么写吧。
其他几行都是日志的配置,当时大家最好都加上,因为配置中随时有可能失败,在日志里可以查看到底出了什么错误。
JkMount /* controller这句的还以大家应该都明白,相当于以前自动生成配置中的 JkMount /* ajp13
当然这样配置只是为了方便,把所有的请求都转发到tomcat了,实际上肯定不会这么做的,否则apache只有纯转发的功能了,我们只需要把需要执行java代码的地址转发给tomcat,实际上可能这样配置:
JkMount /*.do controller
JkMount /*.jsp controller
JkMount /yourServletURL controller
我自己是配置一个VirtualHost,在VirtualHost配置这些转发的。
以下是我的workers.properties内容
worker.list=controller
worker.controller.type=lb
worker.controller.sticky_session=1
worker.controller.error_escalation_time=0
worker.controller.max_reply_timeouts=10
# localhost server 1
# ------------------------
worker.jvm1.reference=worker.template
worker.jvm1.port=8009
worker.jvm1.host=localhost
worker.jvm1.lbfactor = 5
worker.jvm1.activation=A
# localhost server 2
worker.jvm2.reference=worker.template
worker.jvm2.port=8019
worker.jvm2.host=localhost
worker.jvm2.lbfactor=1
worker.jvm2.activation=A
worker.template.type=ajp13
worker.template.socket_connect_timeout=5000
worker.template.socket_keepalive=true
worker.template.ping_mode=A
worker.template.ping_timeout=10000
worker.template.connection_pool_minsize=0
worker.template.connection_pool_timeout=600
worker.template.reply_timeout=300000
worker.template.recovery_options=3
worker.controller.balance_workers=jvm1,jvm2
具体的那些参数就不细说了,重点是tomcat的端口,默认的tomcat ajp13的端口是8009。我是在一台机器上配的2个tomcat,所以ajp的端口分别是8009和8019,还有worker.jvm1和 worker.jvm2分别代表2个Tomcat.这个在tomcat的server.xml中也有体现,下面再说这个。
还有就是worker.controller.sticky_session=1这个参数配置为true或者1,说明需要session复制。
二、配置Tomcat7
打开Tomcat7的server.xml,找到<Server port="8005" shutdown="SHUTDOWN">,其中一个tomcat要把这个端口改掉,还有默认的8009,8080,8443端口,只要一台机器上部署多个Tomcat,这些端口不能重复的,反正访问的时候也不会用这些端口访问,随便改了。要注意的是<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" URIEncoding="GBK"/>,这个东西,port的值要和 workers.properties 中的 worker.jvm1.port、 worker.jvm2.port匹配。
找到<Engine name="Catalina" defaultHost="localhost">,修改为<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">(第一个tomcat)和<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm2">(第二个Tomcat),这里的 jvmRoute的值就是workers.properties里的值了,匹配就好。
在Engine节点内,增加如下配置:
<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="4001" 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>
那个注释掉Deployer不能加,会报个严重错误(虽然能启动成功),而且这东西是集群同步文件的,而且没有实现。上面这一段配置不用修改,端口、地址之类的不用动,原样复制就可以。
这样Tomcat就算配置好了。测试一下,需要在应用的 web.xml中 web-app节点内增加
<distributable/> 配置,这样程序就会知道需要集群了。在2个tomcat上搞2个一样的应用试验一下吧,停止一个tomcat,访问还是会成功的。
需要注意的地方:
1.配置好mod_jk的日志文件,有问题随时看。
2. 如果你用的不是 mod_jk-1.2.28-httpd-2.2.3.so,比如我之前用的 mod_jk-1.2.26-httpd-2.2.4.so,可以用,但是 workers.properties里某些属性不支持,这我也是在日志里看到的,但是去掉不支持的属性也能用。
3. 如果程序需要文件上传,不要上传到web目录下,最好配置上传到单独的目录中,这样集群中的所有应用才好共享同一个上传目录。否则如果一个tomcat不运行了,另一个tomcat在运行时查找本应用下的上传文件可能会找不到,因为可能传到死掉的那个tomcat应用目录下了。如果需要用浏览器直接访问这些上传的文件(不需权限控制),应该在Apache里给上传文件的路径配置一个Directory。确保文件通过web能够正常访问。我的配置如下:
<VirtualHost *:80>
ServerAdmin [email protected]
ServerName www.xxx.cn
ServerAlias xxx.cn
#Allow from xxx.cn
ErrorLog logs/xxx.cn-error_log
CustomLog logs/xxx.cn-access_log common
DocumentRoot "E:/wwwroot/xxx_a" #主tomcat(第一个)应用根目录,我的程序叫xxx,部署2个分别较xxx_a,xxx_b
DirectoryIndex index.do index.jsp
Alias /html "E:/html" #生成静态文件的目录
<Directory E:/html>
Options FollowSymLinks
AllowOverride None
</Directory>
Alias /imageupload "D:/imageupload" #图片上传的目录
<Directory D:/imageupload>
Options FollowSymLinks
AllowOverride None
</Directory>
Alias /userfiles "E:/wwwroot/userfiles" #FckEditor的上传目录
<Directory E:/wwwroot/userfiles>
Options FollowSymLinks
AllowOverride None
</Directory>
#禁止访问的路径配置,不让用户通过浏览器访问WEB-INF
<Directory E:/wwwroot/xxx_a/WEB-INF/>
Options FollowSymLinks
AllowOverride None
Order deny,allow
Deny from all
</Directory>
</VirtualHost>
4.如果web应用里有一些后台线程或后台任务之类的东西,最好的方式是单独做成一个非web应用,独立运行,这也是servlet规范推荐的方式。不方便的话,需要在web应用中配置一个参数,指定集群中的某一个实例运行这些线程,不要一个web程序的所有部署 实例 都运行线程,那样可能出现意想不到的后果。