通向架构师的道路(第五天)之tomcat集群-群猫乱舞

来源:互联网 发布:我国网络支付平台 编辑:程序博客网 时间:2024/05/16 17:08

一、为何要集群

单台App Server再强劲,也有其瓶劲,先来看一下下面这个真实的场景。


当时这个工程是这样的,tomcat这一段被称为web zone,里面用spring+ws,还装了一个jboss的规则引擎Guvnor5.x,全部是ws没有service layer也没有dao layer。

然后App Zone这边是weblogic,传输用的是spring rmi,然后App Zone这块全部是service layer, dao layer和数据库打交道。

用户这边用的是.net,以ws和web zone连的。

时间一长,数据一多,就出问题了。

拿Loader Runner跑下来,发觉是Web Zone这块,App Server已经被用到极限了。因为客户钱不多,所以当时的Web Zone是2台服务器,且都是32位的,内存不少,有8GB,测试下来后发觉cpu loader又不高,但是web server这边的吞吐量始终上不去,且和.net客户端那边响应越来越慢。

分析了一下原因:单台tomcat能够承受的最大负载已经到头了,单台tomcat的吞吐量就这么点,还要负担Guvnor的运行,Guvnor内有数百条业务规则要执行。

再看了一下其它方面的代码、SQL调优都已经到了极限了,所以最后没办法,客户又不肯拿钱投在内存和新机器上或者是再买台Weblogic,只能取舍一下,搞Tomcat集群了。

二、集群分类


Tomcat作集群的逻辑架构是上面这样的一张图,关键是我们的production环境还需要规划好我们的物理架构。

2.1 横向集群

比如说,有两台Tomcat,分别运行在2台物理机上,好处是最大的即CPU扩展,内存也扩展了,处理能力也扩展了。


2.2 纵向集群

即,两个Tomcat的实例运行在一台物理器上,充分利用原有内存,CPU未得到扩展。


2.3 横向还是纵向

一般来说,广为人们接受的是横向扩展的集群,可做大规模集群布署。但是我们这个case受制于客户即:

ü   不会再投入新机器了

ü   不会增加内存了

但是呢,通过压力测试报告我们可知:

ü   原有TomcatServer的CPU Loader不高,在23%左右

ü   原有TomcatServer上有8GB内存,而且是32位的,单台Tomcat只使用了1800MB左右的内存

ü   网络流量不高,单块千兆以太网卡完全可以处理掉

因此,我们只能做熊掌与鱼不能兼得的事,即采用了:纵向集群。

2.4 Load Balance与High Available

ü   Load Balance

简称LB即负载均衡,相当于1000根线程每个集群节点:Node负责处理500个,这样的效率是最高的。

ü   High Available

简称HA即高可用性,相当于1000根线程还是交给一台机器去慢慢处理,如果这台机器崩了,另一台机器顶上。

三、集群架构中需要解决的问题

集群规划好了怎么分,这不等于就可以开始实现集群了,一旦你的系统实现了集群,随之而来的问题就会出现了。

我们原有系统中有这样几个问题,在集群环境中是需要解决的,来看:

3.1 解决上传文件同步的问题

集群后就是两个Tomcat了,即和两个线程读同一个resource的问题是一样的,还好,我们原有上传文件是专门有一台文件伺服器的,这个问题不大,两个tomcat都往一台file server里上传,文件伺服器已经帮我们解决了同名文件冲突的这个问题了,如果原先的做法是把文件上传到Tomcat的目录中,那问题就大了,来看:

集群环境中,对于用户来说一切操作都是透明的,他也不知道我有几个Tomcat的实例运行在那边。

用户一点上传,可能上传到了Tomcat2中,但是下次要显示这个文件时,可能用到的是Tomcat1内的jsp或者是class,对不对?

于是,因为你把图片存在了Tomcat的目录中,因此导致了Tomcat1在显示图片时,取不到Tomcat2目录中存放的图片。

因此我们在工程一开始就强调存图片时要用一台专门的文件服务器或者是FTP服务器来存,就是为了避免将来出现这样的问题。

3.2 解决Quartz在集群环境中的同步问题

我们的系统用到一个Quartz(一个定时服务组件)来定时触发一些自动机制,现在有了两个Tomcat,粗想想每个Tomcat里运行自己的Quartz不就行了?

但是问题来了,如果两个Quartz在同一时间都触发了处理同一条定单,即该条定单会被处理两边。。。这不是影响效率和增加出错机率了吗?

因为本身Quartz所承受的压力几乎可以忽略不计的,它只是定时会触发脚本去运行,关键在于这个定时脚本的同步性,一致性的问题上。

我们曾想过的解决方法:

我们可以让一个Tomcat布署Quartz,另一个Tomcat里不布署Quartz

但这样做的结果就是如果布署Quartz的这个Tomcat崩溃掉了,这个Quartz是不是也崩啦?

最后解决的办法:

所以我们还是必须在两台Tomcat里布署Quartz,然后使用HA的原则,即一个Quartz在运行时,另一台Quartz在监视着,并且不断的和另一个Quartz之间保持勾通,一旦运行着的Quartz崩掉了,另一个Quartz在指定的秒数内起来接替原有的Quartz继续运行,对于Quartz,我们同样也是面临着一个熊掌与鱼不能皆得的问题了,Quartz本身是支持集群的,而它支持的集群方式正是HA,和我们想的是一致的。

具体Quartz是如何在集群环境下作布署的,请见我的另一篇文章:quartz在集群环境下的最终解决方案

解决了上述的问题后基本我们可以开始布署Tomcat这个集群了。

 


 

四、布署Tomcat集群

准备两个版本一致的Tomcat,分别起名为tomcat1,tomcat2。

4.1 Apache中的配置

²  worker.properties文件内容的修改

打开Apache HttpServer中的apache安装目录/conf/work.properties文件,大家还记得这个文件吗?

这是原有文件内容:

workers.tomcat_home=d:/tomcat2

workers.java_home=C:/jdk1.6.32

ps=/

worker.list=ajp13

worker.ajp13.port=8009

worker.ajp13.host=localhost

worker.ajp13.type=ajp13


现在开始改动成下面这样的内容(把原有的worker.properties中的内容前面都加上#注释掉):

#workers.tomcat_home=d:/tomcat2

#workers.java_home=C:/jdk1.6.32

#ps=/

#worker.list=ajp13

#worker.ajp13.port=8009

#worker.ajp13.host=localhost

#worker.ajp13.type=ajp13

worker.list = controller

#tomcat1

worker.tomcat1.port=8009       

worker.tomcat1.host=localhost

worker.tomcat1.type=ajp13

worker.tomcat1.lbfactor=1

#tomcat2

worker.tomcat2.port=9009     

worker.tomcat2.host=localhost

worker.tomcat2.type=ajp13

worker.tomcat2.lbfactor=1

#========controller========

worker.controller.type=lb

worker.controller.balance_workers=tomcat1,tomcat2

worker.lbcontroller.sticky_session=0

worker.controller.sticky_session_force=true

worker.connection_pool_size=3000

worker.connection_pool_minsize=50

worker.connection_pool_timeout=50000

 

上面的这些设置的意思用中文来表达就是:

ü   两个tomcat,都位于localhost

ü   两个tomcat,tomcat1用8009,tomcat2用9009与apache保持jk_mod的通讯

ü   不采用sticky_session的机制

sticky_session即:假设现在用户正连着tomcat1,而tomcat1崩了,那么此时它的session应该被复制到tomcat2上,由tomcat2继续负责该用户的操作,这就是load balance,此时这个用户因该可以继续操作。

如果你的sticky_session设成了1,那么当你连的这台tomcat崩了后,你的操作因为是sticky(粘)住被指定的集群节点的,因此你的session是不会被复制和同步到另一个还存活着的tomcat节点上的。

ü   两台tomcat被分派到的任务的权重(lbfactor)为一致

你也可以设tomcat1 的worker.tomcat2.lbfactor=10,而tomcat2的worker.tomcat2.lbfactor=2,这个值越高,该tomcat节点被分派到的任务数就越多

²  httpd.conf文件内容的修改

找到下面这一行:

Include conf/extra/httpd-ssl.conf

我们将它注释掉,因为我们在集群环境中不打算采用https,如果采用是https也一样,只是为了减省开销(很多人都是用自己的开发电脑在做实验哦)。

#Include conf/extra/httpd-ssl.conf

找到原来的“<VirtualHost>”段

改成如下形式:

<VirtualHost *>

DocumentRoot d:/www

<Directory "d:/www/cbbs">

    AllowOverride None

    Order allow,deny

          Allow from all

</Directory>

<Directory "d:/www/cbbs/WEB-INF">

         Order deny,allow

         Deny from all

</Directory>

ServerAdmin localhost

DocumentRoot d:/www/

ServerName shnlap93:80

DirectoryIndex index.html index.htm index.jsp index.action

ErrorLog logs/shsc-error_log.txt

CustomLog logs/shsc-access_log.txt common

 

JkMount /*WEB-INF controller

JkMount /*j_spring_security_check controller

JkMount /*.action controller

JkMount /servlet/* controller

JkMount /*.jsp controller

JkMount /*.do controller

JkMount /*.action controller

 

JkMount /*fckeditor/editor/filemanager/connectors/*.* controller

JkMount /fckeditor/editor/filemanager/connectors/* controller

</VirtualHost>

注意:

原来的JKMount *** 后的 ajp13变成了什么了?

controller

4.2 tomcat中的配置

可以拿原有的tomcat复制成另一个tomcat,分别为d:\tomcat, d:\tomcat2。

打开tomcat中的conf目录中的server.xml,找到下面这行

1)

<Server port="8005" shutdown="SHUTDOWN">

记得:

一定要把tomcat2中的这边的”SHUTDOWN”port改成另一个端口号,两个tomcat如果是在集群环境中,此处的端口号绝不能一样。

2)找到

<Connector port="8080" protocol="HTTP/1.1"

确保tomcat2中此处的端口不能为8080,我们就使用9090这个端口吧

3)把两个tomcat中原有的https的配置,整段去除

4)找到

<Connector port="8080" protocol="HTTP/1.1"

               URIEncoding="UTF-8"  minSpareThreads="25" maxSpareThreads="75"

               enableLookups="false" disableUploadTimeout="true" connectionTimeout="20000"

                   acceptCount="300"  maxThreads="300" maxProcessors="1000" minProcessors="5"

                   useURIValidationHack="false"

                             compression="on" compressionMinSize="2048"

                             compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"

               redirectPort="8443" />

确保tomcat2中这边的redirectPort为9443

5)找到

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"

改为:

<Connector port="8009" protocol="AJP/1.3" redirectPort="8443"

                    URIEncoding="UTF-8"  minSpareThreads="25" maxSpareThreads="75"

                            enableLookups="false" disableUploadTimeout="true" connectionTimeout="20000"

                            acceptCount="300"  maxThreads="300" maxProcessors="1000" minProcessors="5"

                            useURIValidationHack="false"

                                      compression="on" compressionMinSize="2048"

compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"

   

/>

确保tomcat2server.xml中此处的8009被改成了9009且其它内容与上述内容一致(redirectPort不要忘了改成9443

6)找到

<Engine name="Standalone" defaultHost="localhost" jvmRoute="jvm1">      

改成

<!-- You should set jvmRoute to support load-balancing via AJP ie :

    <Engine name="Standalone" defaultHost="localhost" jvmRoute="jvm1">        

-->

<Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat1">

同时把tomcat2中此处内容改成

   <!-- You should set jvmRoute to support load-balancing via AJP ie :

    <Engine name="Standalone" defaultHost="localhost" jvmRoute="jvm1">        

    -->

<Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat2">

7)

在刚才的

<Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat1">

的下面与在

<!-- The request dumper valve dumps useful debugging information about

           the request and response data received and sent by Tomcat.

           Documentation at: /docs/config/valve.html -->

      <!--

      <Valve className="org.apache.catalina.valves.RequestDumperValve"/>

      -->

之上,在这之间加入如下一大陀的东西:

               <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" 

                         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="4001" 

                       selectorTimeout="100" 

                       maxThreads="6"/> 

             <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter"> 

               <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender" timeout="60000"/>  

             </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;"/> 

              <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/> 

         </Cluster>

此处有一个Receiver port=”xxxx”,两个tomcat中此处的端口号必须唯一,即tomcat中我们使用的是port=4001,那么我们在tomcat2中将使用port=4002

8)把系统环境变更中的CATALINA_HOME与TOMCAT_HOME这两个变量去除掉

9)在每个tomcat的webapps目录下布署同样的一个工程,在布署工程前先确保你把工程中的WEB-INF\we b.xml文件做了如下的修改,在web.xml文件的最未尾即“</web-app>”这一行前加入如下的一行:

<distributable/>

使该工程中的session可以被tomcat的集群节点进行轮循复制。


4.3 启动集群

好了,现在启动tomcat1, 启动tomcat2(其实无所谓顺序的),来看效果:

分别访问http://localhost:8080/cbbs与http://localhost:9090/cbbs

确保两个tomcat节点都起来了,然后此时,我们启动Apache

然后访问直接用http://localhost/cbbs不加端口的形式访问:

用sally/abcdefg登录,瞧,应用起来了。

然后我们拿另一台物理客户端,登录这个web应用,我们可以看到:

第一个tomcat正在负责处理我们第一次登录的请求。

当有第二个HTTP请求时,另一个tomcat自动开始肩负起我们第二个HTTP请求了,这就是Load Balance。

23楼 雨泽 5天前 17:05发表 [回复]
学习了
22楼 lcz_2 2013-01-20 12:01发表 [回复]
真心感觉不错,谢谢楼主分析,持续关注
21楼 kky2010_110 2013-01-18 11:30发表 [回复]
worker.lbcontroller.sticky_session=0
你这里有个错误,
worker.controller.sticky_session=0这样一个tomcat挂掉以后另一个才能起作用。
我算是鸡蛋里挑骨头了,真心写的不错了
20楼 java0000wa 2013-01-17 15:08发表 [回复]
楼主 加了你那一坨之后session不复制了 和解? 
我把默认的注释去掉可行....
19楼 宋杰 2013-01-09 22:12发表 [回复]
引用“lifetragedy”的评论:回复zhimajiejie:很感兴趣的是你能告诉我你把什么存在SESSIO...

比如用户选购的商品之类的东西,用户的信息等。我之前实习的一家公司遇到了这个问题。在多个tomcat中循环复制session,岂不是会降低效率,特别是tomcat很多的时候。
Re: lifetragedy 2013-01-09 23:43发表 [回复]
回复zhimajiejie:session里除了用户基本信什么都不应该放.

如果你把每次查询什么商品信息也放在了SESSION里,导致这个SESSION太大的话,比如说有人喜欢用SESSION做DB查询结果暂存或者是用SESSION来做显示分页,由此造成的一切性能问题,自负.
18楼 宋杰 2013-01-09 18:58发表 [回复]
另外一个问题是,当做tomcat集群的时候,如果tomcat的数量太多的话,比如5-6个的时候,session的复制回导致每个tomcat的性能下降的很厉害。请问楼主,有什么好的办法吗?我从网上看说可以使用很多方式,一般是存储到数据库或者缓存中,但是对于现有程序的改动比较大。另外有人说可以使用Terracotta,问一下楼主有没有这方面的经验,Terracotta在生产环境中的使用怎样?
Re: lifetragedy 2013-01-09 21:35发表 [回复]
回复zhimajiejie:很感兴趣的是你能告诉我你把什么存在SESSION里了?
17楼 宋杰 2013-01-08 22:34发表 [回复]
楼主,你好。按你的方法配置tomcat集群的时候,发现session不能在多个tomat中进行复制,就是一个tomcat被关闭之后,其中的session不能复制到其他tomcat中。后来发现修改works.properties中的两个配置参数就可以了
worker.lbcontroller.sticky_session=0
worker.controller.sticky_session_force=true
改为
worker.lbcontroller.sticky_session=1
worker.controller.sticky_session_force=false
不知道为什么?
16楼 宋杰 2013-01-08 16:58发表 [回复]
楼主,你好!有一个问题想向你请教。对于tomcat集群中,application中存放的属性,楼主是怎么处理的呢?我们在项目中使用的是memcached存放原来需要存放到application中的属性。
Re: lifetragedy 2013-01-08 20:24发表 [回复]
回复zhimajiejie:不知道你用的到底是哪个MEMCACHE,对于不同的MEMOCACHE处理是不同的.
有的CACHE支持集群,即LOAD BALANCE
有的CACHE不支持集群但支持HIGH AVAILABLE即CACHE是一份,始终是一份,然后这个节点死,另一个节点也布署着CACHE,那个CACHE会在另一个节点的CACHE死掉后的1-2秒内(可设置有的)自动起来,和QUARTZ的集群原理一样,一般这样的CACHE有的甚至还会用数据库来做persist
Re: 宋杰 2013-01-09 18:53发表 [回复]
引用“zhimajiejie”的评论:楼主,你好!有一个问题想向你请教。对于tomcat集群中,application中存放的属性,楼主是...

感谢楼主的回复,我们使用的就是http://memcached.org/这个,这个不支持HA,所以还是不够好。我按照楼主的提示再试试quartz
15楼 deshi168 2012-12-07 11:35发表 [回复]
必须顶,正在进行第一遍的浏览,以后会研究很多遍,希望博主出更多好文章!
14楼 zxm94w 2012-12-04 20:30发表 [回复]
对了,发现tomcat的server.xml和你的有些差别,<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">

我们是 Catalina 而你提供的 里面是 Standalone,但是想来这个应该没大碍吧?
Re: lifetragedy 2012-12-04 22:18发表 [回复]
回复zxm94w:work.properties里worklist指的是什么?

它通过8009去解析jmv1, jmv2,这边的jvm1必须与worklist指定的一致。
自己多试试,这样说吧,一样新的东西试个100遍,成功一次,很正常,一旦成功了就是你自己的知识了,况且这东西不需要试100次。
13楼 zxm94w 2012-12-04 20:28发表 [回复]
楼主你好,一路按照流程走下来,一切都OK,唯独最后这,集群的时候,发现通过Apache服务器访问的时候报500,但是单独请求两个tomcat的端口是正常的,再三检查了配置文件,没有发现啥异常,求解!
12楼 opqaz 2012-11-30 15:21发表 [回复]
博主 ,我配置后 是500错误
11楼 zrarzw 2012-11-29 15:57发表 [回复]
直接访问两个tomcat是没问题的
Re: zxm94w 2012-12-04 17:28发表 [回复]
回复zrarzw:帅哥,你的问题解决了么?貌似我也遇到和你一样的情况了。
Re: lifetragedy 2012-11-29 16:10发表 [回复]
回复zrarzw:你的WORK.PROPERTIES可能没配好
Re: zhujyy110 2012-12-14 17:18发表 [回复]
回复lifetragedy:还有一个可能就是 workers.properties后面带了空格
我一个tomcat可以 另外一个不行,
一点一点排除终于OK 感谢分享
Re: lifetragedy 2012-12-14 17:22发表 [回复]
回复zhujyy110:开源得东西就是这样,有时搞多一个空格,打了个全角,少打个逗号一类得,没有gui界面是要仔细点哈
Re: lifetragedy 2012-12-14 17:02发表 [回复]
回复lifetragedy:是的,TOMCAT是这样的,商业的就不用这样,有GUI配置界面。你可以看后面的WEBLOGIC集群与WAS集群
Re: zhujyy110 2012-12-14 16:58发表 [回复]
回复lifetragedy:worker.tomcat2.host这些要和tomcat里面配置一样
如果修改了tomcat的域名 这些地方都需要修改
10楼 zrarzw 2012-11-29 15:56发表 [回复]
博主配了集群后访问http://localhost/demoweb/ 报404错误 不知道哪里有问题 我按照你的教材配的
9楼 wangdan902 2012-11-22 15:08发表 [回复]
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" 
加了这一坨之后报错呀
Unable to start cluster.
org.apache.catalina.tribes.ChannelException: java.io.IOException: Invalid argument; No faulty members identified.
Re: lifetragedy 2012-11-22 15:20发表 [回复]
回复wangdan902:请仔细检查自己的配置,相信是个小错误,不太好找,没事,哪怕找个通宵找到问题了,这块就学会了,是这样的,没办法,都是这么经历过来的。

文中配置没有问题,因为大都人都配起来了.
Re: wangdan902 2012-11-29 09:57发表 [回复]
回复lifetragedy: <Membership className="org.apache.catalina.tribes.membership.McastService" 
前几天忙的没时间来咯,应该说加了这段就报错
Re: lifetragedy 2012-11-29 10:12发表 [回复]
回复wangdan902:你的TOMCAT版本?请用apache官网的tomcat6.0.20或以上版本,tomcat5和7不行,配置不一样的。
Re: wangdan902 2012-11-29 10:38发表 [回复]
回复lifetragedy:linux 64位
Re: lifetragedy 2012-11-29 11:00发表 [回复]
回复wangdan902:明了,linux下你的BROADCAST是不是被LINUX的防火墙给禁了。。。在LINUX下是有这个问题,需要把BROADCAST用route add加入,这点和WINDOWS下不一样,嘿
Re: wangdan902 2012-11-29 10:37发表 [回复]
回复lifetragedy:tomcat 6.0.35
8楼 sojer 2012-11-22 11:34发表 [回复]
超详细易学!收藏了
7楼 子龍威武 2012-11-20 14:52发表 [回复]
学习啦
6楼 生活好无奈 2012-07-13 09:50发表 [回复]
很赞 很多刚毕业的人需要这种指导
5楼 lgwinym 2012-07-10 16:38发表 [回复]
很不错的文章,学习了。。。
4楼 baungham 2012-07-09 13:35发表 [回复] [引用] [举报]
噢!正是我一直想要了解东西,太好了,谢谢了,希望楼主能继续发表这一系列文章!
3楼 lw_China 2012-07-09 13:34发表 [回复]
>ü 原有TomcatServer上有8GB内存,而且是32位的,单台>Tomcat只使用了1800MB左右的内存

32 JVM位的话,8G内存就是摆设啊,1800MB的使用基本上是最大值了。吞吐量上不去,CPU不高,但是内存高,我猜测GC比较频繁。。 如果可以换64位试试吧。