Tomcat 8でDeltaManagerを使ってユニキャスト通信でセッションレプリケーションするときのメモ

Tomcatでセッションレプリケーションをする場合、通常はKVSなどを使ってセッション共有を行うと思いますが（AWSではDynamoDBやRedisなど）、

* Tomcatの台数が少ない（ほぼ増えない／減らない）
* レプリケーションのタイムラグが小さくないといけない

などの場合は、DeltaManagerを使って、各Tomcatサーバのメモリ上でセッション共有を行う選択肢もあります。
通常、DeltaManagerを使う場合はマルチキャスト通信でセッションイベントの通知を行い、イベント発生時にユニキャスト通信で情報の伝達を行いますが、

* AWSなどパブリッククラウド環境でマルチキャスト通信ができない
* オンプレミス環境でも、マルチキャスト通信を使いたくない（周囲の影響を受ける／影響を与える危険を避けたい、など）

といった場合は、マルチキャスト通信を使わず、ユニキャスト通信で通知も伝達も行うように設定することになります。

最近は、この方法を使うケースが少ないのかTomcat 8ベースの情報もあまりなさそうだったので、メモとして残しておきます。

---

**2018/06/27 追記：**
Tomcat 8.0 の代わりに Tomcat 8.5 を使う場合は、設定の一部を変更する必要があります（後述）。

---

# マルチキャスト通信設定の場合の例

[Tomcat 8公式サイト](http://tomcat.apache.org/tomcat-8.0-doc/cluster-howto.html)に例示されています。
Engineエレメントの中に記述（有効化）します。

（以下、公式サイトより引用）

```text:server.xml（抜粋・Engineエレメント内）
        <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>
```

---

**2018/06/27 追記：**
Tomcat 8.5 の場合、途中の
`<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>`
を、
`<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>`
に変更します（Tomcat 8.0 よりもセッションレプリケーションのパフォーマンスは若干低下します）。

---

なお、下のほうにあるDeployerタグは、実際にはEngineエレメント→Clusterエレメント内ではなくHostエレメント→Clusterエレメント内に記述します。

```text:server.xml（抜粋・Hostエレメント内）
        <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster">

          <Deployer className="org.apache.catalina.ha.deploy.FarmWarDeployer"
                    tempDir="/tmp/war-temp/"     ※テンポラリディレクトリ／絶対パス or $CATALINA_BASEからの相対パス
                    deployDir="/tmp/war-deploy/" ※warデプロイ先ディレクトリ／同上
                    watchDir="/tmp/war-listen/"  ※warの変更を監視する対象のディレクトリ（マスタのみ）／同上
                    watchEnabled="false"/>       ※warの変更を監視するかどうか（マスタ："true"／スレーブ："false"）

        </Cluster>
```

※デプロイしたwarをクラスタ内で同期させる場合に記述します。その場合、マスタサーバにデプロイしたwarがスレーブサーバに同期されます。
1台ずつ（リクエストを受け付けない状態にして）warをデプロイ後クラスタに復帰させる運用の場合は記述しません（私自身も動作は未確認です）。

必要に応じて、iptables（UDP 45564=マルチキャスト通信とTCP 4000=ユニキャスト通信を許可するため）やroute（マルチキャスト通信を流すインターフェースを指定するため）の設定を行い、Tomcatを起動すると、ログ（catalina.out）に以下のような行が記録されます。

```text:catalina.out（抜粋）
20-Jan-2017 09:29:03.614 INFO [main] org.apache.catalina.ha.tcp.SimpleTcpCluster.startInternal Cluster is about to start
20-Jan-2017 09:29:03.618 INFO [main] org.apache.catalina.tribes.transport.ReceiverBase.bind Receiver Server Socket bound to:/192.168.10.11:4000
20-Jan-2017 09:29:03.630 INFO [main] org.apache.catalina.tribes.membership.McastServiceImpl.setupSocket Setting cluster mcast soTimeout to 500
20-Jan-2017 09:29:03.633 INFO [main] org.apache.catalina.tribes.membership.McastServiceImpl.waitForMembers Sleeping for 1000 milliseconds to establish cluster membership, start level:4
20-Jan-2017 09:29:04.101 INFO [Membership-MemberAdded.] org.apache.catalina.ha.tcp.SimpleTcpCluster.memberAdded Replication member added:org.apache.catalina.tribes.membership.MemberImpl[tcp://{192, 168, 10, 12}:4000,{192, 168, 10, 12},4000, alive=63266064, securePort=-1, UDP Port=-1, id={xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx xx }, payload={}, command={}, domain={}, ]
```

※192.168.10.11が自サーバ、192.168.10.12が相手サーバです。

# ユニキャスト通信設定のポイント

* Clusterタグで「channelStartOptions」を指定し（3）、マルチキャスト通信を無効化する
* Interceptorの記述順に注意する
* 自サーバ側のMemberタグのport設定と、対向サーバ側のReceiverタグのport設定の番号を合わせる
* 自サーバはMemberタグで記述しない（記述する場合はLocalMemberタグで）

以下、設定例です。

| Tomcatホスト名 | IPアドレス | Receiverポート番号 |
|:-:|:-:|:-:|
| web1.xxx.local | 192.168.10.11 | 4567 (TCP) |
| web2.xxx.local | 192.168.10.12 | 4568 (TCP) |

```text:web1.xxx.localのserver.xml（抜粋・Engineエレメント内）
      <Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"
               channelSendOptions="8" channelStartOptions = "3">

        <Manager className="org.apache.catalina.ha.session.DeltaManager"
                 expireSessionsOnShutdown="false"
                 notifyListenersOnReplication="true"/>

        <Channel className="org.apache.catalina.tribes.group.GroupChannel">
          <Interceptor className="org.apache.catalina.tribes.group.interceptors.TcpPingInterceptor"/>
          <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.StaticMembershipInterceptor">
            <LocalMember className="org.apache.catalina.tribes.membership.StaticMember"
              domain="web-cluster"
              uniqueId="{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0}"/>
            <Member className="org.apache.catalina.tribes.membership.StaticMember"
              port="4568"
              securePort="-1"
              host="web2.xxx.local"
              domain="web-cluster"
              uniqueId="{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}"/>
          </Interceptor>
          <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                    address="auto"
                    port="4567"
                    autoBind="100"
                    selectorTimeout="5000"
                    maxThreads="6"/>

          <Sender className="org.apache.catalina.tribes.transport.ReplicationTransmitter">
            <Transport className="org.apache.catalina.tribes.transport.nio.PooledParallelSender"/>
          </Sender>
        </Channel>

        <Valve className="org.apache.catalina.ha.tcp.ReplicationValve"
               filter=""/>
        <Valve className="org.apache.catalina.ha.session.JvmRouteBinderValve"/>

        <ClusterListener className="org.apache.catalina.ha.session.ClusterSessionListener"/>
      </Cluster>
```

---

**2018/06/27 追記：**
Tomcat 8.5 の場合、マルチキャスト通信の設定例と同様に、途中の
`<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatch15Interceptor"/>`
を、
`<Interceptor className="org.apache.catalina.tribes.group.interceptors.MessageDispatchInterceptor"/>`
に変更します。

---

```text:web2.xxx.localのserver.xml（web1.xxx.localとの差分＝Member・Receiver部分のみ抜粋）
            <LocalMember className="org.apache.catalina.tribes.membership.StaticMember"
              domain="web-cluster"
              uniqueId="{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}"/>
            <Member className="org.apache.catalina.tribes.membership.StaticMember"
              port="4567"
              securePort="-1"
              host="web1.xxx.local"
              domain="web-cluster"
              uniqueId="{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0}"/>
          </Interceptor>
          <Receiver className="org.apache.catalina.tribes.transport.nio.NioReceiver"
                    address="auto"
                    port="4568"
                    autoBind="100"
                    selectorTimeout="5000"
                    maxThreads="6"/>
```

※Deployerタグは、マルチキャストの場合と同様、Hostエレメント→Clusterエレメント内に記述します。

LocalMemberタグはなくても大丈夫なはずです。
Interceptorは、TcpPingInterceptor → TcpFailureDetector → StaticMembershipInterceptorの順になるように記述する必要があるようです。
また、↑の例では、設定の対応関係を示すために、web1.xxx.localとweb2.xxx.localでReceiverポート番号をあえて別のものにしていますが、1対1であれば同じにしてしまっても構いません（相手の識別は行うので、1対1でなくてもおそらくOK。送信側はクライアントポートの範囲の番号を使います。また、autoBindの指定範囲でポート番号を順にスキャンするため、Member側の指定ポート番号が少し前にずれていても大丈夫です）。

AWSならVPCのセキュリティグループ設定、オンプレミス環境ならiptablesの設定で当該ポート番号（TCPユニキャスト）の通信を許可した上で、Tomcatを起動すると、ログ（catalina.out）に以下のような行が記録されます。

```text:web1.xxx.localのcatalina.out（抜粋）
20-Jan-2017 09:08:44.549 INFO [main] org.apache.catalina.ha.tcp.SimpleTcpCluster.startInternal Cluster is about to start
20-Jan-2017 09:08:44.564 INFO [main] org.apache.catalina.tribes.transport.ReceiverBase.bind Receiver Server Socket bound to:/192.168.10.11:4567
20-Jan-2017 09:08:44.625 INFO [Thread-5] org.apache.catalina.ha.tcp.SimpleTcpCluster.memberAdded Replication member added:org.apache.catalina.tribes.membership.StaticMember[tcp://web2.xxx.local:4568,web2.xxx.local,4568, alive=0, securePort=-1, UDP Port=-1, id={0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 }, payload={}, command={}, domain={xx xx xx xx xx xx xx xx xx ...(xx)}, ]
```

# その他

Tomcat 7までと同様、以下についても気を付けます。

* アプリケーションのweb.xmlに\<distributable/\>タグを付ける（\<web-app\>～\</web-app\>内）
* アプリケーションコードでセッションオブジェクトの内容を更新するときは setAttribute する

※DeltaManagerではなくBackupManagerを使う場合は、以下の記事もご覧ください。

**[Tomcat 8でBackupManagerを使ってユニキャスト通信でセッションレプリケーション](http://qiita.com/hmatsu47/items/98aecba0a8c34bcf77e7)**