Fenriswolf 程式筆記

奮利斯狼的地盤,小綿羊勿入

Jetty Session Replication

在組 cluster 時最容易碰到的問題就是在同個 cluster 內不同 server 之間彼此同步 session。Jetty 預設沒有支援這個功能,需要在眾多方案之中選一個來用,還要自己 build 相關的模組,非常麻煩

Jetty 支援三種 replication 方法
1. JDBC: 這是在 Jetty 7 之後才有的功能,本篇的測試環境是用 Jetty 6 所以不能使用。這個方法需要建立對應的 table 及會吃 database 的資源。以個人的經驗,很少聽到有人會使用 database 來做 session replication

2. Terracotta: 一個非常強大的 framework,提供 DSO(Distributed Shared Objects) 的機制,意思是在多個不同的 JVMs 中共享一樣的 objects。設定上比較複雜,而且需要起一個獨立的 Terracotta server。如果單純只想做 session replication 不太適合

3. WADI: 本篇介紹的技術。WADI 是 Web Application Distribution Infrastructure 的縮寫,可以做到 session replication 的功能,不需要另外啟動其他服務,而且效能會比 Terracotta 好,吃的系統資源也少。但是在 Jetty 7 的官網只找的到 1 和 2 的設定文件,下載的檔案內沒有 WADI 相關的程式,網路上也很難找到針對 Jetty 7 的設定。如果有升級需求要考慮用前兩種方法。不過其實 Jetty 7 有對 WADI 做了一些修正及測試,在 Jetty SVN 上也找的到 WADI 的原始檔,如果是進階的使用者可以去下載回來自己 build

1. 安裝 WADI 套件
不想自己 build 可到程式下載區下載
Jetty 並沒有提供 WADI 相關的 jar 檔,只有提供原始碼給使用者自己 build
1.a. 下載 Maven
下載後解開並在系統 PATH 參數加入 MAVEN_HOME/bin

1.b. build Jetty 6.1.26
下載並解壓縮 jetty-6.1.26.zip,目錄切換到 JETTY_HOME/contrib/wadi 目錄下執行

mvn install -Dmaven.test.skip=true

build 成功應該會看到 jetty-wadi-session-manager-6.1.26.jar 及其他相關 jar 檔已經被放到 JETTY_HOME/lib/wadi 目錄下,在 JETTY_HOME/contexts 下也會多一個 wadi.xml 方便大家參考及測試
加 -Dmaven.test.skip=true 本來只是為了加速而不要 compile 相關測試程式。不過 compile 這個套件的測試程式會造成以下的錯誤訊息,所以一定要加

[ERROR] org.codehaus.groovy.maven.gossip.config.Configurator - Failed to configure; using fall-back provider
org.codehaus.groovy.maven.gossip.config.ConfigurationException: Missing property: url
.....
[INFO] BUILD FAILURE

2. context xml 檔案設定
大部分這類的技術都是依靠 IP multicasting。也就是只要定義好群組及節點名稱,只要在同個網段之下,每個 server 會自動去找同組的所有 servers 並同步 session。對一般使用者而言非常方便。但要部屬在一個受限的網路環境就會有問題,例如 Amazon EC2 只支援 unicasting,不支援 mutlicasting。以下會分別介紹 Jetty session replication 如何實作 mutlicasting 及 unicasting

2.a. multicasting

<Configure class="org.mortbay.jetty.webapp.WebAppContext">
  <Set name="contextPath">/wadi</Set>
  <Set name="war"><SystemProperty name="jetty.home" default="."/>/webapps/test</Set>
  <Set name="extractWAR">false</Set>
  <Set name="copyWebDir">false</Set>
  <Set name="defaultsDescriptor"><SystemProperty name="jetty.home" default="."/>/etc/webdefault.xml</Set>
 
  <New id="wadiCluster" class="org.mortbay.jetty.servlet.wadi.WadiCluster">
    <Arg>CLUSTER</Arg>
    <Arg><SystemProperty name="node.name" default="red"/></Arg>
    <Arg>http://localhost:<SystemProperty name="jetty.port" default="8080"/>/wadi</Arg>
    <Call name="start"/>
  </New>
 
  <Set name="SessionHandler">
    <New class="org.mortbay.jetty.servlet.wadi.WadiSessionHandler">
      <Arg>
        <New id="wadiSessionManager" class="org.mortbay.jetty.servlet.wadi.WadiSessionManager">
          <Arg><Ref id="wadiCluster"/></Arg>
          <Arg type="int">2</Arg>
          <Arg type="int">24</Arg>
          <Arg type="int">360</Arg>
          <Arg type="boolean">true</Arg>
          <Arg type="boolean">false</Arg>
        </New>
      </Arg>
    </New>
  </Set>
</Configure>

這個 xml 跟 WADI 相關的設定只有 WadiCluster 及 WadiSessionHandler,因此其他部分不多做說明
WadiCluster 參數 :
1. 群組(cluster)名稱,這邊群組名稱已經被寫死了,不能在啟動 Jetty 時用 -D 參數傳入,有需要可自行修改
2. 節點(node)名稱
3. 端點(endpoint) URL,事實上這個值並沒有被用到,可以隨便給一個合法的 URL 即可

WadiSessionHandler 參數:
1. 傳入上方定義的 wadiCluster
2. 複製 session 的個數
3. WADI 會在內部儲存一個索引表來追蹤 session 的所在位置,這個值是索引的個數
4. session time out 的秒數
5. 要不要啟用 replication,當然要給 true
6. 啟用 deltaReplication。一般來說不需要啟用,但是當 session 很大時候,啟用 deltaReplication 有助於只同步 session 內修改過的部分而不是同步整個 session

其實從 xml 的格式就可以看出跟直接 call API 非常的像,如果需要用程式動態啟動 server 並加入 WADI 設定的話可以參考以下範例

String clusterName = "CLUSTER";
String nodeName = "red";
String endPointURI = "http://localhost:8080/wadi/";
WadiCluster cluster = new WadiCluster(clusterName, nodeName, endPointURI);
cluster.start();
 
WadiSessionManager wadiSessionManager = new WadiSessionManager(cluster, 2, 24, 360, true, false);
WadiSessionHandler wadiSessionHandler = new WadiSessionHandler(wadiSessionManager);
 
WebAppContext webAppContext = new WebAppContext(null, wadiSessionHandler, null, null);
webAppContext.setSessionHandler(wadiSessionHandler);
 
Server server = new Server();
server.setHandler(webAppContext);

2.b. unicasting
使用 unicasting 的方式要使用 tomcat 裡的 Tribes 模組,來讓 cluster 內各成員互相溝通,Tribes-6.0.16.jar 已經在 build 的步驟裡加入 JETTY_HOME/lib/wadi 目錄下了,不需要另外找
上半部的設定與 multicasting 一樣就不列出來了,重點在 wadiCluster 用 Static Member 的方式明確指定群組內的成員
以下是 node1 的設定,下方 addStaticMember 則是設定除了自己以外所有成員的 hostname, port 及 id

<New id="wadiCluster" class="org.mortbay.jetty.servlet.wadi.WadiCluster">
  <Arg>CLUSTER</Arg>
  <Arg><SystemProperty name="node.name" default="red"/></Arg>
  <Arg>http://localhost:<SystemProperty name="jetty.port" default="8080"/>/wadi</Arg>
   
  <Call name="addStaticMember">
      <Arg>
        <New class="org.apache.catalina.tribes.membership.StaticMember">
          <Set name="Host">localhost</Set>
          <Set name="Port">4001</Set>
          <Set name="UniqueId">{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2}</Set>
        </New>
      </Arg>
    </Call>
  <Call name="start"/>
</New>

StaticMember 參數:
1. 其他成員的 IP 或 host name
2. 其他成員溝通所用的 port
3. 成員所用的唯一識別 id,這個值其實是字串,但要用陣列的表示法傳入,而且一定要是 16 bytes

node 2 的設定
port 及 id 的值會跟 node 1 剛好相反

<New id="wadiCluster" class="org.mortbay.jetty.servlet.wadi.WadiCluster">
  <Arg>CLUSTER</Arg>
  <Arg><SystemProperty name="node.name" default="red"/></Arg>
  <Arg>http://localhost:<SystemProperty name="jetty.port" default="8080"/>/wadi</Arg>
   
  <Call name="addStaticMember">
      <Arg>
        <New class="org.apache.catalina.tribes.membership.StaticMember">
          <Set name="Host">localhost</Set>
          <Set name="Port">4000</Set>
          <Set name="UniqueId">{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}</Set>
        </New>
      </Arg>
    </Call>
  <Call name="start"/>
</New>

下面一樣列出用程式方式來執行 WADI 設定

String clusterName = "CLUSTER";
String nodeName = "red";
String endPointURI = "http://localhost:8080/wadi/";
WadiCluster cluster = new WadiCluster(clusterName, nodeName, endPointURI);
StaticMember staticMember = new StaticMember();
staticMember.setHost("localhost");
staticMember.setPort(4001);
staticMember.setUniqueId("{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2}");
cluster.addStaticMember(staticMember);
cluster.start();
 
WadiSessionManager wadiSessionManager = new WadiSessionManager(cluster, 2, 24, 360, true, false);
WadiSessionHandler wadiSessionHandler = new WadiSessionHandler(wadiSessionManager);
 
WebAppContext webAppContext = new WebAppContext(null, wadiSessionHandler, null, null);
webAppContext.setSessionHandler(wadiSessionHandler);
 
Server server = new Server();
server.setHandler(webAppContext);

其實這個做法是 multicasting 及 unicasting 兩者並行的。雖然可以解決在 EC2 部署的問題。但是如果同一個網段內有太多的 server 同時做 multicasting 還是會造成網路塞車
6.1.26 版的 WadiCluster 及 WADI 內的 TribesDispatcher classes 並沒有相關的參數可以設定
進階使用者可以去 Jetty SVN 抓 jetty-wadi-session-managerwadi-tribes 兩個模組回來自己 build,這版的 WadiCluster 多了 setDisableMulticasting method 可以明確的關掉 multicasting

3. 測試
看一下在 JETTY_HOME/contexts 裡的 test.xml 和 wadi.xml 會發現 test application 被部署了兩次
‧context path 為「/」 : 支援 session replication
‧context path 為「/wadi」: 支援 session replication
各位也可以在不同的 context path 切換看看 session 的效果

為了方便測試,在同一台機器上啟動兩個 Jetty server,分別用 8080 及 8081 port
要注意 node 名稱不能重覆

java -Djetty.port=8080 -Dnode.name=red -Djava.net.codeferIPv4Stack=true -jar start.jar
java -Djetty.port=8081 -Dnode.name=blue -Djava.net.codeferIPv4Stack=true -jar start.jar

啟動之後可以從 log 看出確實有找到其他的成員。這裡 Partition 陣列的個數就是 WadiSessionHandler 的第三個參數

New Partition Balancing
Partition Balancing
    Size [24]
    Partition[0] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[1] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[2] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[3] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[4] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[5] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[6] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[7] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[8] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[9] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[10] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[11] owned by [TribesPeer [blue; tcp://10.1.168.165:4001]]; version [2]; mergeVersion [0]
    Partition[12] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[13] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[14] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[15] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[16] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[17] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[18] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[19] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[20] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[21] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[22] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]
    Partition[23] owned by [TribesPeer [red; tcp://{10, 1, -88, -91}:4000]]; version [2]; mergeVersion [0]

測試程式直接用 Jetty 內建的 test application
連到 http://localhost:8080/wadi/ 可以看到以下畫面,點選 Session Dump Servlet

一開始沒有 session,直接點選 New Session

這裡會列出 session id,建立日期及已加入的 session attributes,這裡為了做測試已經多加了一個 hello=world 的值

接下來只要在 8080 及 8081 ports 之間切換就可以看到 session 確實有被複製到這兩個 servers 上

小提示
3.a 以上的測試是用同一台機器的同一個瀏覽器來測試,如果要跨機器及瀏覽器,或是瀏覽器不支援 cookie 時,需要在網址列後面加入 jsessionid 參數,例如
http://localhost:8080/wadi/session/;jsessionid=blue6gtcaqmyrbrt5qjnhs0v6y76
session id 的值在 Session Dump Servlet 可以找到

3.b test application 預設不允許跨機器存取
編輯 JETTY_HOME/webapps/test/WEB-INF/web.xml,把 TestFilter 的 remote 參數設為 true
 
 
執行環境
JDK 1.6.0_24
Jetty 6.1.26

參考資料
Jetty 與 WADI 整合
在 EC2 建立 Jetty cluster

程式下載
jetty-wadi-6.1.26.zip

廣告

2012/03/22 - Posted by | Application Server |

仍無迴響。

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

w

連結到 %s

%d 位部落客按了讚: