Apache Tomcat を複数台のクラスタ構成にし、Tomcat 間でセッション情報を共有するセッションレプリケーション機能を利用した開発プロジェクトをいくつか経験してきましたが、それらのプロジェクト内において誰かしら同じ内容でハマるので、本稿では今まで経験してきた”ハマりどころ”を共有し、今後のプロジェクトに役立てたいと思います。
本稿では、Tomcat にデプロイされるアプリケーションの観点と、Tomcat そのものの設定の観点の両方について、今までにハマったポイントを解説します。
アプリケーションの web.xml に distributable の指定を忘れる
Java の WEB アプリケーションでは、アプリケーション内に web.xml を持つ必要がありますが、セッションレプリケーション機能を利用するアプリケーションを Tomcat にデプロイする場合には distributableタグ を web.xml に記述する必要があります。
プログラマが個別に自分のパソコン上の Tomcat で開発している間はセッションレプリケーションを利用しないことが多いため distributable の記述が必要なく、結合環境や本番環境などにデプロイした際に初めて「あれ? セッション情報が取得できない! 何で!?」と大騒ぎするケースが多いです。
複数の Tomcat 間でセッションレプリケーションする構成になっている Tomcat に distributable の指定をせずにアプリケーションをデプロイした場合、どのようになってしまうかを知っておくことがトラブルを切り分けるのに役に立ちます。 例えば2台の Tomcat 間でセッションレプリケーションを行っていると仮定し、それぞれを「1号機」「2号機」と呼ぶとしましょう。 初回アクセスが1号機に割り当てられてその処理でセッションを生成したとして、次のアクセスが2号機に割り当てられた場合、初回アクセス時に発行したはずのセッション情報が取得できません。
よって「セッションが取得できない!」という騒ぎになったら、まずは distributable が指定されているかを疑いましょう。 web.xml には次のように記述します。
<?xml version="1.0"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" ...>
(略)
<distributable />
</web-app>
セッションから取得した値を操作した後に setAttribute() をしていない
とても単純な言い方をすると「セッションの値を他の Tomcat に共有するタイミングは setAttribute メソッドを呼び出した時である。」ということを忘れてはいけません。 いや「忘れている」というよりは、「知らない」ことの方が経験上は多いです。
setAttribute しないと他の Tomcat にセッション値の情報が共有されませんので、次のようなプログラムでは更新した値が共有されません。
Member member = (Member)session.getAttribute("member");
member.name = "Tom";
上のサンプルでは、名前を “Tom” に変更したつもりでしょうが、オブジェクトの値を変更した後に setAttribute メソッドを呼び出していない為、他の Tomcat にはセッション値が共有されません。 ハマりどころとしては、プログラムを実行したマシン上では値が変更されている点です。 次のアクセスでまた同じ Tomcat にアクセスが割り当てられれば情報が変更されたように見えますが、別の Tomcat にアクセスが割り当てられた場合には情報が変更されていません。 「同じ操作をしているのに処理がうまくいく場合と、いかない場合がある!」と騒ぎになります。
もし上記のような変更を行うのであれば、次のようにプログラムするのが正しいです。
Member member = (Member)session.getAttribute("member");
member.name = "Tom";
session.setAttribute("member", member);
この”ハマりどころ”も、Tomcat を複数台で構成していないと不具合が分かりづらい為、簡単にプログラム工程や単体試験工程をすり抜けてしまいます。 (※なるべく早い段階で不具合として浮かび上がらせる為の工夫ができれば是非行いたいものです)
レプリケーションのメンバーの指定に自分自身が含まれてしまっている
ここからは Tomcat のセッションレプリケーションの設定に関するハマりどころです。
Tomcat にセッションレプリケーションの設定をする際には、セッション値を共有するメンバーを登録しますが、そのメンバーには自分を除くメンバーを指定します。 自分自身をメンバーとして登録してはなりません。 静的メンバーを追加する場合は次のように記述します。
<Interceptor className="org.apache.catalina.tribes.group.interceptors.StaticMembershipInterceptor">
<Member className="org.apache.catalina.tribes.membership.StaticMember" host="192.168.1.xx" ...>
</Interceptor>
この際に、Member に自分自身の情報も記述してしまうと、セッション情報を共有した際におかしな挙動をしてしまいます。 あくまでも、自分を除いたセッションを共有する他のメンバーを記述することが大事です。
ちなみにここに自分自身を追加すると、セッションを共有しようとしたタイミングでタイムアウトが発生したりしました。 タイムアウトが発生した理由としては、自分が他のメンバーにセッション情報を投げたにも関わらず、メンバーとして記述された自分自身がその通知を受け取らない(なぜなら自分自身は情報を投げて応答を待っている状態だから)だと考えられます。
番外編 : invalidate() が効かない
番外編として Tomcat 自体のバグを共有します (筆者はコレにずいぶん悩まされました)。 Tomcat 8.0.8 および Tomcat 7.0.54 において、プログラム中で HttpSession を invalidate() しても、セッションが破棄されない不具合がありました。 この不具合は公式のバグ管理システムで報告されています。 (Bug 56578 – session.invalidate does not work on cluster enabled webapps) Tomcat 8.0.9 および Tomcat 7.0.55 でこのバグが修正されていますので、何にしろ最新の Tomcat を利用するに越した事はありません。
おわりに
以上、Tomcatクラスタ構成でセッションレプリケーション機能を利用する際のハマりどころについて記述しました。 現場で他にも何か問題があった際には、随時更新しようと思います。