この記事は、「Payara Advent Calendar 2016 - Qiita」の17日目の記事となります。
昨日は、@khasunumaさんの「Payara がサポートする 2 つのクラスタリング - notepad」でした。
明日も…@khasunumaさんのご担当となります…(すごい)。
この記事では、Lite Membersをテーマに扱おうと思います。
Lite Membersとは?
Lite Membersとは、Payara 4.1.1.162で追加されたクラスタリング関係の新機能です。
What's New in Payara Server 162?
Lite Membersとはなにか?ですが、Lite Members自体はHazelcast 3.6の新機能です。
Hazelcast 3.0のリリース前に1度ドロップされた機能が、3.6で追加されたようです。通常、Hazelcastクラスタに
参加したNodeは、データが分配され各Nodeでデータは持ちます。しかし、Lite Memberとして設定されたNodeに
ついては、Hazelcastのクラスタには参加するもののデータを持たなくなります。ちょっと特殊なNodeです。
このようにデータを持たないままHazelcastクラスタへアクセスする場合は、Hazelcastの
クライアントライブラリを使用して(いわゆるClient/Server Mode)クラスタ外から
アクセスするのが通例なのですが、Lite Memberの場合はクラスタに参加しつつもデータを持たないNodeになります。
Payaraでは、Hazelcastの利用モードはEmbedded Mode固定であり、Client/Server Modeでは
利用できません。
Lite MemberとClient/Server Modeとの違いは、こちら。
Difference between Lite Member and Smart Client?
Payaraに組み込まれたHazelcastを使用している限りは、Client/Server Modeに対するLite Memberの
利点は気にしなくてもよいでしょう。
今回は、Payaraにフォーカスして書いていきます。使用するPayaraのバージョンは、4.1.1.164です。
※ちなみに、サンプルで使うのはPayara Microとします
PayaraにおけるLite Members
Payaraでの関連記事は、こちらです。
Flexible Clustering with Payara Server
Payara Server Lite Nodesを見ると、コンセプトとしては
- ネイティブなNodeより良いパフォーマンス
- イベントに対するListenerの登録が可能
- Partition(Hazelcastにおけるデータの管理単位)は持たないが、非Lite Memberへのアクセスは可能
- Near Cacheが利用可能
のようです。パフォーマンスについては、Lite Members単体の説明としてはちょっと謎ですが…。
最後にオマケ的に入れますが、Hazelcast固有の機能を直接使い倒すわけでもなければ、最後のNear Cacheについては忘れていいと思います。
Listenerもそうですが、特にNear Cacheについては。
Lite Memberとしてクラスタに参加することでなにが変わるかですが、Lite MemberについてはNodeの追加や削除の影響
(クラスタで保持するデータのリバランスの発生やリカバリ)がなくなります。
通常のHazelcast Clusterのイメージは次のような感じです。各Nodeでデータ(Primary/Backup)を保持し、リクエストされたNodeに
データがなければ他のNodeに取得しにいきます。
Nodeを追加、削除を行うと、クラスタの自動リバランスが動作します。Nodeが追加されればクラスタ全体で
使用可能なメモリ量は増えますし、削除されればデータが一部なくなるため、バックアップから復元しようとします。
クラスタ参加NodeをLite Memberとすることで、クラスタで管理するデータには依存せず、純粋に計算能力を
保持しただけのNodeを追加/削除することができます。
Lite Member「のみ」のクラスタを構成することはできませんが(必ず非Lite MemberなNodeが必要)、仮にLite Memberが
すべてダウンしても、データは一切欠損しませんしね。
※デフォルトではデータのバックアップ数は1のため、2つの非Lite MemberなPayara Nodeが同時にダウンするとデータが一部失われる可能性があります
クラスタ上の分散データは非Lite Memberの持つメモリの総量で管理し、多くの計算能力が必要なケースは
Lite Memberを使ってもよいのでは、というのがFlexible Clustering with Payara Serverで想定している
シナリオのようです。
とまあ、そんな感じなのがLite Memberなのですが、データを持たない分だけHazelcastクラスタ上にあるデータが
必要な時には、ネットワーク呼び出しが発生するのでその点は意識しておく必要があります。
ここまででのLite Memberを含めたクラスタのイメージは次のような感じでしょう。
Payara上で動作するアプリケーション的に、Hazelcastの分散データ構造を使用するのは以下のようなケースだと思います。
※他にもあるかもですが、現在自分が把握している範囲で…
- Web Session(Distributed Mapによるセッションレプリケーション)
- JCache(Distributed Cacheによる分散キャッシュ)
- EJB
- Clustered CDI Event Bus(TopicによるNode間のCDIイベントの伝播)
- jBatchのJob/CheckpointのStore
直接Hazelcastを使うのでもなければ、まずはセッションとJCacheが該当するでしょう。
今回は、この2つを使ってLite Memberがどのようなものかを見ていきたいと思います。
準備
それでは、ここからはプログラムを書いていきます。実行は、Payara Microで行います。
まずは、Payara MicroへのMaven依存関係を追加。
<dependency> <groupId>fish.payara.extras</groupId> <artifactId>payara-micro</artifactId> <version>4.1.1.164</version> <scope>provided</scope> </dependency>
また、WARファイルの名前は「app.war」となるように今回は設定しています。
<build> <finalName>app</finalName> </build>
サンプルコード
サンプルは、JAX-RSでのHttpSessionの操作とJCacheのCDI Interceptorで行います。
JAX-RSの有効化。
src/main/java/payaraadventcalendar/JaxrsActivator.java
package payaraadventcalendar; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; @ApplicationPath("rest") public class JaxrsActivator extends Application { }
ログインではありませんが、指定された名前のユーザーを登録された時刻でセッションに保持するJAX-RSリソース。
src/main/java/payaraadventcalendar/UserResource.java
package payaraadventcalendar; import java.io.Serializable; import java.time.LocalDateTime; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; @Path("user") public class UserResource { @GET @Path("register") public Response register(@QueryParam("firstName") String firstName, @QueryParam("lastName") String lastName, @Context HttpServletRequest request, @Context UriInfo uriInfo) { User user = new User(); user.setId(UUID.randomUUID().toString()); user.setFirstName(firstName); user.setLastName(lastName); user.setLoginTime(LocalDateTime.now()); request.getSession().setAttribute("user", user); return Response.created(uriInfo.getRequestUri()).build(); } @GET @Produces(MediaType.APPLICATION_JSON) public User get(@Context HttpServletRequest request) { return (User) request.getSession().getAttribute("user"); } public static class User implements Serializable { private static final long serialVersionUID = 1L; private String id; private String firstName; private String lastName; private LocalDateTime loginTime; // getter、setter省略 } }
JCacheを使ったJAX-RSリソースクラス。こちらは、CDI管理Beanとしています。
src/main/java/payaraadventcalendar/CalcResource.java
package payaraadventcalendar; import java.util.concurrent.TimeUnit; import javax.cache.annotation.CacheDefaults; import javax.cache.annotation.CacheResult; import javax.enterprise.context.ApplicationScoped; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.QueryParam; @Path("calc") @ApplicationScoped @CacheDefaults(cacheName = "calcCache") public class CalcResource { @GET @Path("add") @CacheResult public int add(@QueryParam("a") int a, @QueryParam("b") int b) throws InterruptedException { TimeUnit.SECONDS.sleep(5L); return a + b; } }
セッションレプリケーションが使えるように、web.xmlにdistributableを設定しておきましょう。
src/main/webapp/WEB-INF/web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <distributable/> </web-app>
で、パッケージング。
$ mvn package
Payara Microを起動してクラスタを構成する
では、クラスタを構成してみましょう。今回は、Lite Memberを2 Node、非Lite Memberを2 Node起動させます。
Lite Memberは、「--lite」オプションを付けて起動するのですが、この時に先にLite Memberを起動してしまうとエラーになります。
$ java -jar payara-micro-4.1.1.164.jar --deploy target/app.war --lite
こんなのとか
No member group is available to assign partition ownership...
こんなエラーメッセージを見ることになるでしょう。
Exception while dispatching an event com.hazelcast.partition.NoDataMemberInClusterException: Partitions can't be assigned since all nodes in the cluster are lite members
なので、先に非Lite Memberから起動します。今回は、非Lite Memberを2 Node、Lite Memberを2 Nodeの構成としましょう。
JAX-RSリソースへのアクセスは、Lite Memberに対して行うことにします。なので、非Lite Memberには8080ポートは空けてもらいます。
## 非Lite Member 1 $ java -jar payara-micro-4.1.1.164.jar --deploy target/app.war --port 18080 ## 非Lite Member 2 $ java -jar payara-micro-4.1.1.164.jar --deploy target/app.war --port 19080 ## Lite Member 1 $ java -jar payara-micro-4.1.1.164.jar --deploy target/app.war --lite --port 8080 ## Lite Member 2 $ java -jar payara-micro-4.1.1.164.jar --deploy target/app.war --lite --port 9080
クラスタができました、と。
Members [4] { Member [172.17.0.1]:5900 - a99907a6-a7ff-4151-956a-410bf093db38 Member [172.17.0.1]:5901 - b9dd75c3-29a7-4b59-84cf-26a7a0b9c5e4 Member [172.17.0.1]:5902 - f383ec99-f98b-41d4-93e4-04cd388a925e this lite Member [172.17.0.1]:5903 - 6cfd1213-5edf-46fa-82b1-783aa37ca171 lite }
「lite」の表記が出ているのが、Lite Memberです。
蛇足
今回は全NodeにWARファイルをデプロイしていますが、実は非Lite Memberについてはアプリケーションとしての
実アクセスがなければWARファイルのデプロイは不要だったりします。Hazelcastの設定などをしていれば話は
別ですが、単にデータの配置先として使用している限りはWARはなくても大丈夫です。というか、Payaraでなくて
通常のHazelcast Nodeでもいい気が…。
この場合は、非Lite Memberは単純にアプリケーション外部の分散KVSとして扱われることになりますね。
動作確認
では、ここでデータを放り込んでみましょう。
「app/user/register」で、ちょっと50人くらいセッションに放り込んでみます。
※スクリプトの中身は割愛(ソースコードはGitHubに置いていますので、興味があればそちらへ)。
$ groovy gen-users.groovy # 次みたいなのが、50個流れます $ curl -i 'http://localhost:8080/app/rest/user/register?firstName=%E9%BA%97%E5%A5%88&lastName=%E8%A5%BF%E6%B2%A2'
テストデータは、ここで作りました。
テストデータ・ジェネレータ
また、Cacheにも数個登録してみましょう。
$ curl -i 'http://localhost:8080/app/rest/calc/add?a=3&b=5' $ curl -i 'http://localhost:8080/app/rest/calc/add?a=8&b=9' $ curl -i 'http://localhost:8080/app/rest/calc/add?a=1&b=5' $ curl -i 'http://localhost:8080/app/rest/calc/add?a=21&b=3' $ curl -i 'http://localhost:8080/app/rest/calc/add?a=12&b=25'
あまり、内容に意味はありません…。
ここで、JMXで統計情報を見てみます。Payara Microでは、デフォルトでHazelcastのJMXについての設定が有効化されています。
https://github.com/payara/Payara/blob/payara-server-4.1.1.164/nucleus/payara-modules/hazelcast-bootstrap/src/main/java/fish/payara/nucleus/hazelcast/HazelcastCore.java#L194
まずはLite Member。HazelcastのDistributed Map(IMap)を見てみましょう。ここで表示しているのはMap名が「/app」で
セッションの内容が格納されているDistributed Mapになります。コンテキストパスがMapの名前になるようです。
https://github.com/payara/Payara/blob/payara-server-4.1.1.164/appserver/web/web-ha/src/main/java/org/glassfish/web/ha/strategy/builder/ReplicatedWebMethodSessionStrategyBuilder.java#L123
https://github.com/payara/Payara/blob/payara-server-4.1.1.164/appserver/web/web-ha/src/main/java/org/glassfish/web/ha/session/management/ReplicationWebEventPersistentManager.java#L266
https://github.com/payara/Payara/blob/payara-server-4.1.1.164/appserver/ha/ha-hazelcast-store/src/main/java/fish/payara/ha/hazelcast/store/HazelcastBackingStoreFactory.java#L55
4 Node全部載せるのも冗長なので、それぞれ1 Nodeずつにします
で、注目するのは「localOwnerEntryCount」と「localBackupEntryCount」が0になっていることです。
「localOwnerEntryCount」がそのNodeのCollectionでPrimaryとして保持しているデータの数、「localBackupEntryCount」がバックアップとして保持しているデータの数となります。
つまり、データを持っていません。
続いて、非Lite Member。こちらについては、「localOwnerEntryCount」と「localBackupEntryCount」がそれぞれ
25になっています。もう半分は、別の非Lite Memberが持っていることになります。
※いつもこのようにキレイに分かれるとは限りません
一応ですが、セッション数です。今回属性はひとつしか登録していませんが、その数ではありません。
Cacheについては、この設定だけでは見れなかったです…。
このように、Lite Memberはデータを持たないため、たとえこの2つのLite Memberを再起動しようがデータは
なくなりません。
興味のある方は、クラスタを構成してデータを入れた後にLite Memberを全部落としたりしてみるとよいでしょう。
Near Cacheについて
Near Cacheについてですが、今回のようにJava EEの標準APIを使っている限りではあまり関係がありません。
そもそもNear Cacheとはなにか?という話ですが、自Nodeにデータがない場合には他Nodeにネットワーク越しにデータを
取りにいくわけですが、この結果をローカルのNodeにキャッシュしておく機能です。メモリ消費量が上がり、他Nodeのデータのコピーを
持つため一貫性が弱まるというトレードオフはありますが、性能を向上できる可能性があります。
Creating Near Cache for Map
JCache Near Cache
とはいえ、今回のようにPayara+Hazelcastでは
- Web Session … Near Cacheを設定しても、リクエストの最後にセッションを更新するためNear Cacheが破棄される
- Cache … Near CacheのサポートがClient/Server Modeのみのため、Embedded Modeを利用しているPayaraではそもそも使用できない
となります。なので、あんまり関係ない、と…。
ただ、Web Sessionについてはローカルにキャッシュされたセッションが使われるので、セッションパーシステンスしていればNear Cacheは
なくてもいい気がします。
https://github.com/payara/Payara/blob/payara-server-4.1.1.164/appserver/web/web-ha/src/main/java/org/glassfish/web/ha/session/management/ReplicationManagerBase.java#L138
それでもNear Cacheを使う場合は、Hazelcast固有のAPIを使うケースになるでしょう。
まとめ
Payaraの…といってもHazelcastの機能そのものですが、Lite Membersについてご紹介しました。
いかがでしょうか?使うかどうかはケースバイケースですが、知っていればクラスタの構成パターンについて取れる
バリエーションが増えると思います。クラスタ構成時の参考になれば、幸いです。
今回作成したコードは、こちらに置いています。
https://github.com/kazuhira-r/payara-advent-calendar/tree/master/2016
さらに蛇足ですが、実はHazelcast単独でのLite Membersについては前に取り上げていたりします。興味があれば、こちらもどうぞ。
HazelcastのLite Membersを試す - CLOVER