CLOVER🍀

That was when it all began.

EhcacheとJGroupsで、Cacheのレプリケーションをしてみる

先日、こんなエントリが書かれました。

Ehcache 使い方メモ
http://qiita.com/opengl-8080/items/f42664769740674176ff

こちら、すごいですね。メモと題しているものの、日本語情報でここまでEhcacheについて書かれたのほとんど見たことないかも…。

この中で、RMIを使ったレプリケーションも紹介されていました。レプリケーションについてはそういえばやったことがないので、これを機にということで試してみました。

Ehcacheのレプリケーション

Ehcacheでは、Cacheのレプリケーションの方法として、以下の3つから選ぶことができるようです。

Ehcache Replication Guide
http://ehcache.org/generated/2.10.0/html/ehc-all/#page/Ehcache_Documentation_Set%2Fto-title_ehcache_replication_guide.html%23

  • RMI
  • JGroups
  • JMS

このうち、RMIはEhcacheのコアに入っており、それ以外はオプション扱いです。

ちょっと見てみた感じ、更新されていない感がすごいです…。

ehcache-jgroupsreplication(2012/7/26最終アップデート、Ehcache 2.5.2、JGroups 3.1.0.Finalに依存)
http://search.maven.org/#artifactdetails|net.sf.ehcache|ehcache-jgroupsreplication|1.7|jar

ehcache-jmsreplication(2013/8/13最終アップデート、Ehcache 2.5.2以上、JMS 1.1に依存)
http://search.maven.org/#artifactdetails|net.sf.ehcache|ehcache-jmsreplication|0.5|jar

という状態ですが、今回はムリヤリJGroupsを使ってみました(笑)。

なので、ここからの内容はご参考(?)までに。

Maven依存関係

Maven依存関係は、以下のように定義。

        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>2.10.0</version>
        </dependency>
        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-jgroupsreplication</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>org.jgroups</groupId>
            <artifactId>jgroups</artifactId>
            <version>3.6.4</version>
        </dependency>

ムダにJGrouopsを最新化してみました。

Ehcacheの設定

こちらを参考に、Ehcacheの設定をしてみます。JGroupsの設定も含めます。

Example Configuration using UDP Multicast
http://ehcache.org/generated/2.10.0/html/ehc-all/index.html#page/Ehcache_Documentation_Set%2Fco-jgrp_example_config_using_udp_multicast.html%23

Configuring CacheReplicators
http://ehcache.org/generated/2.10.0/html/ehc-all/index.html#page/Ehcache_Documentation_Set%2Fco-jgrp_configuring_cache.html%23

なんですけど、ムダにちょっと新しめの設定にしてみました。
src/main/resources/ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd"
         updateCheck="false" monitoring="autodetect"
         dynamicConfig="false">
    <cache name="replicatedCache"
           maxEntriesLocalHeap="10000"
           eternal="true"
           timeToIdleSeconds="0"
           timeToLiveSeconds="0"
           overflowToDisk="false"
           memoryStoreEvictionPolicy="LRU">
        <cacheEventListenerFactory
                class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
                properties="replicateAsynchronously=true, replicatePuts=true,
       replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true"/>
    </cache>

    <cacheManagerPeerProviderFactory
            class="net.sf.ehcache.distribution.jgroups.JGroupsCacheManagerPeerProviderFactory"
            properties="connect=UDP(mcast_addr=231.12.21.132;mcast_port=45566;ip_ttl=2):
            PING:
            MERGE3(min_interval=10000;max_interval=30000):
            FD_SOCK:
            FD_ALL(timeout=60000;interval=15000;timeout_check_interval=5000):
            VERIFY_SUSPECT(timeout=5000):
            pbcast.NAKACK2(xmit_interval=1000;xmit_table_num_rows=50;xmit_table_msgs_per_row=1024;xmit_table_max_compaction_time=30000;resend_last_seqno=true):
            UNICAST3(xmit_interval=500;xmit_table_num_rows=50;xmit_table_msgs_per_row=1024;xmit_table_max_compaction_time=30000;max_msg_batch_size=100;conn_expiry_timeout=0):
            pbcast.STABLE(stability_delay=500;desired_avg_gossip=5000;max_bytes=1M):
            pbcast.GMS(print_local_addr=true):
            UFC(max_credits=2m;min_threshold=0.40):
            MFC(max_credits=2m;min_threshold=0.40):
            FRAG2"
            propertySeparator="::"
            />
</ehcache>

Nodeの見つけ方は、マルチキャストで行うようにしています。Node間の通信方法はUDPです。

動作確認用のコード

確認用のコードとしては、こんなのを用意。ちょっとした対話形式ですね。
src/main/java/org/littlewings/ehcache/EhcacheReplicator.java

package org.littlewings.ehcache;

import net.sf.ehcache.Cache;
import net.sf.ehcache.CacheManager;
import net.sf.ehcache.Element;

public class EhcacheReplicator {
    public static void main(String... args) {
        CacheManager cacheManager = CacheManager.newInstance();

        try {
            Cache cache = cacheManager.getCache("replicatedCache");

            while (true) {
                String line = System.console().readLine("Command> ");

                if (line == null) {
                    continue;
                } else if ("exit".equals(line)) {
                    break;
                } else {
                    try {
                        String[] tokens = line.split(" +");
                        String key;
                        String value;

                        switch (tokens[0]) {
                            case "get":
                                key = tokens[1];
                                Element element = cache.get(key);

                                if (element != null) {
                                    System.out.printf("get key[%s] => %s%n", key, element.getObjectValue());
                                } else {
                                    System.out.printf("get key[%s] not found.%n", key);
                                }

                                break;
                            case "put":
                                key = tokens[1];
                                value = tokens[2];

                                cache.put(new Element(key, value));

                                System.out.printf("putted key[%s] => %s%n", key, value);

                                break;
                            case "size":
                                System.out.printf("Cache size: %d%n", cache.getSize());
                                break;
                            default:
                                System.out.printf("Unknown command[%s]%n", tokens[0]);
                                break;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        } finally {
            cacheManager.shutdown();
        }
    }
}

実行してみる

それでは、こちらを動かしてみましょう。

まずは、2つNodeを起動してみます。

## Node 1
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.ehcache.EhcacheReplicator

[INFO] --- exec-maven-plugin:1.4.0:java (default-cli) @ ehcache-replication-jgroups ---
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
6 27, 2015 11:18:05 午後 org.jgroups.protocols.UDP setBufferSize
警告: JGRP000015: the receive buffer of socket MulticastSocket was set to 500KB, but the OS only allocated 212.99KB. This might lead to performance problems. Please set your max receive buffer in the OS correctly (e.g. net.core.rmem_max on Linux)

-------------------------------------------------------------------
GMS: address=xxxxx-21169, cluster=EH_CACHE, physical address=fe80:0:0:0:20c:29ff:fe47:1a6e%2:47357
-------------------------------------------------------------------
Command> 

もうひとつ。

## Node 2
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.ehcache.EhcacheReplicator

[INFO] --- exec-maven-plugin:1.4.0:java (default-cli) @ ehcache-replication-jgroups ---
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
6 27, 2015 11:18:36 午後 org.jgroups.protocols.UDP setBufferSize
警告: JGRP000015: the receive buffer of socket MulticastSocket was set to 500KB, but the OS only allocated 212.99KB. This might lead to performance problems. Please set your max receive buffer in the OS correctly (e.g. net.core.rmem_max on Linux)

-------------------------------------------------------------------
GMS: address=xxxx-13394, cluster=EH_CACHE, physical address=fe80:0:0:0:20c:29ff:fe47:1a6e%2:34282
-------------------------------------------------------------------
Command> 

起動しました。メンバーとして認識されたかどうか怪しいですが、ちょっと試してみます。

Node 1でデータ登録。

Command> put key1 value1
putted key[key1] => value1

Node 2で参照。

Command> get key1
get key[key1] => value1

Node 2でデータ登録。

Command> put key2 value2 
putted key[key2] => value2

Node 1で参照。

Command> get key2
get key[key2] => value2

Node 1でCacheのサイズ確認。

Command> size
Cache size: 2

ちゃんとレプリケーションされていそうですね。

もうひとつNodeを加えると…?

ここで、もうひとつCacheのNodeを起動してみます。

## Node 3
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.ehcache.EhcacheReplicator

[INFO] --- exec-maven-plugin:1.4.0:java (default-cli) @ ehcache-replication-jgroups ---
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
6 27, 2015 11:21:35 午後 org.jgroups.protocols.UDP setBufferSize
警告: JGRP000015: the receive buffer of socket MulticastSocket was set to 500KB, but the OS only allocated 212.99KB. This might lead to performance problems. Please set your max receive buffer in the OS correctly (e.g. net.core.rmem_max on Linux)

-------------------------------------------------------------------
GMS: address=xxxxx-44544, cluster=EH_CACHE, physical address=fe80:0:0:0:20c:29ff:fe47:1a6e%2:52716
-------------------------------------------------------------------
Command> 

なんと、この新しいNodeにはこれまでのデータは入っていないようです。

Command> size
Cache size: 0
Command> get key1
get key[key1] not found.
Command> get key2
get key[key2] not found.

ここでめげずに、Node 3で新しいデータを登録してみます。

Command> put key3 value3
putted key[key3] => value3

Node 2から参照。

Command> get key3
get key[key3] => value3

Node 1でデータ登録。

Command> put key4 value4
putted key[key4] => value4

Node 3で参照。

Command> get key4
get key[key4] => value4

新しく登録した分については、共有されていますね。

これはどういうことか?

というわけで、新しいNodeについては起動前に登録されていたデータは参照不可なわけですが、この設定を見るとEventListenerでレプリケーションしてそうな感じですよね。

        <cacheEventListenerFactory
                class="net.sf.ehcache.distribution.jgroups.JGroupsCacheReplicatorFactory"
                properties="replicateAsynchronously=true, replicatePuts=true,
       replicateUpdates=true, replicateUpdatesViaCopy=false, replicateRemovals=true"/>

これなら、起動以降のイベント分しか反映されないかも…。

まとめ

かなりムリヤリですが、EhcacheのJGroupsを使ったレプリケーションを試してみました。

そもそもメンテナンスされてなさそうなモジュールですし(だったら、なんでドキュメントに残っているんだろう…)、使うのはオススメできない気がします。

また、新規に登録されたデータしか参照できないのは、RMIでもEventListnerで頑張る感じみたいなのできっと同じ挙動なのでしょう(設定こそ違いますが、たぶんJMSも…)。

Configuring the CacheManagerPeerListener
http://ehcache.org/generated/2.10.0/html/ehc-all/#page/Ehcache_Documentation_Set%2Fco-rmi_configuring_cache_replicators.html%23

新しいNode追加時に、同期してくれないと困らない…のかな?あくまでCacheなので、そういう割り切りなのかもしれません。

でも、複数Nodeでデータを共有したいのなら、Ehcacheじゃなくて他の分散Cache製品を選びそうな気がしますね…。個人的には、Ehcacheは単一のJavaVMでのCacheで使うイメージが強いです。それ以上なら、Ehcacheの方向でいくのなら、BigMemory MaxとかGoを選ぶんじゃないでしょうか?(商用ですし、機能は確認してないですけど…)。

BigMemory Max
http://terracotta.org/products/bigmemorymax

BigMemory Go
http://terracotta.org/products/bigmemorygo