CLOVER🍀

That was when it all began.

HazelcastのJCache対応を試してみました

Hazelcast 3.3.1から、JCache対応が入りました。

New Features
http://docs.hazelcast.org/docs/3.3/manual/html-single/hazelcast-documentation.html#new-features

3.3のリリース時点ではまだ間に合っていなくて、ドキュメントを見ると「自分でGitHubからcloneしてビルドしてね」みたいな手順が載っていたのですが、3.3.1からディストリビューションにちゃんと入ったようです。

Hazelcast JCache Implementation
http://docs.hazelcast.org/docs/3.3/manual/html-single/hazelcast-documentation.html#hazelcast-jcache-implementation

で、今回これを試してみようということで。

今まで、InfinispanとEhcacheのJCacheモジュールを試していますが、Hazelcastの方はけっこう勝手が違っていて、同じ感覚で使おうとしたらハマりました…。

まあ、そのあたりも踏まえて見ていってみましょう。

準備

まず、Maven依存関係に以下を加えます。

    <dependency>
      <groupId>javax.cache</groupId>
      <artifactId>cache-api</artifactId>
      <version>1.0.0</version>
    </dependency>
    <dependency>
      <groupId>com.hazelcast</groupId>
      <artifactId>hazelcast</artifactId>
      <version>3.3.2</version>
    </dependency>

JCacheのAPIとHazelcast、両方必要です。

ここで、Hazelcastの依存関係に何を指定するかで、動きが変わるようです。

Provider Types
http://docs.hazelcast.org/docs/3.3/manual/html-single/hazelcast-documentation.html#provider-types

ProviderのタイプにはServer、Clientがあり、依存関係の定義に応じて以下のようにデフォルトのProviderが変わるそうです。

  • hazelcast- … Server Provider
  • hazelcast-client- … Client Provider
  • hazelcast-all- … Client Provider

この挙動はシステムプロパティで切り替えることができ、以下のように指定するようです。

-Dhazelcast.jcache.provider.type=[client|server]

まあ、自分は滅多にClient Modeは使わないので、今回はServer Providerでいきます。

テストコードを書く

動作確認を兼ねて、テストコードを書いてみます。

依存関係に、JUnitとAssertJを加えました。

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.assertj</groupId>
      <artifactId>assertj-core</artifactId>
      <version>1.7.0</version>
      <scope>test</scope>
    </dependency>

ここからのテストコードでは、以下のimport文があることを前提にします。

import static org.assertj.core.api.Assertions.*;

import java.net.URI;
import java.util.concurrent.TimeUnit;

import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.configuration.Configuration;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.expiry.AccessedExpiryPolicy;
import javax.cache.expiry.Duration;
import javax.cache.spi.CachingProvider;

import org.junit.Ignore;
import org.junit.Test;

とりあえず、1番単純な例を書いてみます。

    @Test
    public void testJCacheGettingStarted() {
        try (CachingProvider provider = Caching.getCachingProvider();
             CacheManager manager = provider.getCacheManager();
             Cache<String, String> cache = manager.createCache("testCache",
                                                               new MutableConfiguration<>())) {
            cache.put("key", "value");
            assertThat(cache.get("key")).isEqualTo("value");

            cache.remove("key");
            assertThat(cache.get("key")).isNull();
        }
    }

続いて、有効期限付きの例。

    @Test
    public void testCacheExpired() throws Exception {
        Configuration<String, String> configuration =
            new MutableConfiguration<String, String>()
              .setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(new Duration(TimeUnit.SECONDS, 3)));

        try (CachingProvider provider = Caching.getCachingProvider();
             CacheManager manager = provider.getCacheManager();
             Cache<String, String> cache = manager.createCache("testCache",
                                                               configuration)) {
            cache.put("key1", "value1");
            cache.put("key2", "value2");

            TimeUnit.SECONDS.sleep(1);

            cache.get("key2");

            TimeUnit.SECONDS.sleep(2);

            assertThat(cache.get("key1")).isNull();
            assertThat(cache.get("key2")).isEqualTo("value2");
        }
    }

ここまでは、普通に使える感じですね。

あと、例は出しませんがCacheの実装(com.hazelcast.cache.ICacheインターフェース)を引っ張ってくることで、非同期操作とエントリごとの有効期限の設定が可能になるようです。

で、InfinispanやEhcacheで過去にやったように、設定ファイルでCacheを定義しようとしたら、見事にハマりました。

最初に何も考えずに、こんなコードを書いてみました。
*hazelcast-jcache.xmlという設定ファイルは、クラスパス上に作成済みの前提です

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        URI uri = classLoader.getResource("hazelcast-jcache.xml").toURI();

        try (CachingProvider provider = Caching.getCachingProvider();
             CacheManager manager = provider.getCacheManager(uri, classLoader);
             Cache<String, String> cache = manager.getCache("testCache")) {
            // 省略
        }

まあ、動きません。あらかじめ設定ファイルに「testCache」に対応するDistributed Mapの設定を書いていたのですが、関係なかったようです。

ここで、ちょっと実装を見てみました。それまでHazelcastのJCache対応は、既存のDistributed Mapの上に構築されているものだと勝手に思っていたのですが、そうでなく全然別系統の実装として存在するようです。

むしろ、設定ファイルでJCacheで使用するCacheは定義できなさそうな感じですね…。つまり、JCacheとして使用するCacheは、MutableConfigurationで定義しなさい、と。

*開発者にツッコミをいただいたので、最後に追記しました

このあたりは、ローカルCacheとして使うことを考慮しているのでしょうかねぇ…。

Hazelcastの設定ファイルがまったく使えないかというと、そんなことはなく、hazelcast.xmlをクラスパスに置いておくと普通に読まれますよ。

Configuring Hazelcast
http://docs.hazelcast.org/docs/3.3/manual/html-single/hazelcast-documentation.html#configuration

ただ、JCacheに特化したことが書けないだけです。クラスタの設定とかをする感じですかね。

じゃあ、CachingProvider#getCacheManagerに与えるURIって何?という話になるのですが(InfinispanやEhcacheでは、ここに設定ファイルを指定できていた)、Hazelcastの場合は生成するCacheManagerの名前空間分け的な使い方を想定した実相になっているようです。

https://github.com/hazelcast/hazelcast/blob/v3.3.2/hazelcast/src/main/java/com/hazelcast/cache/impl/HazelcastAbstractCachingProvider.java#L74

あと、個人的にはよくサンプルコードとして、単一JavaVM上に複数のHazelcastInstanceを立てて簡単にクラスタを構成していたのですが、JCacheのAPIのみを使用している限りでは、このあたりも難しそうです。

https://github.com/hazelcast/hazelcast/blob/v3.3.2/hazelcast/src/main/java/com/hazelcast/cache/impl/HazelcastServerCachingProvider.java#L51

実装によって、いろいろ変わるのだなぁとちょっと思いました…。

今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/hazelcast-examples/tree/master/hazelcast-jcache

追記
開発者の方に、このエントリに対するツッコミをいただきました。

Hazelcast 3.4向けのようですが、JCache用のドキュメントを執筆中らしいです。まだ始められたばかりだそうですが。

これを見ていると、cache要素を使用することで、設定ファイルでも定義できるようになりそうですね。Hazelcast 3.3のXMLスキーマにはまだcache要素はないので、3.4からだと思いますが。

ちょっと、様子を見ておきましょう。