Ehcacheとterracottaの連携を試してみた。

大量のアクセスがあるWebサービスを運用している場合に、DBの負荷が非常に高くなっている場合がある。
RDBMSはスケールアウトしずらく、スケールアップでその高負荷に対応するなどして、ボトルネックを解消しようとすると思う。

RDBMSの高負荷の解消のためには、キャッシュ機構を使用して、DBへのアクセスを減らすという方法がある。
キャッシュ機構には、自分の知っているものは下記がある。

キャッシュ機構


今回は、この中でEhcacheを使用したいと思う。

Ehchace

オープンソースの分散キャッシュサーバ。
memcachedと比べると、多機能である。

●キャッシュ方法

ディスクキャッシュ、メモリキャッシュをサポートする。
(※memcachedはメモリキャッシュのみ)

●キャッシュの追い出しポリシー
  • LRU(Last Recently Used)

  最後に参照された日時が最も古い順に削除する。

  • LFU(Last Frequently Used)

  参照された回数が最も少ない要素から順に削除する。

  • FIFO(First In First Out)

  要素がキャッシュに追加された順に削除する。
memcachedLRUが採用されている。

レプリケーション

RMI(Remote Method Invocation)や、JMS(Java Message Service)を使用してレプリケーション・キャッシュ*1を実現することができる。
また、後で記述するが、Terracottaを用いたレプリケーションも可能である。

●その他機能

トランザクション、ロック、モニタリング等の機能も存在する。

Terracotta

複数のJVM間でJavaオブジェクトのクラスタリングを行う。
また、HiberfnateやSpringとの連携も容易である。(各ミドルウェア向けのモジュールが提供されている)

Ehcacheのインストール

現在、最新のバージョンは2.4.2。
今回はmaven2を使用してプロダクトをインストールする。
pom.xmlに下記を追記する。

<repository>
    <id>terracotta-release</id>
    <url>http://repo.terracotta.org/maven2</url>
    <releases><enabled>true</enabled></releases>
    <snapshots><enabled>false</enabled></snapshots>
</repository>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.4.2</version>
    <type>pom</type>
</dependency>

また、APIでslf4jを使用しているので、下記も追記する。

<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-jdk14</artifactId>
  <version>1.6.1</version>
</dependency>
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>slf4j-api</artifactId>
  <version>1.6.1</version>
</dependency>

Terracottaのインストール

terracottaサーバの起動のために、Terracottaサイトから下記をダウンロードする。
現在の最新バージョンは3.5.1。
terracotta-3.5.1-installer.jar

下記コマンドでインストールする。

java -jar terracotta-3.5.1-installer.jar

また、TerracottaのruntimeAPIをダウンロードするために、pom.xmlに下記を記述する。

<dependency>
    <groupId>org.terracotta</groupId>
    <artifactId>terracotta-toolkit-1.2-runtime-ee</artifactId>
    <version>3.1.0</version>
</dependency>

EhcacheとTerracottaの連携

下記のような構成の場合のEhcacheとTerracottaの連携方法をサンプルとして記述します。

●システム構成

アプリケーションサーバが3台、Terracottaサーバが2台でミラーリング構成になっている。
DBサーバは1台。

●設定ファイルの作成

EhcacheとTerracottaを連携するためには、ehcache.xmlを作成する必要がある。
ehcache.xmlはクラスパスのルート位置に作成する。

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="true" monitoring="autodetect">

    <!-- ディスクキャッシュ時のデータ保存領域 -->             
    <diskStore path="java.io.tmpdir"/>

    <!-- terracottaの設定 -->
    <!--
    <terracottaConfig url="localhost\:9510, your.domain:9510" />
    -->
    <terracottaConfig>
	   <tc-config>
	       <system>
	           <configuration-model>development</configuration-model>
	       </system>
	       <servers>
	           <server host="localhost" name="server1">
                    <!-- permanent-store(ディスクに保存)に設定しないと再接続ができない。 -->
                    <dso>
                        <persistence>
                        <mode>permanent-store</mode>
                        </persistence>
                    </dso>
               </server>
               <server host="your.domain" name="server2">
                    <dso>
                        <persistence>
                        <mode>permanent-store</mode>
                        </persistence>
                    </dso>
               </server>
           </servers>
           <clients>
               <logs>C:\app\logs-%i</logs>
           </clients>
	   </tc-config>
    </terracottaConfig>
    
    <!-- デフォルトキャッシュ -->
    <defaultCache />

    <!--
        name: キャッシュ名
        eternal:キャッシュオブジェクトの有効期限の有無(true:無)
        maxElementsInMemory:メモリ上にキャッシュするオブジェクトの最大数(0の場合は無制限)
        maxElementsOnDisk:ディクス上にキャッシュするオブジェクトの最大数(0の場合は無制限)
        overflowToDisk:キャッシュオブジェクトの最大数を超えた時、Diskに保持するかどうか。(true:する) ※terracottaを使用する場合はfalseに設定する。
        timeToIdleSeconds:未アクセスの削除期限
        timeToLiveSeconds:キャッシュオブジェクトの寿命
        memoryStoreEvictionPolicy:メモリに格納するエントリが最大値に達したときの振る舞い(LRU、FIFOまたは、LFU)
    -->
	<cache
    	    name="terracottaTest"
            eternal="false"
            maxElementsInMemory="1000"
	    maxElementsOnDisk="10000"
            overflowToDisk="false"
            timeToIdleSeconds="3600"
            timeToLiveSeconds="3600"
            memoryStoreEvictionPolicy="LFU">
            <!-- terracottaを使用する -->
	    <terracotta
                clustered="true"
        	valueMode="serialization"
        	consistency="strong"
        	storageStrategy="DCV2">
            <!-- 読み取り中にタイムアウトした場合は、ローカルから読み取る設定 -->
            <nonstop
                enabled="true"
                immediateTimeout="false"
                timeoutMillis="1000">
                <timeoutBehavior type="localReads" />
            </nonstop>
        </terracotta>
    </cache>
</ehcache>

次に、Terracottaの設定ファイル「tc-config.xml」を任意の位置に作成する。
serversタグで書かれたサーバの起動順によって、アクティブサーバとスタンバイサーバになる。

tc-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<tc:tc-config xmlns:tc="http://www.terracotta.org/config"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.terracotta.org/schema/terracotta-6.xsd">
	<system>
       <configuration-model>development</configuration-model>
	</system>
    <servers>
        <server host="localhost" name="server1">
            <data>%(user.home)/terracotta/server-data</data>
            <logs>%(user.home)/terracotta/server-logs</logs>
        </server>
        <server host="your.domain" name="server2">
            <data>%(user.home)/terracotta/server-data</data>
            <logs>%(user.home)/terracotta/server-logs</logs>
        </server>
        <ha>
            <mode>networked-active-passive</mode>
            <networked-active-passive>
                <election-time>5</election-time>
            </networked-active-passive>
        </ha>
    </servers>
</tc:tc-config>
Terracottaサーバの起動

Windowsの場合とMac/Linuxの場合で起動バッチファイルまたはシェルファイルとなる。
先ほど作成した設定ファイルを使用する場合は、-fオプションで設定ファイルのパスを指定するとよい。

{terracottaのインストールディレクトリ}\bin\start-tc-server.bat (-f tc-config.xml)
{terracottaのインストールディレクトリ}/bin/start-tc-server.sh (-f tc-config.xml)
●アプリの設定

今回はWEBアプリのフレームワークSeasar2を使用する。
app.diconにキャッシュインスタンスを作成する。

app.dicon
<component name="cacheManager" class="net.sf.ehcache.CacheManager">
    @net.sf.ehcache.CacheManager@getInstance()
    <destroyMethod name="shutdown" />
</component>

実際にキャッシュに保存、取得するロジックは下記。

public class EhcacheLogic {

    @Resource
    protected CacheManager cacheManager;

    private Cache cache;

    /**
     * 初期処理。
     */
    public void init() {
        if (!this.cacheManager.cacheExists("terracottaTest")) {
            this.cacheManager.addCache("terracottaTest");
        }
        cache = cacheManager.getCache("terracottaTest");
    }

    /**
     * キャッシュに保存する。
     * @param key キー値
     * @param value
     */
    public void put(String key, String value) {
        Element element = new Element(key, value);
        cache.put(element);
    }

    /**
     * キャッシュから取りだす。
     * @param key キー値
     * @return
     */
    public Object get(String key) {
        Element element = cache.get(key);
        if (element == null) {
            return new String("");
        }
        Serializable originalResult = element.getValue();

        return SerializationUtils.clone(originalResult);
    }

    /**
     * キャッシュから削除する。
     * @param key キー値
     */
    public void remove(String key) {
        cache.remove(key);
    }
}

Terracottaでのクラスタリングは検証中であるが、からり早く各JVMにオブジェクトがコピーされていた。
Terracottaには開発者コンソールというものもある。どのような設定で動作しているのか、また、キャッシュの保存、取得、取得ミスなどの情報が閲覧できる。
調査をすすめて、こちらに追記していきたいと思う。

EhcacheとTerracottaの連携に関して、下記の本を参考にさせていただいた。

WEB+DB PRESS Vol.61

WEB+DB PRESS Vol.61

*1:あるアプリケーションサーバのキャッシュにデータを格納すると他のアプリケーションサーバのキャッシュへデータをレプリケートするキャッシング方式