end0tknr's kipple - 新web写経開発

http://d.hatena.ne.jp/end0tknr/ から移転しました

Tomcat8のsession replicationによる複数host間でのセッション情報共有

初めて立て見みましたが、やってみると、ほぼ、tomcatのdocumentのまんま。という感じ

全体構成

┌PC─────┐
│┌────┐│
││browser ││
│└──┬─┘│
└───│──┘
        │
┌ HostA│──┐              ┌ HostB───┐
│┌──┴─┐│              │            │
││nginx   ││ Load Balance │            │
│└──┬─┘│ (Round Robin)│            │
│      ├─────────────┐      │
│┌──┴─┐│              │┌─┴──┐│
││tomcat8 ├─────────┤tomcat8 ││
│└──┬─┘│ Session      │└─┬──┘│
│┌──┴─┐│ Replication  │┌─┴──┐│
││java    ││(DeltaManager ││java    ││
││servlet ││ MultiCast)   ││servlet ││
│└────┘│              │└────┘│
└──────┘              └──────┘

install & setup nginx

ロードバランサとして使用しますが、まずはinstall

[HOST A]$ sudo rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
[HOST A]$ sudo yum install nginx

次に設定。 centos7ha1.a5.jp:80 で受けたrequestを centos7ha1.a5.jp:8080 , centos7ha2.a5.jp:8080 に対し、 round robinでload balanceします。 また、access.log に対し、load balance先のipを出力しています。($upstream_addr)

[HOST A]$ sudo vi /etc/nginx/nginx.conf

user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
  log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for" "$upstream_addr"';
    access_log  /var/log/nginx/access.log  main;
    sendfile        on;
    keepalive_timeout  65;

    upstream centos7ha1.a5.jp {
        server centos7ha1.a5.jp:8080;
        server centos7ha2.a5.jp:8080;
    }
    server {
        listen 80;
        location / {
            proxy_pass http://centos7ha1.a5.jp;
        }
    }
}

nginxを起動し、ブラウザでアクセスすると、 centos7ha1.a5.jp:8080 , centos7ha2.a5.jp:8080 に対し、 round robinでload balanceされていることが分かります。

[HOST A]$ sudo systemctl start nginx
[HOST A]$ sudo tail -f /var/log/nginx/access.log
[19/Sep/2019:10:04:49 +0900] "GET / HTTP/1.1" <略> "192.168.63.16:8080"
[19/Sep/2019:10:04:50 +0900] "GET / HTTP/1.1" <略> "192.168.63.17:8080"
   :

その他、今回は各ホストで名前解決する為、/etc/hostsを以下のようにしています。

[ALL HOST]$ cat /etc/hosts
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.63.16  centos7ha1.a5.jp
192.168.63.17  centos7ha2.a5.jp

install java & tomcat

[ALL HOST]$ sudo yum install java
[ALL HOST]$ java -version
openjdk version "1.8.0_222"
OpenJDK Runtime Environment (build 1.8.0_222-b10)
OpenJDK 64-Bit Server VM (build 25.222-b10, mixed mode)
[ALL HOST]$ sudo useradd tomcat
[ALL HOST]$ sudo passwd tomcat
[ALL HOST]$ sudo su - tomcat
[ALL HOST]$ mkdir local
[ALL HOST]$ cd local
[ALL HOST]$ wget https://archive.apache.org/dist/tomcat/tomcat-8/v8.0.53/bin/apache-tomcat-8.0.53.tar.gz
[ALL HOST]$ tar -xvf apache-tomcat-8.5.45.tar.gz
[ALL HOST]$ ln -s apache-tomcat-8.5.45 tomcat

config tomcat

https://tomcat.apache.org/tomcat-8.0-doc/cluster-howto.html

上記urlを参考に、DeltaManager + MultiCastな Session Replication環境を構築します。 (MultiCast以外にUniCastなSession Replicationもできるようです)

まずは、マルチキャストIPを各hostが受信するようにroutingを設定。

[ALL HOST]$ sudo vi /etc/sysconfig/network-scripts/route-enp0s8
ADDRESS0=224.0.0.0
NETMASK0=240.0.0.0
[ALL HOST]$ sudo service network restart
[ALL HOST]$ netstat -rn
[ALL HOST]$ sudo su - tomcat
[ALL HOST]$ vi /home/tomcat/local/tomcat/conf/server.xml
以下を追記。

<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
         channelSendOptions="8">
  <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="4000"
              autoBind="100"
              selectorTimeout="5000"
              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"/>
  </Channel>

  <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
         filter=""/>
  <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.ClusterSessionListener"/>
</Cluster>

動作確認用 java servletの作成

/SetSession で日時をセッションに登録し、 /GetSession でセッションにある情報を画面表示させます。(以下)

package jp.end0tknr;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;

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

@WebServlet("/SetSession")
public class SetSession extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    public SetSession() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

        HttpSession session = request.getSession();
        
        Date date = new Date(); // 今日の日付
        String strDate = date.toString();        
        session.setAttribute("sessionKey", strDate);
        
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();

        out.println("<html>");
        out.println("<head>");
        out.println("<title>Hello World!</title>");
        out.println("</head>");
        out.println("<body>");
        out.println("<h1>Hello !! Create Session !!</h1>");
        out.println("</body>");
        out.println("</html>");
        out.close();
    
    }
}
package jp.end0tknr;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/GetSession")
public class GetSession extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    public GetSession() {
        super();
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

        PrintWriter out = response.getWriter();
        out.println("<html><body>");
        out.println("sessionValue = " + request.getSession().getAttribute("sessionKey"));
        out.println("<br>");
        out.println(request.getSession().getId());
        out.println("</body></html>");        
        out.close();
    }
}

以下は、servlet用のweb.xmlの抜粋。 web.xmlに「」があると、セッションがレプリカされます。

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://xmlns.jcp.org/xml/ns/javaee">
  <display-name>SessionTest</display-name>
  <distributable />
</web-app>

後は、tomcatを起動し、動作確認して下さい。

[ALL HOST]$ sudo su - tomcat
[ALL HOST]$ cd local/tomcat
[ALL HOST]$ ./bin/startup.sh