概述
Tomcat是J2EE開發當中使用最多的應用服務器,本章就來介紹一下使用Apache2應用服務器加上Tomcat6一起實現應用集羣與負載均衡。這裏我們使用的Tomcat爲7.0.26;Apache版本爲2.2,如果您機器上沒有,請到www.apache.org上下載,Tomcat7.0.26有兩個,我們這裏稱之爲tomcat1和tomcat2,下文中如果沒特指tomcat1還是tomcat2,那麼tomcat1和tomcat2都要進行操作,apache2.2只有一個。所以都準備好之後,接下來我們就來看看如何進行配置。
配置Tomcat集羣
打開tomcat下conf/server.xml文件,找到<Engine name="Catalina" defaultHost="localhost">,增加jvmRoute屬性,以支持AJP負載均衡。
tomcat1改爲<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">
tomcat2改爲<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm2">
因爲我們的兩個Tomcat在是一臺機器上運行,所以我們有必要修改其中一個Tomcat採用端口,以保證兩個同時運行時不會產生衝突(當然,如果兩個tomcat不在一臺機器上運行,那麼這個步驟就可以省略)。我們這裏修改的是tomcat2,打開tomcat2下conf/server.xml文件,修改如下:
<Server port= "8005" shutdown= "SHUTDOWN" > 修改爲 <Server port= "9005" shutdown= "SHUTDOWN" > |
<Connector port= "8080" protocol= "HTTP/1.1" connectionTimeout= "20000" redirectPort= "8443" /> 修改爲 <Connector port= "9080" protocol= "HTTP/1.1" connectionTimeout= "20000" redirectPort= "9443" />。 |
<Connector port= "8009" protocol= "AJP/1.3" redirectPort= "8443" /> 修改爲 <Connector port= "9009" protocol= "AJP/1.3" redirectPort= "9443" /> |
端口修改完成,我們需要來配置一個集羣環境中的session複製,打開tomcat下conf/server.xml文件,在<Engine></Engine>節點內加入下面代碼:
<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|.*\.jpeg|.*\.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> |
其中同臺機器上的TCP端口不能有衝突,所以在上面的配置當中,對於Tomcat2需要修改節點Culster->Channel->Reciver的port屬性,默認爲5000,tomcat識別的範圍爲4000-4100,這裏把tomcat2的這個port屬性修改爲4001。
這些配置都完成後,接下來我們就可以在這兩個Tomcat當中添加一個簡單應用來實際測試一下集羣效果。
測試Tomcat集羣
在tomcat/webapps下,新建一文件夾爲test,test文件夾下創建WEB-INF目錄,創建標準web.xml文件,文件中一定要加入<distributable/>標籤以支持分佈式應用。
web.xml文件內容如下:
<?xml version= "1.0" encoding= "ISO-8859-1" ?> <web-app xmlns= "<a
href="http://java.sun.com/xml/ns/javaee" "="">http://java.sun.com/xml/ns/javaee" xmlns:xsi= "<a href="http://www.w3.org/2001/XMLSchema-instance" "="">http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation= "http://java.sun.com/xml/ns/javaee
<a href="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" "="">http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version= "3.0"
metadata-complete= "true" > <display-name>Welcome to Test Tomcat7 Cluster</display-name> <description>Welcome to Test Tomcat7 Cluster</description> <distributable/> </web-app> |
在test文件夾下建立test.jsp文件,文件內容如下:
<%@ page contentType= "text/html; charset=UTF-8" %> <%@ page import = "java.util.*" %> <html><head><title>Cluster App Test</title></head> <body> Server Info: <% out.println(request.getLocalAddr() + " : "
+ request.getLocalPort()+ "<br>" );%> <% out.println( "<br> ID " + session.getId()+ "<br>" ); // 如果有新的 Session 屬性設置 String dataName = request.getParameter( "dataName" ); if (dataName !=
null && dataName.length() >
0 ) { String dataValue = request.getParameter( "dataValue" ); session.setAttribute(dataName, dataValue); } out.println( "<b>Session 列表</b><br>" ); System.out.println( "============================" ); Enumeration e = session.getAttributeNames(); while (e.hasMoreElements()) { String name = (String)e.nextElement(); String value = session.getAttribute(name).toString(); out.println( name + " = "
+ value+ "<br>" ); System.out.println( name + " = "
+ value); } %> <form action= "index.jsp" method= "POST" > 名稱:<input type=text size= 20 name= "dataName" > <br> 值:<input type=text size= 20 name= "dataValue" > <br> <input type=submit> </form> </body> </html> |
這個簡單的JSP可以把提交的值保存到Session當中,同時顯示出來。
啓動tomcat1,等tomcat1啓動完成後,啓動tomcat2。先訪問tomcat1對應的test工程http://localhost:8080/test.jsp頁面,輸入值並提交,然後把鏈接轉到tomcat2對應的test工程http://localhost:9080/test.jsp頁面,看看session內容是否相同,如果相同則說明session複製成功,也就是羣集成功。
接下來我們在這兩個Tomcat前端添加一個Apache Http Server,所有訪問請求通過這個Http Server進行轉發,根據tomcat1或2的忙碌情況決定轉發給tomcat1或者是tomcat2去處理用戶請求。
Apache負載均衡
打開並修改apache安裝目錄下conf/httpd.conf文件,找到下面這些內容:
#LoadModule negotiation_module modules/mod_negotiation.so #LoadModule proxy_module modules/mod_proxy.so #LoadModule proxy_ajp_module modules/mod_proxy_ajp.so #LoadModule proxy_balancer_module modules/mod_proxy_balancer.so #LoadModule proxy_connect_module modules/mod_proxy_connect.so #LoadModule proxy_ftp_module modules/mod_proxy_ftp.so #LoadModule proxy_http_module modules/mod_proxy_http.so |
去掉這些#,也就是啓用這些被註釋掉的功能,並且在文件的未尾處添加下面這些內容:
ProxyPass / balancer: //cluster/ stickysession=JSESSIONID ProxyPassReverse / balancer: //cluster/ <proxy balancer: //cluster> BalancerMember ajp: //127.0.0.1:8009 loadfactor=1 route=jvm1 BalancerMember ajp: //127.0.0.1:9009 loadfactor=1 route=jvm2 </proxy> |
重新啓動apache,以apache作爲負載均衡器的tomcat集羣建立完成,所有請求都可以通過請求apache http server,通過這個http server轉發到具體的tomcat服務器。
Tomcat集羣和Session複製詳解
Cluster
<Cluster className= "org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions= "8" > |
Tomcat集羣的主元素,在Cluster元素裏面配置了集羣的詳細信息。目前Tomcat僅提供了org.apache.catalina.ha.tcp.SimpleTcpCluste作爲唯一的實現類。下面是Cluster詳細屬性:
屬性 |
描述 |
||
---|---|---|---|
className |
Cluste的實現類,當前org.apache.catalina.ha.tcp.SimpleTcpCluste作爲唯一的實現類。 |
||
channelSendOptions |
Session發送方式,默認值爲:8。當消息通過SimpleTcpCluster發送時,用來決定如何發送信息。
如果使用異步(ASYNCHRONOUS)加應答(USE_ACK)方式來發送消息,那麼值應該是10(8+2)或者0x000B。 |
Manager
<Manager className= "org.apache.catalina.ha.session.DeltaManager" expireSessionsOnShutdown= "false" notifyListenersOnReplication= "true" /> |
Manager用來在Tomcat節點之前複製Session,當前有兩個實現類,分別爲:org.apache.catalina.ha.session.DeltaManagert和org.apache.catalina.ha.session.BackupManager。DeltaManager調用SimpleTcpCluster.send方法來發送信息,複製併發送Session到集羣下所有的節點,不管這個節點有沒有部署當前程序。
BackupManager通過自己直接調用channel來發送信息,複製併發送Session到集羣下部署了當前程序的節點。
DaltaManager的優點是經過實踐確認和證明的,非常可靠,而BackupManager在可靠性上不如DaltaManager。DaltaManager缺點是要求集羣節點必須部署了同樣的程序,節點必須是同種類的。
下面是Manager的屬性描述:
屬性 |
描述 |
---|---|
className |
Manager的實現類。 |
notifyListenersOnReplication |
如果設置爲true,當session屬性被複制和移動的時候,session listener被通知 |
expireSessionsOnShutdown |
當一個web程序被結束時,tomcat分發銷燬命令到每個Session,並通知所有session listener執行。當集羣下某個節點被停止時,如果想銷燬所有節點下的的Session,設置爲true,默認爲false 。 |
BackupManager屬性:
屬性 |
描述 |
---|---|
mapSendOptions |
BackupManager通過一個可複製的map來實現session接受和發送,通過設置mapSendOptions來設定這個map以何種方式來發送信息,默認爲:6(asynchronous)。 |
Channel
<Channel className= "org.apache.catalina.tribes.group.GroupChannel" > |
Channel是Apache Tribes的主組件,channel管理一組子組件,並和它們一起組成了tomcat實例間的通訊框架。在tomcat集羣中,DeltaManager通過SimpleTcpCluster調用channel來實現信息傳遞,而BackupManager自己調用channel以及子組件這些組件來實現信息傳遞。ReplicatedContext也會調用channel傳遞context屬性。
下面是channel子組件
*MemberShip
MemberShip組件自動檢索發現集羣裏的新節點或已經停止工作的節點,併發出相應的通知。默認使用組播(Multicast)實現。
*Sender
Sender組件管理從一個節點發送到另外一個節點的出站連接和數據信息,允許信息並行發送。默認使用TCP Client Sockets。
*Receiver
Receiver組件負責監聽接收其他節點傳送過來的數據。默認使用non-blocking TCP Server sockets。
*Interceptor
Channel通過Interceptor堆棧進行消息傳遞,在這裏可以自定義消息的發送和接收方式,甚至MemberShip的處理方式。
Value
<Valve className= "org.apache.catalina.ha.tcp.ReplicationValve" filter= " .*\.gif;.*\.js;.*\.jpg;.*\.png;.*\.css;.*\.txt;
" /> <Valve className= "org.apache.catalina.ha.session.JvmRouteBinderValve" /> |
Cluster下的Value同其他tomcat value一樣,value在調用Http Request 鏈中起着攔截器的作用,用來決定什麼情況下數據需要被複制。
org.apache.catalina.ha.tcp.ReplicationValve,ReplicationValue在Http Request結尾判斷當前數據是否需要被複制。它的具體屬性描述如下表所示:
屬性 |
描述 |
---|---|
className |
org.apache.catalina.ha.tcp.ReplicationValve,ReplicationValue |
filter |
Filter內容爲url或者文件結尾,當訪問鏈接配置filter時,不論實際session有沒有改變,集羣會認爲session沒有任何變化,從而不會複製和發送改變的session屬性。Filter寫法如下:filter=" .\.gif;.\.js;.\.jpg;.\.png;.\.css;.\.txt; "。filter使用正則表達式,每個url或者後綴以逗號分開。 |
Deployer
使集羣支持farmed deployment。這個功能還很弱,在變化和更改中。
ClusterListener
<ClusterListener className= "org.apache.catalina.ha.session.JvmRouteSessionIDBinderListener" /> <ClusterListener className= "org.apache.catalina.ha.session.ClusterSessionListener" /></Cluster> |
Clusterlistener用來追蹤信息發送和接收。JvmRouteSessionIDBinderListener用來監聽session id的變化。這個listener僅當使用mod_jk並且屬性有jvmRoute時才起作用。當session id 改變後,JvmRouteBinderValve將廣播這個信息並被此listener捕獲。ClusterSessionListener用來監聽集羣組件接收信息,當使用DeltaManager的時候,信息被集羣接收,並通過ClusterSessionListener傳遞給Session Manager。
Apache負載均衡詳解
ProxyPass / balancer: //cluster/ stickysession=JSESSIONID ProxyPassReverse / balancer: //cluster/ <proxy balancer: //cluster> BalancerMember ajp: //127.0.0.1:8009 loadfactor=1 route=jvm1 BalancerMember ajp: //127.0.0.1:9009 loadfactor=1 route=jvm2 </proxy> |
通過Apache自帶的mod_proxy某塊來使用代理技術連接tomcat, ajp_proxy需要tomcat提供ajp服務。