話題の記事

Amazon DynamoDBによるTomcatセッション永続化とフェイルオーバー

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Tomcatのセッション管理

Tomcatでクラスター構成にする場合、課題となるのがセッション管理です。ロードバランサーでセッションIDを保持することで、毎回同じサーバーにリクエストが向かうのであれば問題なさそうに見えますが、あるサーバーがダウンしてしまうとセッション情報が消えてしまいます。これを解決する方法として、データベースにセッション情報を保持する方法が一般的ですが、データベースへ負荷が掛かりますし、データベースが落ちたら困ります。何かもっと良い方法は無いかと皆さん思っていたはずです。そこで、AWSですよねー。AWSでは、ElastiCacheやDynamoDBがサービスとして提供されています。ここで、永続化をしっかりやってくれるのはDynamoDBであり、AWS SDK for Javaでの登場が待たれていたわけです。そして、このたび出てきました!

スティッキーセッション

ロードバランサーがセッションIDをキーにしてリクエストするべきインスタンスを振り分けます。方式としては、アプリケーションサーバー発行のIDまたは、ロードバランサー発行のIDを用います。Tomcatであればjsessionidが使われますね。

screenshot 2013-10-14 17.25.56

非スティッキーセッション

クライアントからリクエストする度に同じインスタンスに向かいません。ここではラウンドロビンと言いたいところですが、厳密には単純な分散ではないため"非"としました。

tomcat-session-000

【ELB】負荷分散とEC2へのリクエスト数との関係について調べてみた

なぜDynamoDBがいいの?

ここでは、なぜDynamoDBがセッション管理に良いのか(このあと前提が覆りますがこのままお楽しみくださいw)確認したいと思います。まずは、非スティッキーセッションの場合のセッションの考え方を図にしました。毎回どのサーバーにリクエストが向かうか分かりませんから、セッション管理を行うことはできません。

tomcat-session-002

ロードバランサー配下のインスタンスでセッション管理を行うために、スティッキーセッションを行った場合の図が以下です。この方法のリスクとしては、セッション管理しているサーバーに障害が起こった場合、セッション情報が消えてしまうことです。セッション情報は主にログイン情報や買い物かごの中身など一時的に持っておくデータですから最悪消えてしまっても最初から処理をすれば大きな問題にはなりませんが、利用者としては買い物かごの中身が消えてしまうことは良い気分ではありません。

tomcat-session-003

ということで、次に考えるのは、各WEBサーバーにセッション情報を置かずにデータベースを利用して保存しておくことです。これによって、EC2で構築したWEBサーバーに障害が発生してもセッション情報が消えることはありません。また、スケールアウト/スケールインの際にも有効です。良いことづくしですが、リスクが無いわけではありません。データベースサーバーに障害が発生した場合はセッション情報が復帰するまで全体に影響が出てしまいます。RDSであれば、レプリケーションからフェイルオーバーして復帰しますが、WEBサーバーがスケールアウトした際にデータベースへの接続数が倍々で増えてしまって負荷になってしまうかもしれませんね。

tomcat-session-004

ということで、もっと良い方法ないかなーということで、ElastiCacheではなくDynamoDBです。ElastiCacheはインメモリで高速に動きますので、セッション管理には最適なような気がしますが、永続的なデータを扱うモノではありませんので悩みます。「高速」「永続化」「クラスタ」「サービス」「Tomcatで指定できる」を兼ね備えたもの・・・・DynamoDBです!

tomcat-session-005

DynamoDB Session Managerのセットアップ

それでは、Tomcatとセッション管理のマネージャをセットアップしてみましょう。

$ sudo yum update -y
$ sudo yum install tomcat7*
$ wget http://sdk-for-java.amazonwebservices.com/latest/aws-java-sdk.zip
$ unzip aws-java-sdk.zip
$ cd aws-java-sdk-1.5.7/lib/
$ sudo cp aws-java-sdk-1.5.7.jar /usr/share/tomcat7/lib/
$ wget https://github.com/aws/aws-dynamodb-session-tomcat/releases/download/v1.0.0/AmazonDynamoDBSessionManagerForTomcat-1.0.0.jar
$ sudo cp AmazonDynamoDBSessionManagerForTomcat-1.0.0.jar /usr/share/tomcat7/lib/

ライブラリを読み込み、セッションを保存するDynamoDBのテーブルを指定します。

$ sudo vi /usr/share/tomcat7/conf/context.xml

<?xml version="1.0" encoding="UTF-8"?>
<Context>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <Manager className="com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager"
        regionId="ap-northeast-1"
        endpoint="dynamodb.ap-northeast-1.amazonaws.com"
        createIfNotExist="true" />
</Context>

Tomcatをスタートしたら、セッションを使うURLにアクセスしてデータが保存されているか確認しましょう。

$ sudo service tomcat7 start

ブウラザからセッション管理がクラスタされているか確認

ELBを新規に作成して非スティッキーセッションにして動作確認します。ELB配下にセットアップ済みのEC2インスタンスを配備してIn Serviceになっていることを確認してからブラウザで動作確認してください。

セッション管理されている様子を確認するJSPは以下のコードです。

<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
<%@ page import="java.net.*" %>
<%
Integer counter = (Integer)session.getAttribute("counter");

if(counter == null){
	counter = new Integer(1);
}else{
	counter = new Integer(counter.intValue()+1);
}
session.setAttribute("counter", counter);
%>
<%= session.getAttribute("counter") %><br>
from Cluster 1<br>
<%= InetAddress.getLocalHost() %><br>
<%= session.getId() %><br>

ロードバランサーのアドレスを何度もリロードするとカウントアップしていきます。

screenshot 2013-10-14 17.36.54

しかし、、、、セッションIDは同じでうまく管理されているはずなのに値が上がったり下がったりしています。なぜ???

screenshot 2013-10-14 17.37.04

ここからが長い調査の始まりでした。。。。

Tomcatアーキテクチャーの仕組み

screenshot 2013-10-14 18.29.54

Tomcatは階層化されたコンテナによって管理されています。{TOMCAT_HOME}/conf/server.xmlの階層と同じです。Server > Service > Engine > Host の順番に設定されています。Hostの下にContextが設定されます。DynamoDBSessionManagerについては、このContext内のManager実装クラスとして設定していました。

さて、このContextの設定パラメータを眺めてみますと、気になる属性名があります。backgroundProcessorDelayです。子コンテナの処理を遅延させる値と書いてあります。特に設定しない場合には、親コンテナの設定を引き継ぐと書いてあります。

screenshot 2013-10-14 18.02.36

親であるHostコンテナにも同様に書いてありますので、その親であるEngineコンテナを見てみました。デフォルトで、じゅ、10秒遅延!これでラウンドロビン振り分けしたセッション管理の値がリアルタイムに同期されていないことが分かりましたね。

screenshot 2013-10-14 18.01.52

永続化を行うPersistentManagerを調べる

Tomcatがセッションの永続化を行う際に出てくるクラスがPersistentManagerクラスです。DynamoDBでセッション管理を行うDynamoDBSessionManagerクラスは、PersistentManagerBaseクラスを継承しています。とうことで、多くの設定についてPersistentManagerと同じになります。Tomcatのドキュメントを読むと以下のような属性の設定について記述がありました。

screenshot 2013-10-14 18.33.27

maxIdleBackupの属性値の説明では、セッションを永続化するときに必要な秒数が書いてあります。

maxIdleBackup + processExpiresFrequency * engine.backgroundProcessorDelay

processExpiresFrequencyの説明も読んでみます。セッション切れ頻度を表しているようです。デフォルト値が6で最小値が1だそうです。

screenshot 2013-10-14 18.56.02

これらから、セッション永続化を行うために遅延する時間が少なくともデフォルトで60秒以上であることが分かります。また、最小値に設定をしたとしても、1秒以上になってしまうため、ユーザーリクエストがラウンドロビンの状態で連打した場合にはセッション情報の管理に不整合が起きてしまいます。

以上により、Tomcatでセッションクラスターする時はスティッキーセッションにしてください。また、saveOnRestartをtrueにすることで、サーバーに障害が発生した際にセッション情報を即座に永続化してくれます。これにより、ロードバランサーが他のサーバーに振り分け直した場合にも、永続化された場所からセッション情報を復元するため、ユーザーがログアウトしてしまうことがありません。

Tomcatクラスタでセッションをフェイルオーバーする

さて、いろいろと調べてきましたので、ここで実際の動作を確認したいと思います。ELBのアドレスをブラウザで表示すると、Cluster1と表示されます。リロードすると表示がカウントアップされていきます。スティッキーセッションですので同じサーバーにリクエストが振られ続けています。

$ sudo vi /usr/share/tomcat7/conf/context.xml
 
<?xml version="1.0" encoding="UTF-8"?>
<Context
    backgroundProcessorDelay="1"
>
    <WatchedResource>WEB-INF/web.xml</WatchedResource>
    <Manager className="com.amazonaws.services.dynamodb.sessionmanager.DynamoDBSessionManager"
        regionId="ap-northeast-1"
        endpoint="dynamodb.ap-northeast-1.amazonaws.com"
        createIfNotExist="true" 
        saveOnRestart="true"
        processExpiresFrequency="3"
    />
</Context>

screenshot 2013-10-14 22.30.27

ブラウザの結果です。

screenshot 2013-10-14 22.49.41

次にCluster1のサーバーのTomcatを止めてみます。すると、Cluster1のTomcatはDynamoDBにセッションデータを書き込みます。

screenshot 2013-10-14 22.30.40

ブラウザから改めてアクセスをすると、新しいサーバーは見たことがないセッションIDということでDynamoDBに確認に行きます。そして該当するデータがあればロードします。これにより、ブラウザでは同じセッションIDで、かつ、データを引き継いでいることを確認できます。

screenshot 2013-10-14 22.50.19

良い感じですね。Auto Scalingにおけるスケールインをした場合にもセッション情報を引き継げるはずです。

AWS Elastic BeanstalkでDynamoDBSessionManagerを使う

次にElastic Beanstalkを使ってセットアップしましょう。Beanstalkには拡張ポイントがありまして、設定ファイルを記述することができます。最新のEclipseプラグインにアップデートすることでDynamoDBによるセッション管理をある程度自動化してくれます。Eclipseから新規でAWS Java Web Projectを作成します。

screenshot 2013-10-15 0.40.04

このままでは.ebextensionsフォルダが見えませんので、Customize Viewをします。「.*」のチェックを外します。

screenshot 2013-10-15 1.11.52

.ebextensionsフォルダが見えましたね。

screenshot 2013-10-15 1.18.25

context.xmlは以前作成したものをコピペしてもらって構いません。アクセスキーやシークレットキーはPoserUserなIAMRoleが設定されているのであれば不要です。

*.configファイルは、Beanstalkセットアップ時に実行されるYAMLです。server-update.configというファイルが出来ていますね。ここでライブラリーのコピーと設定のコピーを行っています。

container_commands:
    copy-libraries:
        command: "cp WEB-INF/.ebextensions/*.jar /usr/share/tomcat7/lib/"
    replace-context:
        command: "cp WEB-INF/.ebextensions/context.xml /etc/tomcat7/context.xml"

デプロイをした後にスティッキーセッションの指定を忘れずに。ということで簡単にセットアップできましたね。

まとめ

今回は、Tomcatのセッションデータを永続化するためにPersistentManager実装であるDynamoDBSessionManagerを用いました。また、この機能を用いることで、ロードバランサー配下のインスタンスに障害が発生した際に、セッションのフェイルオーバーが行われることも確認できました。この際、スティッキーセッション指定にすることが必要でした。さらに、これらの仕組みを用いて構築を省力化するために、BeanstalkとEclipseプラグインを用いました。今日はこれでおしまいです。さて次は何しようかな。Redisを使った永続化でもやってみましょうか。BeanstalkはYAMLで書いて、CloudFormationはJSONで書いて、EC2はcloud-initで書いて、OpeWorksはRubyで書いて、、、どこに何を書けば良いんだろうw。次回もお楽しみに〜

参考資料

Manage Tomcat Session State with Amazon DynamoDB

Release: 1.0.0 - First version of the Amazon DynamoDB Session Manager for Apache Tomcat

Apache Tomcat 7 More about the Cat

Tomcat 7: How to enable HTTP Session DataBase persistence for PostgreSQL and DB2

スティッキーなしのセッションパーシスタンス

Tomcatでフェイルオーバー

Apache Tomcat 7 - The Manager Component

Customizing and Configuring AWS Elastic Beanstalk Environments

Tomcatのクラスタ環境でSession Replicationする方法

Customize Elastic Beanstalk Using Configuration Files