Tomcat5集羣中的Session複製

Tomcat 5服務器爲集羣和SESSION複製提供了集成的支持。本系列的第一篇文章將爲大家提供SESSION持久性以及TOMCAT集羣中SESSION複製的內在工作機制一個概要認識。我將會討論SESSION複製在TOMCAT5中是怎樣進行的以及跨越多集羣節點的SESSION持久性的複製機制。在第2部分,我會詳細討論一個帶有SESSION複製功能的TOMCAT集羣的安裝例子,並且比較不同的複製情形。
  
  集羣
  
  傳統獨立服務器(非集羣的)不提供任何失效無縫轉移以及負載平衡能力。當服務器失敗的時候,就無法獲取整個網站的內容,除非服務器被重新喚起。由於服務器失效,任何存儲在服務器內存中的SESSION都會丟失,用戶必須重新登陸並且輸入所有由於服務器失效丟失的數據。
  
不同的是,作爲集羣一部分的服務器則提供了可測性以及失效無縫轉移能力。一個集羣就是一組同步運行並且協同工作,能提供高可靠性,高穩定性以及高可測性的多服務器例程。服務端集羣對客戶端表現出來似乎就是一個單獨的服務器例程。從客戶端的視角來看,集羣的客戶端和單獨的服務器沒多大不同,但是他們通過提供實效無縫轉移和SESSION複製做到了不間斷服務以及SESSION數據持久性。
  
  集羣中的服務器通訊
  
  集羣中的應用程序服務器通過諸如IP多點傳送(IP multicast)和IP sockets這樣的技術和其他服務器共享信息
  
  ●IP多點傳送:主要用於1對多的服務器通訊,通過廣播服務和 heartbeats消息的可用來顯示服務器的有效
  
  ●IP sockets:主要用於在集羣的服務器例程中進行P2P服務器通訊
  
  使用IP多點傳送進行一對多通訊
  
TOMCAT服務器使用IP多點傳送在集羣中的服務器例程間進行一對多的通訊,IP多點傳送是一種能夠讓多服務器向指定IP地址和端口號進行訂閱並且監聽消息的廣播技術(多點傳送IP地址範圍從224.0.0.0 到239.255.255.255)。在集羣中的每個服務器都使用多點傳送廣播特定的 heartbeat消息,通過監視這些 heartbeat消息,在集羣中的服務器例程判斷什麼時候服務器例程失效。在服務器通訊中使用IP多點傳送的一個缺點是他不能保證這些消息被確實接收到了。例如,一個應用持續的本地多點傳送緩存滿了,就不能寫入新的多點傳送消息,等消息過了之後該應用程序就沒有被通知到。
  
  使用IP Sockets進行服務器通訊
  
IP sockets 同樣也通過了一套在集羣中的服務器間進行發送消息和數據的機制。服務器例程使用IP sockets 在集羣節點間進行HTTP SESSION狀態的複製。正確的SOKET配製對於集羣的性能是至關重要的,基於SOCKET的通訊的效率取決於SOCKET的實現類別(例如:系統使用本地的或者純JAVA SOCKET讀取器實現),如果服務器使用純JAVA SOCKET讀取器則要看服務器例程是否註冊使用了足夠的SOCKET讀取器線程。
  
  如果想要有最佳的SOCKET性能,系統應該註冊使用本地的SOCEKT而不是純JAVA實現。這是因爲相對於基於JAVA的SOCKET實現,本地SOCKET消耗更少的系統資源。雖然 SOCKET讀取器的JAVA實現是P2P通信中一種可靠而且可移動的方法,可是他不能爲集羣中的重型SOCKET使用提供最好的性能。當判斷從 SOCKET是否有數據讀取的時候本地SOCKET讀取器使用了更有效率的方法。使用本地SOCKET讀取器實現,讀取器線程不需要去統計靜止的 SOCKET:他們僅僅爲活動的SOCKET服務,並且在一個給定的SOCKET開始活躍起來時他們可以立刻捕捉到。而使用純JAVA SOCKET讀取器,線程必須動態的統計所有打開的SOCKET,判斷他們是否包含可讀取的數據。換句話說,SOCKET讀取器總是忙於統計 SOCKET,即使這些SOCKET沒有數據可讀。這些本不應該的系統開銷降低了性能。
  
  TOMCAT 5中的集羣
  
雖然在TOMCAT5的早些版本中也有集羣的功能,但是在稍後的版本中(5。0。19或者更高),集羣變的更加模塊組件化。在 server.xml 中集羣元素已經被重構,這樣我們可以替換集羣的不同部分而不會影響其他元素。例如,當前配置中把成員服務設置爲多點傳送發現。這裏可以輕易地把成員服務修改替換爲使用TCP或者 Unicast ,而不會改變集類邏輯的其他部分。
  
  其他一些集羣元素,例如SESSION管理器,複製發送端,複製接受端也可以被自定義的實現取代而不影響集羣配置的其他部分。同樣,在TOMCAT集羣中的任何服務器組件可以使用集類API向集羣中的所有成員發送消息。
  
  SESSION複製
  
服務器集羣通常操縱兩種SESSION: sticky sessions和 replicated sessions 。sticky sessions就是存在單機服務器中的接受網絡請求的SESSION,其他集羣成員對該服務器的SESSION狀態完全不清楚,如果存有SESSION 的服務器失敗的話,用戶必須再次登陸網站,重新輸入所有存儲在SESSION中的數據。
  
  另一種SESSION類型是,在一臺服務器中SESSION狀態被複制到集羣中的其他所有服務器上,無論何時,只要SESSION 被改變,SESSION數據都要重新被複制。這就是 replicated session 。 sticky 和 replicated sessions都有他們的優缺點, Sticky sessions簡單而又容易操作,因爲我們不必複製任何SESSION數據到其他服務器上。這樣就會減少系統消耗,提高性能。但是如果服務器失敗,所有存儲在該服務器內存中的SESSION數據也同樣會消失。如果SESSION數據沒有被複制到其他服務器,這些SESSION就完全丟失了。當我們在進行一個查詢事務當中的時候,丟失所有已經輸入的數據,就會導致很多問題。
  
  爲了支持 JSP HTTP session 狀態的自動失效無縫轉移,TOMCAT服務器複製了在內存中的SESSION狀態。這是通過複製存儲在一臺服務器上的SESSION數據到集羣中其他成員上防止數據丟失以及允許失效無縫轉移。
  
  對象的狀態管理
  
  通過在服務器上的保存狀態可以區分出4種對象:
  
●無狀態:一個無狀態對象在調用的時候不會在內存中保存任何狀態,因爲客戶端和服務器端沒必要保存任何有關對方的信息。在這種情況下,客戶端會在每次請求服務器時都會發送數據給服務器。SESSION狀態被在客戶端和服務器端來回發送。這種方法不總是可行和理想的,特別是當傳輸的數據比較大或者一些安全信息我們不想保存在客戶端的時候;
  
  ●會話:一個會話對象在一個SESSION中只被用於特定的某個客戶端。在SESSION中,他可以爲所有來自該客戶端的請求服務,並且僅僅是這個客戶端的請求。貫穿一個SESSION,兩個請求間的狀態信息必須保存。會話服務通常在內存中保存短暫的狀態,當在服務器失敗的時候可能會丟失。SESSION狀態通常被保存在請求間的服務器的內存中。爲了清空內存,SESSION狀態也可以被從內存中釋放(就像在一個對象CACHE)。在該對象中,性能和可量測性都有待提高,因爲更新並不是被單獨的寫到磁盤上,並且服務器失敗的時候數據也沒辦法搶救。
  
  ●緩存:緩存對象在內存中保存狀態,並且使用這個去處理從多客戶端來的請求。緩存服務的實現可以擴展到他們把緩存的是數據備份保存在後端存儲器中(通常是一個關係數據庫)。
  
●獨立的:一個獨立的對象在一個時間內只活躍在集羣中的一臺服務器上,處理來自多客戶端的請求。他通常由那些私有的,持久的,在內存中緩寸的數據支持。他同樣也在內存中保持短暫狀態,在服務器失敗的時候要重建或者丟失。當失敗的時候,獨立對象必須在同一個服務器上重起或者移植到另一臺服務器上。
  
  (來源: "Using WebLogic Server Clusters")
  
  SESSION複製的設計考慮事項
  
  網絡考慮事項
  
把集羣的多點傳送地址和其他應用程序隔離是至關重要的。我們不希望集羣配置或者網絡佈局干擾到多點傳送服務器通信。和其他應用程序共享集羣多點傳送地址將迫使集羣的服務器例程處理不應該的消息,消耗系統內存。共享多點傳送地址可能也會使IP多點傳送緩衝過載,延遲服務器 heartbeat 消息傳輸。這樣的延遲可能導致一個服務器例程被標識爲死亡,僅僅因爲他的 heartbeat 消息沒有被及時接收。
  
  編程考慮事項
  
  除了上面提到的網絡相關因素,還有些和我們寫 J2EE 網絡應用程序有關的設計考慮也會影響SESSION複製。以下列出了一些編程方面的考慮:
  
  ●SESSION數據必須被序列化:爲了支持HTTP session 狀態的內存內複製,所有的 servlet 和 JSP session 數據必須被序列化,對象中的每個域都必須被序列化,這樣對象被可靠的序列化。
  
●把應用程序設計爲冪等的:冪等的的意思就是一個操做不會修改狀態信息,並且每次操作的時候都返回同樣的結果(換句話說就是:做多次和做一次的效果是一樣的),通常,WEB請求,特別是 HTML forms 都被髮送多次(當用戶點擊發送按紐兩次,重載頁面多次),導致多次HTTP請求。設計SERVLET和其他WEB對象爲冪等的,可以容忍多次請求。詳細可以去參考設計模式“Synchronized Token ”和“Idempotent Receiver ”關於怎樣設計冪等的的應用程序。
  
  ●在BUSINESS層存儲狀態:會話狀態應該使用有狀態的SESSION BEANS存儲在EJB層,而不是存儲在WEB層的HttpSession。因爲企業應用程序要支持各種類型客戶端(WEB客戶端,JAVA應用程序,其他EJB),存儲數據在WEB層會導致在客戶端的雙數據存儲。因此,有狀態的SESSION BEAN在這些情況下就被用於存儲SESSION狀態。無狀態的SESSION BEAN要爲每次的調用重構造會話狀態。這些狀態可能必須從數據庫中恢復的數據中重編譯。這些缺點失去了使用無狀態SESSION BEAN去提高性能和可測量性的目的,嚴重的減低了性能。
  
  ●序列化系統消耗:序列化SESSION數據在複製SESSION狀態的時候回會些系統消耗。隨着序列化對象大小的增長消耗也越多。最好是保持SE



實踐中整理出tomcat集羣和負載均衡
(一)環境說明
(1)服務器有4臺,一臺安裝apache,三臺安裝tomcat
(2)apache2.0.55、tomcat5.5.15、jk2.0.4、jdk1.5.6或jdk1.4.2
(3)ip配置,一臺安裝apache的ip爲192.168.0.88,三臺安裝tomcat的服務器ip分別爲192.168.0.1/2/4
(二)安裝過程
(1)在三臺要安裝tomcat的服務器上先安裝jdk
(2)配置jdk的安裝路徑,在環境變量path中加入jdk的bin路徑,新建環境變量JAVA_HOME指向jdk的安裝路徑
(3)在三臺要安裝tomcat的服務器上分別安裝tomcat,調試三個tomcat到能夠正常啓動
(4)tomcat的默認WEB服務端口是8080,默認的模式是單獨服務,我的三個tomcat的WEB服務端口修改爲7080/8888/9999
修改位置爲tomcat的安裝目錄下的conf/server.xml
修改前的配置爲
    <Connector port="8080" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               connectionTimeout="20000" disableUploadTimeout="true" />
修改後的配置爲
    <Connector port="7080" maxHttpHeaderSize="8192"
               maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
               enableLookups="false" redirectPort="8443" acceptCount="100"
               connectionTimeout="20000" disableUploadTimeout="true" />
依次修改每個tomcat的監聽端口(7080/8888/9999)

(5)分別測試每個tomcat的啓動是否正常
http://192.168.0.1:7080
http://192.168.0.2:8888
http://192.168.0.4:9999
(三)負載均衡配置過程
(1)在那臺要安裝apache的服務器上安裝apache2.0.55,我的安裝路徑爲默認C:/Program Files/Apache Group/Apache2
(2)安裝後測試apache能否正常啓動,調試到能夠正常啓動http://192.168.0.88
(3)下載jk2.0.4後解壓縮文件
(4)將解壓縮後的目錄中的modules目錄中的mod_jk2.so文件複製到apache的安裝目錄下的modules目錄中,我的爲C:/Program Files/Apache Group/Apache2/modules
(5)修改apache的安裝目錄中的conf目錄的配置文件httpd.conf,在文件中加LoadModule模塊配置信息的最後加上一句LoadModule jk2_module modules/mod_jk2.so
(6)分別修改三個tomcat的配置文件conf/server.xml,修改內容如下
修改前
    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host). -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :
    <Engine name="Standalone" defaultHost="localhost" jvmRoute="jvm1">        
    -->
        
    <!-- Define the top level container in our container hierarchy -->
    <Engine name="Catalina" defaultHost="localhost">
修改後
    <!-- An Engine represents the entry point (within Catalina) that processes
         every request.  The Engine implementation for Tomcat stand alone
         analyzes the HTTP headers included with the request, and passes them
         on to the appropriate Host (virtual host). -->

    <!-- You should set jvmRoute to support load-balancing via AJP ie :-->
    <Engine name="Standalone" defaultHost="localhost" jvmRoute="tomcat1">        
    
        
    <!-- Define the top level container in our container hierarchy
    <Engine name="Catalina" defaultHost="localhost">
    -->
將其中的jvmRoute="jvm1"分別修改爲jvmRoute="tomcat1"和jvmRoute="tomcat2"和jvmRoute="tomcat3"

(7)然後重啓三個tomcat,調試能夠正常啓動。
(8)在apache的安裝目錄中的conf目錄下創建文件workers2.propertie,寫入文件內容如下

# fine the communication channel
[channel.socket:192.168.0.1:8009]
info=Ajp13 forwarding over socket
#配置第一個服務器
tomcatId=tomcat1 #要和tomcat的配置文件server.xml中的jvmRoute="tomcat1"名稱一致
debug=0
lb_factor=1 #負載平衡因子,數字越大請求被分配的機率越高

# Define the communication channel
[channel.socket:192.168.0.2:8009]
info=Ajp13 forwarding over socket
tomcatId=tomcat2
debug=0
lb_factor=1

# Define the communication channel
[channel.socket:192.168.0.4:8009]
info=Ajp13 forwarding over socket
tomcatId=tomcat3
debug=0
lb_factor=1

[status:]
info=Status worker, displays runtime information. 

[uri:/jkstatus.jsp]
info=Display status information and checks the config file for changes.
group=status:

[uri:/*]
info=Map the whole webapp
debug=0
(9)在三個tomcat的安裝目錄中的webapps建立相同的應用,我和應用目錄名爲TomcatDemo,在三個應用目錄中建立相同 WEB-INF目錄和頁面index.jsp,index.jsp的頁面內容如下
<%@ page contentType="text/html; charset=GBK" %>
<%@ 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.print("<b>Session 列表</b>");

  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>
(10)重啓apache服務器和三個tomcat服務器,到此負載 均衡已配置完成。測試負載均衡先測試apache,訪問http://192.168.0.88/jkstatus.jsp
能否正常訪問,並查詢其中的內容,有三個tomcat的相關配置信息和負載說明,訪問http://192.168.0.88/TomcatDemo/index.jsp看能夠運行,
能運行,則已建立負載均衡。
(四)tomcat集羣配置
(1)負載均衡配置的條件下配置tomcat集羣
(2)分別修改三個tomcat的配置文件conf/server.xml,修改內容如下
修改前
        <!--
        <Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
                 managerClassName="org.apache.catalina.cluster.session.DeltaManager"
                 expireSessionsOnShutdown="false"
                 useDirtyFlag="true"
                 notifyListenersOnReplication="true">

            <Membership
                className="org.apache.catalina.cluster.mcast.McastService"
                mcastAddr="228.0.0.4"
                mcastPort="45564"
                mcastFrequency="500"
                mcastDropTime="3000"/>

            <Receiver
                className="org.apache.catalina.cluster.tcp.ReplicationListener"
                tcpListenAddress="auto"
                tcpListenPort="4001"
                tcpSelectorTimeout="100"
                tcpThreadCount="6"/>

            <Sender
                className="org.apache.catalina.cluster.tcp.ReplicationTransmitter"
                replicationMode="pooled"
                ackTimeout="5000"/>

            <Valve className="org.apache.catalina.cluster.tcp.ReplicationValve"
                   filter=".*/.gif;.*/.js;.*/.jpg;.*/.png;.*/.htm;.*/.html;.*/.css;.*/.txt;"/>
                  
            <Deployer className="org.apache.catalina.cluster.deploy.FarmWarDeployer"
                      tempDir="/tmp/war-temp/"
                      deployDir="/tmp/war-deploy/"
                      watchDir="/tmp/war-listen/"
                      watchEnabled="false"/>
                     
            <ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/>
        </Cluster>
        -->  
修改後
        <!-- modify by whh -->
        <Cluster className="org.apache.catalina.cluster.tcp.SimpleTcpCluster"
                 managerClassName="org.apache.catalina.cluster.session.DeltaManager"
                 expireSessionsOnShutdown="false"
                 useDirtyFlag="true"
                 notifyListenersOnReplication="true">

            <Membership
                className="org.apache.catalina.cluster.mcast.McastService"
                mcastAddr="228.0.0.4"
                mcastPort="45564"
                mcastFrequency="500"
                mcastDropTime="3000"/>

            <Receiver
                className="org.apache.catalina.cluster.tcp.ReplicationListener"
                tcpListenAddress="auto"
                tcpListenPort="4001"
                tcpSelectorTimeout="100"
                tcpThreadCount="6"/>

            <Sender
                className="org.apache.catalina.cluster.tcp.ReplicationTransmitter"
                replicationMode="pooled"
                ackTimeout="5000"/>

            <Valve className="org.apache.catalina.cluster.tcp.ReplicationValve"
                   filter=".*/.gif;.*/.js;.*/.jpg;.*/.png;.*/.htm;.*/.html;.*/.css;.*/.txt;"/>
                  
            <Deployer className="org.apache.catalina.cluster.deploy.FarmWarDeployer"
                      tempDir="/tmp/war-temp/"
                      deployDir="/tmp/war-deploy/"
                      watchDir="/tmp/war-listen/"
                      watchEnabled="false"/>
                     
            <ClusterListener className="org.apache.catalina.cluster.session.ClusterSessionListener"/>
        </Cluster>
       <!-- modify by whh -->      
將集羣配置選項的註釋放開即可,如上。
(3)重啓三個tomcat。到此tomcat的集羣已配置完成。



(五)應用配置
對於要進行負載和集羣的的tomcat目錄下的webapps中的應用中的WEB-INF中的web.xml文件要添加如下一句配置
<distributable/>
配置前
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
  <display-name>TomcatDemo</display-name>
</web-app>
配置後
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version="2.4">
  <display-name>TomcatDemo</display-name>
   <distributable/>
</web-app>


問:tomcat集羣是怎麼處理session的
答:在tomcat做集羣之後,每個tomcat之間自動根據tomcat的配置文件中的參數進行session複製,
對於一個客戶端對說,只要是同一個IP,那它每次上傳的sessionID就是一樣的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章