2013年6月28日金曜日

JBoss Data Gridでトランザクション機能を使ってみる

JBossテクニカルチームの寺田です。

今回はJBossDataGirdのトランザクション機能を使ってみます。
トランザクションはライブラリモードのみでサポートされ、リモートクライアントサーバモードではサポートされ
ません。
ライブラリモードでサポートされるということは、ライブラリモード上でのクラスタモード(分散およびレプリケ
ーション)でもサポートされるはずなのでその際の振る舞いを見てみようと思います。
前回の記事では、トランザクション関係のイベントが見れていなかったので、今回はトランザクション機能の調査をしつつトランザクション関連のイベントの 確認も行ってみます。
また、サンプルとしては、前回作成したclustered-cache-listenerを元にclustered-cache-listener-txとしました。

Agenda

トランザクションマネージャの設定

ロックを獲得させるたために以下の(A)のように悲観的ロックに設定(lockMode())
.transaction()
.transactionMode(org.infinispan.transaction.TransactionMode.TRANSACTIONAL)
.lockingMode(org.infinispan.transaction.LockingMode.PESSIMISTIC)  : (A)
.autoCommit(true)
.transactionManagerLookup(new GenericTransactionManagerLookup()).locking()
.lockAcquisitionTimeout(300000)  : (B)
実行してみると、以下の赤字部分を確認するとイベントが発生していることがわかる。
INFO: Cluster formed successfully!
2013/05/01 17:56:21 org.infinispan.quickstart.clusteredcache.util.LoggingListene
r observeTransactionRegistered
INFO: ###Transaction Registered
2013/05/01 17:56:21 org.infinispan.quickstart.clusteredcache.util.LoggingListene
r observeTransactionCompleted
INFO: ###Transaction Completed
2013/05/01 17:56:21 org.infinispan.quickstart.clusteredcache.util.LoggingListene
r observeEvicted
Node1とNode2でキーと値を入力させるようにして、以下のような順で操作を行う
・Node1でkey = key1,value=value1_1で登録し、ロックを獲得
・Node2でkey = key1,value=value2_1で登録
と順に行うと、以下のようにput()の呼び出しでNode2の処理が待つため、Node2がput()の
呼び出しでkey1のロックを獲得に行っていることがわかります。
   cache.put(key,value);
また、トランザクションマネージャの設定の(B)の箇所でロック獲得時のタイムアウト時間を設定しています。
この設定がないと10秒程度でTimeoutExceptionが発生してしまいます。

クラスターワイドロック

Cache.lock(key)でクラスターワイドロックというクラスター間で有効なロックが獲得できます。
クラスターワイドロックを獲得するには、以下のようにAdvanceCacheを
取得してからlock()メソッドを呼び出してやる必要があります。
このlock()メソッドでロックを獲得すると、クラスターのNode1で
ロック獲得後、Node2で同じキーをロック獲得しようとするとロック獲得
待ちが発生し、Node1にてロックが獲得されていることがわかります。
   cache.getAdvancedCache().lock(key);

デッドロック

デッドロックの確認を行うために、デッドロックの検知の設定も行います。
以下のキャッシュマネージャの設定でデッドロックの検知の設定(deadlockDetection().enable())を追加しています。
さらに、時間設定のパラメータは以下のように設定する必要があります。
spinDuration < lockAcquisitionTimeout < replTimeout
そこで以下のように設定しました。
spinDuration(60000msec) < lockAcquisitionTimeout(120000msec) < replTmeout(300000msec)
具体的には、キャッシュマネージャの設定で以下のように赤字部分のように設定します。
      .clustering()
      .cacheMode(CacheMode.DIST_SYNC)
      .hash().numOwners(2)
      .sync().replTimeout((long)300000).clustering()
      .eviction().strategy(LIRS).maxEntries(5)
      .loaders().passivation(true).addFileCacheStore().location("store")
      .transaction().transactionMode(org.infinispan.transaction.TransactionMode.TRANSACTIONAL)
      .lockingMode(org.infinispan.transaction.LockingMode.PESSIMISTIC).autoCommit(true)
      .transactionManagerLookup(new GenericTransactionManagerLookup()).locking().lockAcquisitionTimeout(120000)
      .deadlockDetection().enable().spinDuration(60000)
      .build();
デッドロックが発生するように、以下の操作を行います。
Node1にて、key1,value1_1を登録
Node2にて、key2,value2_2を登録
Node1にて、key2,value2_1を登録(ロック獲得待ちになる)
Node2にて、key1,value1_2を登録(ロック獲得待ちになる)
しばらく放置すると、Node1でデッドロックが検出され、Node2は成功することを確認できました。
Node1
Exception in thread "main" org.infinispan.util.concurrent.locks.DeadlockDetected
Exception: Deadlock found and we DldGlobalTransaction{coinToss=-5296779933095986
486, isMarkedForRollback=false, lockIntention=key2, affectedKeys=[], locksAtOrig
in=[]} GlobalTransaction::6:local shall not continue. Other tx is
 DldGlobalTransaction{coinToss=1880233465788899185, isMarkedForRollback=false, l
ockIntention=key1, affectedKeys=[], locksAtOrigin=[]} GlobalTransaction::3:remote
        at org.infinispan.util.concurrent.locks.DeadlockDetectingLockManager.loc
kAndRecord(DeadlockDetectingLockManager.java:107)
        at org.infinispan.util.concurrent.locks.LockManagerImpl.lock(LockManager
Impl.java:203)
        at org.infinispan.util.concurrent.locks.LockManagerImpl.acquireLock(Lock
ManagerImpl.java:186)
        at org.infinispan.util.concurrent.locks.LockManagerImpl.acquireLock(Lock
ManagerImpl.java:176)
        at org.infinispan.interceptors.locking.AbstractTxLockingInterceptor.lock
spinDuration等のパラメータの初期設定や単位については、ソースをみると、デフォルト値は100ms、
単位はmsなので1000を設定すると1sでデッドロックが検出されることになります。
どこかに説明が書いてあるのかも知れませんが、オープンソースなので、ソースを見ればわかります。
public class DeadlockDetectionConfigurationBuilder extends AbstractConfigurationChildBuilder {

   private boolean enabled = false;
   private long spinDuration = TimeUnit.MILLISECONDS.toMillis(100);
途中いろいろ試行錯誤したのですが、lock()でデッドロックを発生させようとすると以下のようにパラメータ
についてのワーニングが出ます。ワーニング表示は非常に重要ですね。
WARN: ISPN000148: Invalid : spinDuration value of 3000000. It
 can not be higher than :lockAcquisitionTimeout  which is 300000
2013/05/15 17:39:30 org.infinispan.config.TimeoutConfigurationValidatingVisitor
visitConfiguration
WARN: ISPN000148: Invalid :lockAcquisitionTimeout  value of 300000. It
can not be higher than :replTimeout which is 15000
この時間系のパラメータの大小の相関関係が間違っていると以下のようにTimeoutExceptionが発生し非常に悩まされますので注意してください
ERROR: ISPN000136: Execution error
org.infinispan.util.concurrent.TimeoutException: Timed out after 15 seconds wa…  

サンプル

今回もサンプルコードをgithubにアップしました
https://github.com/SIOS-OSS/clustered-cache-listener-tx

以上、トランザクション機能を使ってみました。
ライブラリモードでしか使えないのですが、依存関係のあるデータで整合性を確保するには
トランザクション機能は必要な機能ですので、理解するうえでの助けになれば幸いです。
ここまで、お付き合いいただきありがとうございました。
また、前回のイベントのまとめの表に今回確認したトランザクションに関するものを追記しております。

0 件のコメント:

コメントを投稿