「Tomcat」クラスタ環境を構築する ( セッションレプリケーション有効 )

Tomcat でセッションレプリケーション有効なクラスタ環境を構築してみたので、メモしておきます。

内容的には以下のドキュメントほぼそのままです。ちょっと訳あってバージョンは古いですが 6.0 系でやってます・・・

Apache Tomcat 6.0 (6.0.43) - Clustering/Session Replication HOW-TO
https://tomcat.apache.org/tomcat-6.0-doc/cluster-howto.html

事前に以下のエントリーで書いた内容を実施して、Tomcat複数インスタンス ( instance1 / instance2 ) 起動できるようにしておく。

・「Tomcat複数インスタンスで起動する - プログラム日記
http://a4dosanddos.hatenablog.com/entry/2014/03/08/182306

instance1 / instance2 の server.xmlクラスタのための設定を行なう。

※ 以下のコメントアウトの下ぐらいに追記する。

<!--
<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>
-->    


        <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6">
          <!--
          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>
          -->
          <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"
                        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;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
          <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>


        <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster" channelSendOptions="6">
          <!--
          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>
          -->
          <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"
                        address="228.0.0.4"
                        port="45564"
                        frequency="500"
                        dropTime="3000"/>
            <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                      address="auto"
                      port="5001"
                      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;.*\.jpg;.*\.png;.*\.htm;.*\.html;.*\.css;.*\.txt;"/>
          <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>

Receiver のポート番号を instance1 / instance2 で異なるようにしています。

Manager には "DeltaManager"、"BackupManager" があるみたいです。前者は all-to-all replication of session ってあるので全ノードに対してレプリケーションする、後者 ( 今回はこっち ) は primary-secondary session replication とあるのでプライマリからセカンダリに対してのみレプリケーションするという感じでしょうか。

In this release of session replication, Tomcat can perform an all-to-all replication of session state using the DeltaManager or perform backup replication to only one node using the BackupManager. The all-to-all replication is an algorithm that is only efficient when the clusters are small. For larger clusters, to use a primary-secondary session replication where the session will only be stored at one backup server simply setup the BackupManager.

設定は上記だけで完了です。以下は確認用のアプリケーション。

セッションを生成するサーブレット、セッションオブジェクトにバインドされた属性を取得するサーブレットで確認してみようと思います。

■ CreateSessionServlet.java

package test;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import domain.Person;
import domain.PersonSeri;

public class CreateSessionServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public CreateSessionServlet() {
        super();
    }
    
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession();
		session.setAttribute("personSeri", new PersonSeri("name1"));
		
		PrintWriter out = response.getWriter();
		out.println("<html><body>");
		out.println("Create Session !!");
		out.println("</body></html>");
	}
}

■ GetSessionAttrServlet.java

package test;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import domain.Person;
import domain.PersonSeri;

public class GetSessionAttrServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
       
    public GetSessionAttrServlet() {
        super();
    }
    
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		
		PrintWriter out = response.getWriter();
		out.println("<html><body>");
		out.println("PersonSeri " + ((PersonSeri)request.getSession().getAttribute("personSeri")).getName());
		out.println("<br>");
		out.println(request.getSession().getId());
		out.println("</body></html>");
	}
}

セッションオブジェクトにバインドしている PersonSeri は以下の感じです。Serializable インターフェイスを実装するのを忘れないようにする。

package domain;

import java.io.Serializable;

public class PersonSeri implements Serializable {
	
	private String name;

	public PersonSeri(String name) {
		this.name = name;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

web.xml に distributable 要素を設定しておく。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">

  - 略 ( CreateSessionServlet、GetSessionAttrServlet のサーブレットマッピングの設定 )

  <distributable />
</web-app>

instance1 と instance2 にそれぞれデプロイする。で、以下の順にブラウザからアクセスする。

1. instance1 の CreateSessionServlet でセッションを生成する
2. instance1 の GetSessionAttrServlet でセッションオブジェクトにバインドされた値を取得する ( ブラウザ上に PersonSeri : name1 と表示される)
3. instance2 の GetSessionAttrServlet でセッションオブジェクトにバインドされた値を取得する

3 の際に instance2 ではセッションを生成していないにもかかわらず、セッションレプリケーションされているので、ブラウザ上に PersonSeri : name1 と表示されるはずです。


以上です。

[ 環境情報 ]
CentOS 6.2
Apache Tomcat 6.0.43
Java SE 6 Update 37