Tomcat 8でBackupManagerを使ってユニキャスト通信でセッションレプリケーション

**[Tomcat 8でDeltaManagerを使ってユニキャスト通信でセッションレプリケーションするときのメモ](http://qiita.com/hmatsu47/items/ba6a17c86ad3a9f94281)**ではDeltaManagerでのセッションレプリケーション方法を扱いましたが、こちらはBackupManagerでのセッションレプリケーションです。

結論から言うと、ユニキャスト通信でも、DeltaManagerと同様にBackupManagerでセッションレプリケーション可能です。

※ある意味時代遅れの方法なので、DeltaManagerの記事はそれほど読まれないかと思っていましたが、思っていたよりも読まれることが多いようなので（レガシーなアプリケーションのクラウド移行を進めている方が多いのでしょうか？）、BackupManagerについても取り上げることにしました。

# DeltaManager用設定からの変更箇所

**[Tomcat 8でDeltaManagerを使ってユニキャスト通信でセッションレプリケーションするときのメモ](http://qiita.com/hmatsu47/items/ba6a17c86ad3a9f94281)**の設定のうち、server.xmlのManagerエレメント部分を書き換えます。

```text:server.xml（Managerエレメント部分・変更前）
          <Manager className="org.apache.catalina.ha.session.DeltaManager"
                   expireSessionsOnShutdown="false"
                   notifyListenersOnReplication="true"/>
```

```text:server.xml（Managerエレメント部分・変更後）
          <Manager className="org.apache.catalina.ha.session.BackupManager"
                   notifyListenersOnReplication="true"/>
```

BackupManagerにはexpireSessionsOnShutdown属性がないので削除します。
なお、DeltaManagerの**expireSessionsOnShutdown="false"**、両Managerの**notifyListenersOnReplication="true"**はデフォルト値通りなので記述しなくても構いません。

他の部分は元のまま変更しなくてOKです。

# DeltaManagerとBackupManagerの違い

詳細については、以下の書籍をご覧ください。

**[詳解 Tomcat](https://www.oreilly.co.jp/books/9784873117058/)（オライリー・ジャパン）**
※5章 5.2.3でセッションレプリケーションについて解説されています。

DeltaManagerはクラスタ中の全ノード（サーバ）間でセッション情報（ID・オブジェクトなど）をAll-to-Allでレプリケートします。
クラスタ内のノードが2台の場合、例えば各ノードをAとBと表現するならば、

 * A→B
 * B→A

の2通りの通信しか行われませんが、ノードが3台（A・B・C）の場合、

 * A→B
 * A→C
 * B→A
 * B→C
 * C→A
 * C→B

の6通りに増えます。4台の場合は12通り、5台の場合は20通りです。
マルチキャスト通信を使う場合はまだいいのですが、ユニキャスト通信ではノードが多くなるとレプリケーション負荷が高くなることがイメージできると思います。

BackupManagerは、セッションの生成・変更・削除そのものについてはAll-to-Allで通信しますが、セッションに保存されたオブジェクトについては、

 * セッションの生成を行った（受け付けた）ノード（Primaryノード）
 * Primary以外から選ばれた1つのノード（Backupノード）

の2台の間で保持（レプリケート）するだけ、という形でレプリケーション負荷を抑えています。
これら以外のノード（Proxyノード）では、セッションIDと、当該セッションを保持するPrimary／Backupノードの情報のみを保持します。

ノード障害があった場合、

 * そのノードがPrimaryノードになっているセッションについて、Backupノードが**一旦**新たなPrimaryノードとなり、Proxyノードの中から新たなBackupノードが選出されます。
 * そのノードがBackupノードになっているセッションについて、Proxyノードの中から新たなBackupノードが選出されます。
 * 新たなPrimary／Backupノードが対象のセッションオブジェクトを保持していない場合は、保持しているノードからレプリケートします。

但し、Tomcatより前段（クライアント側）にあるロードバランサーが、必ずしも新Primaryノードにリクエストを振り分けてくれるとは限りません。ロードバランサーが違うノードにリクエストを振り分けた場合は、振り分けられたノードがPrimaryノード、（振り分けられたノードが新Backupノードであれば）それ以外のノードがBackupノードになるように再構成が行われます（上で**「一旦」**と表現したのはこのためです）。

なお、ノード障害ではなく、メンテナンスのためにロードバランサーで特定のノードをクラスタから除外することもあると思います。ノード障害が検出されていない状態でPrimaryノードではないノードにリクエストが振り分けられた場合にも、新たなPrimaryノード（＝リクエストが振り分けられたノード）／Backupノードが選出されます。
ノードをシャットダウンした場合は、そのノードがPrimary／Backupとなっているセッションオブジェクトについて、新Primary／Backupノードを選出するとともにレプリケートします。

# 使用上の注意

BackupManagerの場合、平時にはDeltaManagerよりセッションレプリケーション負荷を抑えられますが、障害などでクラスタ内のノード構成が変化すると、そのタイミングでレプリケーションが行われるため、かえって（瞬間的な）負荷が高まる可能性があります。
また、セッションオブジェクトを2台のノードのみで保持するため、AWSのAZ障害時には（同じAZにある2台で保持していることが原因で）一部のセッションオブジェクトが失われる可能性があります。

それから、DeltaManager同様、ユニキャスト通信の場合はクラスタ設定（server.xml内）にノード情報をあらかじめ列記しておかないといけませんので、AWSのAuto Scalingのような仕組みとは相性が悪いです（1台ずつ個別にスケーリングを指定して「Time-based Scaling」として使うことはできると思いますが）。

ちなみに、これはTomcatやDeltaManager／BackupManagerに限った話ではないのですが、AWSのALBはDraining時の処理に問題を抱えているため（Auto Scalingの縮退時に問題になります）、セッションレプリケーションしないといけないような、信頼性を求められるWebアプリケーションとは相性が悪い点にも注意が必要です（CLBでは同じ問題は発生しないらしいですが）。

なお、Tomcatのサービスを停止する際、前述の通りそのノードがPrimary／Backupとなっているセッションオブジェクトについて、新Primary／Backupノードを選出するとともにレプリケートします。その関係で、クラスタ内に3台以上のノードが残る場合、シャットダウンに長い時間が掛かり、Linuxの起動スクリプトの処理がシャットダウンの途中でタイムアウトしてしまうことがあります。