シンクロ・フードの越森です。
今回は、Tomcatのクラスタリング環境でのセッションレプリケーションについてお話したいと思います。
弊社ではAWS移行したことをきっかけにTomcatのセッションレプリケーションを見直すことになったのですが、中々これといったやり方を決めることができず試行錯誤したため、皆さんの参考になればと思い公開します。
※現段階でも、運用でカバーしている問題は残っていますが...。
何故セッションレプリケーションが必要なのか?
セッションレプリケーションとはTomcatなどのアプリケーションサーバーを複数台並列で稼働させるクラスタリング環境において、各アプリケーションサーバー上のセッションを共有する方法です。
セッションを共有することで、アプリケーションサーバーが1台落ちた場合でもユーザーには影響を与えず、別のアプリケーションサーバーでサイトを運用することができます。
Tomcatのセッションレプリケーションの種類
Tomcatは3種類のセッションレプリケーションの手段を提供しています。
- DeltaManager
- BackupManager
- PersistenceManager
DeltaManager
基本的にマルチキャストで構築されたクラスタリング環境において、セッションレプリケーションするために利用するSessionManagerです。
Tomcatの公式ドキュメントのはじめに説明されている代表的なSessionManagerです。
クラスタリング環境内の全てのサーバーでセッション情報をほぼリアルタイムで共有します。
クラスタリング環境内の全サーバーでセッション情報を共有するため、セッション情報の変更を反映させるための負荷が高いこと、1台で全台数分のセッション情報を持つことによりメモリ使用量が大きくなることから、小規模なクラスタリング環境向けのセッションレプリケーション方法です。
BackupManager
DeletaManagerと同様、マルチキャストで構築されたクラスタリング環境において、セッションレプリケーションするために利用するSessionManagerです。
DeltaManagerとは異なり、リクエストを受け付けているサーバー(Primary)と、クラスタリング環境内にある別の1台のサーバー(Backup)との2台でしかセッション情報を格納しないため、大規模なクラスタリング環境でも利用することができるセッションレプリケーション方法です。
PersistenceManager
Tomcatが持つセッション情報をファイルやデータベースに格納して利用するSessionManagerです。
各Tomcatのサーバーにセッション情報の格納先を設定することでクラスタリング環境を構築することができます。(マルチキャストでグルーピングする必要はありません)
PersistenceManagerではTomcatのメモリ上にセッション情報があれば優先的に利用し、メモリ上にない場合にセッション情報の格納先から取得します。
そのため、Tomcatの前に配置するロードバランサーではスティッキーセッションを付加する必要があります。
注意する点としては、セッション情報の格納先へ反映するタイミングと反映するデータ量です。
反映するタイミングについては、設定で変更はできますが一番短いタイミングでも格納先に反映するのは1秒ごとになるという点です。
反映するデータ量については、反映するタイミングでTomcatが持っている全てのセッション情報を格納先に反映することになりますので、セッション情報に大きな情報を入れているとネットワーク負荷、CPU負荷、I/O負荷が大きくなってしまいます。
オンプレミス時代のセッションレプリケーション
ここからは、シンクロ・フードで利用しているセッションレプリケーションについてお話しします。
初めてセッションレプリケーションを導入したのはオンプレミス時代で、この時はTomcatの公式ドキュメントで紹介されている最初の方法を採用しました。
クラスタリングはマルチキャストで構成し、セッションレプリケーションについては、DeltaManagerによりすべてのノードのセッションをリアルタイムに共有するように設定しました。
設定方法についても公式ドキュメントの設定通りに行うことでセッションレプリケーションを実現できました。
[Apache Tomcat 6.0 Clustering/Session Replication HOW-TO]
https://tomcat.apache.org/tomcat-6.0-doc/cluster-howto.html
AWS移行直後のセッションレプリケーション
2014年からAWSへの移行を検討し始めましたが、検討の過程でAWS環境ではマルチキャストが使えないということが分かり、クラスタリング/セッションレプリケーションの方法についても見直しを行いました。
このタイミングで他の方法に変更することも検討しましたが、簡単には実現することができず調査に時間がかかりそうなことから、他作業との優先度を考えて、マルチキャスト設定部分をユニキャストで1台ずつ設定することで、オンプレミス時代と同じセッションレプリケーション方法で対応しました。
具体的にはマルチキャスト設定を行っているMembershipの記述を削除して、以下の設定を追加しました。
<Interceptor className="org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor"> <Member className="org.apache.catalina.tribes.membership.StaticMember" port="5000" securePort="-1" host="172.16.1.100" domain="product-cluster" uniqueId="{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15}" /> </Interceptor>
[Apache Tomcat 7.0 Clustering/Session Replication HOW-TO]
https://tomcat.apache.org/tomcat-7.0-doc/config/cluster-interceptor.html
自動デプロイ対応後のセッションレプリケーション
AWS移行が終わりインフラの次に取り組むテーマを検討した結果、AWSに移行したことで自動化周りの施策が色々できるようになったので、自動デプロイに取り組むことになりました。
自動化という方向性で進めるとした場合に現在のユニキャストによるサーバー1台ごとの設定は、今後障害になるということでセッションレプリケーションの方法についてもあわせて変更することにしました。
実はそれ以外にもデプロイで1台ずつサーバーを落とすタイミングでセッションが維持できない場合が発生していたというのも理由の1つであったりします。
色々調査したところ、Amazonが公開しているライブラリを使ったDynamoDBによるセッションレプリケーションやRedisを使ったセッションレプリケーションなどがありましたが、やはりTomcatに含まれている点を重視してPersistenceManagerを採用することにしました。
シンクロ・フードのPersistenceManagerによるセッションレプリケーション
具体的にはEC2インスタンス上のMySQLにセッション格納用のテーブルを作成し、Tomcatからそのテーブルに対して読み書きするように設定しています。
セッション格納用のテーブルについては読み書きの頻度が高くI/Oの負荷を考慮して、MEMORYストレージエンジンを利用して、メモリ上にセッション情報を格納しています。
以下はMySQLとTomcatの設定になります。
- MySQLのテーブル定義
CREATE DATABASE tomcat_session DEFAULT CHARACTER SET utf8; USE tomcat_session; CREATE TABLE sessions ( session_id varchar(100) NOT NULL, valid_session char(1) NOT NULL, max_inactive int(11) NOT NULL, last_access bigint(20) NOT NULL, app_name varchar(255) DEFAULT NULL, session_data varbinary(42000), PRIMARY KEY (session_id), KEY kapp_name (app_name) ) ENGINE=MEMORY DEFAULT CHARSET=utf8;
- Tomcatの設定(context.xml)
<Manager className="org.apache.catalina.session.PersistentManager" saveOnRestart="true" processExpiresFrequency="1" maxIdleBackup="0"> <Store className="org.apache.catalina.session.JDBCStore" connectionURL="jdbc:mysql://[IPアドレス]:3306/tomcat_session?useUnicode=true&characterEncoding=UTF-8&user=[ユーザー名]&password=[パスワード]" driverName="com.mysql.jdbc.Driver" sessionAppCol="app_name" sessionDataCol="session_data" sessionIdCol="session_id" sessionLastAccessedCol="last_access" sessionMaxInactiveCol="max_inactive" sessionTable="sessions" sessionValidCol="valid_session" /> </Manager>
現在はPersistenceManagerを利用した方法で運用はできていますが、1点だけ問題が発生するケースがあるため、その問題について説明します。
PersistenceMangerの運用上の問題
Tomcatのフェイルオーバーやロードバランサーの振り分け変更などにより、同じユーザーのセッションが複数台のTomcatのメモリ上に存在する場合に、Tomcatの再起動やセッションのタイムアウトなどでメモリのセッション情報がクリアされずに、フェイルオーバーなどでもう1度別のTomcatのサーバーにアクセスすることになった場合に、セッション情報が過去の情報に戻ってしまうという問題が発生します。
本問題については、谷本さんの以下の記事が詳しいのでご確認ください。
[DynamoDBでTomcatのセッション共有をするとハマるかも]
http://d.hatena.ne.jp/cero-t/20151019/1445262219
実際に問題が発生する具体的なオペレーションのケースとしては、以下の2つが考えられます。
ここでは、Tomcat1, Tomcat2の2台構成で運用していて、その前にロードバランサー(LB)があることを前提とします。
- LBでTomcat1へのアクセス遮断、復旧、遮断
- Tomcat2ダウンによるフェイルオーバーが2回連続で発生
弊社ではどちらのケースにも対応できるように別サーバーへのアクセスが発生するような状況になった場合には、全てのサーバーのTomcatを再起動してメモリ上のセッション情報をクリアするようにしています。
最後に
以上で簡単な説明になりますが、弊社でのTomcatのクラスタリング環境でのセッションレプリケーションの設定をご紹介しました。
Tomcatのセッションレプリケーションについて少しでも参考になれば幸いです。
尚、シンクロ・フードではエンジニアを大募集しています!
こうしたインフラ周りの改善以外にも色々なことをやっていますので、少しでもご興味があれば、気軽にお話しする場を設けますので、以下よりご連絡ください!