最近、アプリケーション基盤の調整をしていますが、そのまた一環で、
Dropwizard で session clustering を行うようにしたいと思います。


Dropwizard で 可能な session clustering 方法

Dropwizard は Jetty を使っているので、Jetty で提供されている clustering 機能はだいたい使えます。
現在、MongoDB を使用しているため、MongoDB に保存する方式をとりたいと思います。


参考記事

前提.使用ライブラリ

前提となるライブラリの verioon を記載します。

  • mongo-java-driver

    <!-- MongoDB -->
    <dependency>
        <groupId>org.mongodb</groupId>
        <artifactId>mongo-java-driver</artifactId>
        <version>3.2.2</version>
    </dependency>

  • dropwizard

    <dependency>
        <groupId>io.dropwizard</groupId>
        <artifactId>dropwizard-core</artifactId>
        <version>1.0.6</version>
    </dependency>
    <dependency>
        <groupId>io.dropwizard</groupId>
        <artifactId>dropwizard-servlets</artifactId>
        <version>1.0.6</version>
    </dependency>
    <dependency>
        <groupId>io.dropwizard</groupId>
        <artifactId>dropwizard-assets</artifactId>
        <version>1.0.6</version>
    </dependency>


jetty-nosql の、MongoSessionManager を使って実装する

使っている、mongo-driver関係で以下だと、私の環境ではエラーとなりました。
mongo-driver verison が 2系であれば以下の実装でも動作するかと思いますので記載しておきます。

pom へ依存関係を追加

参考記事のMongoSessionManager jetty-nosql含まれています。
Dropzaird 1.0.6 の jetty の version は Maven Repository: io.dropwizard » dropwizard-jetty » 1.0.6
確認する限り、9.3.9.v20160517 で、
jetty-nosql は、dropwizard の maven dependency には含まれないので、以下をpomの依存関係に追加しました。
jetty-nosql依存するライブラリ群は、Dropwizard 側で依存している、or Mongo Driver は別途依存性を定義しているため、
除外しています。 1
当初除外していなかったのですが、これが原因で、jettyのクラスがアプリケーションから見えず、エラーとなりました。

  • mongo-driver-v2 で動けばOKなら

    <!-- mongo-driver-v2 で動けばよいなら -->
    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-nosql</artifactId>
        <version>9.3.9.v20160517</version>
        <exclusions>
            <exclusion>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-server</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.eclipse.jetty.toolchain</groupId>
                <artifactId>jetty-test-helper</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.mongodb</groupId>
                <artifactId>mongo-java-driver</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.eclipse.jetty.tests</groupId>
                <artifactId>test-sessions-common</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-jmx</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

  • mongo-driver-v3 でも動かすなら

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-nosql</artifactId>
        <version>9.3.16.v20170120</version>
        <exclusions>
            <exclusion>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-server</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.eclipse.jetty.toolchain</groupId>
                <artifactId>jetty-test-helper</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.mongodb</groupId>
                <artifactId>mongo-java-driver</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.eclipse.jetty.tests</groupId>
                <artifactId>test-sessions-common</artifactId>
            </exclusion>
            <exclusion>
                <groupId>org.eclipse.jetty</groupId>
                <artifactId>jetty-jmx</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

  • 注意点 現在(2017/02/17時点)での、jetty-nosql のversionの最新は、
    jetty-9.4.2.v20170215ですが、9.3.0 > 9.4.0 Session 関連 API に結構な変更が入っており、
    Method 互換性がありません。
    jetty の version と合わせてたものをちゃんと持ってくることをお奨めします。

実装

Bundle に 以下の通り、実装しました。

  • Bundle.java (抜粋)

    public class MyBundle implements ConfiguredBundle<MyConfiguration> {
    
        @Override
        public void initialize(Bootstrap<?> bootstrap) {
            // Do Nothing...
        }
    
        @Override
        public void run(WicketConfiguration configuration, Environment environment) throws Exception {
            MutableServletContextHandler context = environment.getApplicationContext();
            // Mongo Session Manager 登録
            setSessionHandlerTo(environment);
            environment.jersey().disable();
        }
    
        /**
         * setSessionHandlerTo
         *
         * @param env
         */
        private void setSessionHandlerTo(Environment env) throws UnknownHostException {
            // Set SessionHandler
            MongoClientURI mongoURI = new MongoClientURI(MongoDBResource.getUriString());
            // Not Close...
            MongoClient mongoClient = new MongoClient(mongoURI);
            // 非推奨だが、ライブラリが対応していないので使う..
            DB db = mongoClient.getDB("jetty_session");
            DBCollection collection = db.getCollection("jetty_session_collection");
            env.lifecycle().addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener() {
                @Override
                public void lifeCycleStarting(LifeCycle event) {
                    log.info("lifeCycleStarting start event = {}", event);
                    if (!(event instanceof Server)) {
                        return;
                    }
                    Server server = (Server) event;
                    MongoSessionIdManager sessionIdManager = new MongoSessionIdManager(server, collection);
                    MongoSessionManager sessionManager = null;
                    try {
                        sessionManager = new MongoSessionManager();
                    } catch (UnknownHostException e) {
                        throw new IllegalStateException(e);
                    }
                    sessionIdManager.setWorkerName("node1");
                    sessionIdManager.setScavengePeriod(1800000L);
                    sessionIdManager.setScavengeBlockSize(0);
                    sessionIdManager.setPurge(true);
                    sessionIdManager.setPurgeDelay(1800000L);
                    sessionIdManager.setPurgeInvalidAge(1800000L);
                    sessionIdManager.setPurgeValidAge(5400000L);
                    sessionIdManager.setPurgeLimit(0);
                    sessionManager.setSessionIdManager(sessionIdManager);
                    sessionManager.setPreserveOnStop(true);
                    env.servlets().setSessionHandler(new SessionHandler(sessionManager));
                }
            });
        }
    }
    

  • 説明1 MongoSessionIdManager生成には、Server クラスのインスタンスが必要です。
    env.getApplicationContext().getServer(); でもJetty Serverにはアクセスできたのですが、
    StartServer 前だからなのが、NullPointerException が発生しました。
    java - Dropwizard Session Clustering - Stack Overflow
    確認したところ、AbstractLifeCycle.AbstractLifeCycleListener使って、JDBCSessionManager設定していたので、
    この方法を参考に、設定実装を記述しました。

  • 説明2 処理の流れは、
    1. MongoSessionIdManager生成して、MongoSessionManager設定。
    2. SessionHandler コンストラクタに、MongoSessionManager設定して、SessionHandler生成する。
    なります。2
    この流れが9.4.0からだと変わっています。

動かして、NoSuchMethod エラーが発生する場合の対処

サーバを起動したところ、以下の例外が発生しました。

Exception in thread "main" java.lang.NoSuchMethodError: com.mongodb.DBCollection.ensureIndex(Lcom/mongodb/DBObject;Lcom/mongodb/DBObject;)V
    at org.eclipse.jetty.nosql.mongodb.MongoSessionIdManager.<init>(MongoSessionIdManager.java:186)

Difference between createIndex() and ensureIndex() in java using mongodb - Stack Overflow

に記載がありますが、mongo driver の 3.0 から メソッドが削除されています。

Github で Issue がないか確認したところ下記がありました。
MongoSessionIdManager uses deprecated ensureIndex · Issue #641 · eclipse/jetty.project Patch があてられた version があり、そちらにpom の version を 変更したところ、解消されました。

    <dependency>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-nosql</artifactId>
        <version>9.3.16.v20170120</version>
    </dependency>


MongoSessionIdManager の 設定について

  • 設定値

Session Clustering with MongoDB
から抜粋します。

MongoSessionIdManagerMongoSessionManager に、跨って設定メソッドは存在しています。

設定値説明デフォルト
scavengeDelaySessionデータを削除にかける時間不明(MongoSessionIdManagerから設定できない)
scavengePeriodSessionデータを削除する間隔30分(ms指定)
scavengeBlockSize各削除クエリを制限するセッションIDの数。 メモリに非常に多くのセッションがある場合、これを0以外の値に設定すると、削除を複数のクエリに分割することで削除をスピードアップできます。 デフォルトは0で、すべてのセッションIDが1つのクエリで考慮されます。0
purge (Boolean)settionストアから無効なセッションを完全に削除するか否かtrue
purgeDelayPurge処理の実行間隔1日(ms指定)
purgeInvalidAge無効なセッションがパージ対象となるまでの期間1日(ms指定)
purgeValidAgeどれくらいまで遡って、Purge処理の対象とするか指定する期間値336日(ms指定)2
purgeLimitパージクエリから返されるアイテムの数を表す整数値。デフォルトは0で、無制限です。 期限切れの古いセッションが多い場合、この値を設定すると、パージ処理が高速化される可能性があります。0
preserveOnStopSeesionManagerが停止した時に全てのセッションを保持するか否かtrue

[3] 相当、意味がよくかわってないです。。


MongoDB に記録される Session データ

Mongo DB に 保存されたSession データは、以下の通りです。

{
    "_id" : ObjectId("58a6e651fb556ea0b033964a"),
    "id" : "node11enougmx2cmn1fyywq690pz76",
    "created" : NumberLong("1487332945990"),
    "valid" : true,
    "context" : {
        "::*" : {
            "__metadata__" : {
                "version" : NumberLong(2)
            },
            "wicket:Key[type=org%2Eapache%2Ewicket%2Eprotocol%2Ehttp%2EWicketFilter, annotation=[none]]:session" : BinData(0,"..."),
            "wicket:Key[type=org%2Eapache%2Ewicket%2Eprotocol%2Ehttp%2EWicketFilter, annotation=[none]]:wicket:persistentPageManagerData - Key[type=org%2Eapache%2Ewicket%2Eprotocol%2Ehttp%2EWicketFilter, annotation=[none]]" : BinData(0,"..."),
            "Wicket:SessionUnbindingListener-Key[type=org%2Eapache%2Ewicket%2Eprotocol%2Ehttp%2EWicketFilter, annotation=[none]]" : BinData(0,"...")
        }
    },
    "maxIdle" : -1,
    "expiry" : NumberLong(0),
    "accessed" : NumberLong("1487332957425")
}

  • 説明1 maxIdle 値が-1 なので、無期限でsessionは維持されます。
    mongoSessionManager#setMaxInactiveInterval(1800);設定すると、以下のようになります。
    "maxIdle" : 1800

  • 説明2
    "wicket:Key設定されているセッション情報は、Wicket を使用しているので、設定されているのかと思います。
    BinaryDataで文字列として、そこそこ大きい情報が詰まれています。
    Session に男らしく情報を詰め込んでいることがわかりました。

このところ、AP基盤系の設定ばかりしていますが、
基本的にどんなWebシステムでも、考慮が必要なものばかりです。
こういうところは、趣味?で作ってても仕事とつながりやすいところな気がします。
別につなげたいから、やっているわけではないですが..


追記 (2017/07/17)

Dropwizard 1.0.6 から 1.1.2 に update したところ、
jetty の version が 9.3 から、version 9.4 に上がり、MongoSessionIdManager削除されたり、
結構な変更が入っています。
Dropwizard 1.0.6 から 1.1.2 に update した話 | Monotalk
変更方法を記載しましたので、そちらもご確認ください。

以上です。

コメント