先日、こんなエントリが書かれました。
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>
動作確認用のコード
確認用のコードとしては、こんなのを用意。ちょっとした対話形式ですね。
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