OSCacheのクラスタリング

OSCacheのクラスタリングは今までやったことがなかったのですが、わけあってやってみました。
まず、OSCacheのクラスタリング機能が提供してくれる機能についてですが、やってくれるのはflushの同期のみです。キャッシュエントリそのもののレプリケーションをしてくれるわけではありません。レプリケーションのコストを考えると当然だとは思いますが、利用にあたってはこの点は意識しておく必要があります。
というわけで、要するにOSCacheのクラスタリング機能というのはクラスタに参加しているノードに対してflushメッセージを伝達するだけのもの、と考えるとよいです。で、そのメッセージの伝達方法にはJMSのトピックを使う方法と、JGroupsを使う方法が用意されています。
Tomcatとか使ってるのにJMSサーバを別に立てるのはめんどくさいので、今回は簡単に利用できるJGroupsを使う方法を試してみました。

設定

設定は簡単で、OSCacheのJARファイルに加えてjgroups-all.jarをクラスパスに追加したうえでoscache.propertiesに以下の一行を追加するだけ。

cache.event.listeners=com.opensymphony.oscache.plugins.clustersupport.JavaGroupsBroadcastingListener

これでOKと思いきや、OSCache 2.4.1に含まれているJGroups 2.2.8には問題があるらしく、実行するとなんかエラーが出ます。JGroupsのWebサイトから最新のJARを取ってきて入れ替えたら動きました。
ちなみにJGroupsの詳細な設定を行いたい場合はoscache.propertiesのcache.cluster.propertiesという項目を使うみたいです。デフォルトでは以下のような設定になっているそうな。

cache.cluster.properties=UDP(mcast_addr=231.12.21.132;mcast_port=45566;ip_ttl=32;\
mcast_send_buf_size=150000;mcast_recv_buf_size=80000):\
PING(timeout=2000;num_initial_members=3):\
MERGE2(min_interval=5000;max_interval=10000):\
FD_SOCK:VERIFY_SUSPECT(timeout=1500):\
pbcast.NAKACK(gc_lag=50;retransmit_timeout=300,600,1200,2400,4800;max_xmit_size=8192):\
UNICAST(timeout=300,600,1200,2400):\
pbcast.STABLE(desired_avg_gossip=20000):\
FRAG(frag_size=8096;down_thread=false;up_thread=false):\
pbcast.GMS(join_timeout=5000;join_retry_timeout=2000;shun=false;print_local_addr=true)

JavaVMの起動オプション

IPv6が利用可能な場合、IPv6が優先されてしまうようです。JavaVMの起動オプションに「-Djava.net.preferIPv4Stack=true」を追加することでIPv4を優先させることができます。
また、複数ネットワークアダプタが存在する場合、最初に見つかったものが使用されてしまうようです。この場合は「-Djgroups.bind_addr=」を追加することで使用するネットワークアダプタを指定することができます。
僕はホスト上のTomcatVMWare上のTomcatクラスタリングを試したので、ホスト側のTomcatを起動する際にNAT用のアダプタ(VMnet8)のIPアドレスを指定しました。

flushするときの注意点

これで一応動くはずですが、キャッシュをflushする際に1つ注意点があります。WebアプリケーションではServletCacheAdministratorというキャッシュ管理クラスを利用できるのですが、以下のようにServletCacheAdministratorのflushAll()メソッドを呼び出してもflushしたというメッセージの伝達が行われません。

ServletCacheAdministrator cacheAdmin
  = ServletCacheAdministrator.getInstance(getServletContext());

// これはダメ
cacheAdmin.flushAll();

以下のように各スコープのCacheオブジェクトを取得し、Cache#flushAll()とかを呼べば、クラスタに参加しているノードに「ちゃんとflushしたよー」というメッセージが飛びます。

ServletCacheAdministrator cacheAdmin
  = ServletCacheAdministrator.getInstance(getServletContext());

// これはOK
Cache cache = cacheAdmin.getAppScopeCache(getServletContext());
cache.flushAll(new Date());

なんというか、いちおう動くには動いたのですが、使いこなすには結構コツが必要な感じです。