ビジネスアプリケーション向けの並列プログラミングパターンの機能[PoEAA]

並列プログラミングは、長い間、経験豊富な教祖であることがなくなりました。 実装がこの問題を無視する最新のアプリケーションを想像することは困難です。 同様に、ファウラーと会社の企業アプリケーションのパターンを装備していないアプリケーションプログラマを想像することは困難です。 実際、これらのパターンに固有の機能については、この投稿で説明します。 以下に示す資料は、実装技術とはほとんど関係ないため、サンプルがJavaおよびPostgreSQLに基づいているという事実にもかかわらず、すべてのアプリケーションプログラマが関心を持つ可能性があります。 もう1つ注意してください。パターンの名前を混同しないように、元の名前を英語で使用します。



同時アクセスのさまざまなレベルでのオフラインロックパターンの実装動作[楽観的オフラインロック、悲観的オフラインロック]



このセクションでは、オフラインロックパターンの実装に基づいて、2種類の楽観的および悲観的コントロールの同時アクセスのさまざまなレベルでの動作について説明します。 動作は、合成性能テストの結果に基づいて考慮されます。 パフォーマンス自体がソフトウェアにとって常に問題になるわけではありません。 それはすべて特定の状況に依存します。 たとえば、操作の速度がユーザー入力の速度によって制限されるシステムでは、これらの操作のパフォーマンスが不十分であるという問題が発生することはほとんどありません。 逆に、操作の速度がハードウェアの機能と実装によってのみ制限されるシステムでは、パフォーマンスの不足が大きな問題になる可能性があります。



異なるレベルの並列アクセスでOptimistic Offline LockおよびPessimistic Offline Lockパターンを実装するための、楽観的および悲観的タイプの同時アクセス制御のパフォーマンスの測定を始めましょう。 この測定には、テスト結果を受け取るデータを含むテーブルが必要です。 テーブルレイアウトとテストデータを以下に示します。



CREATE TABLE values ( "id" integer PRIMARY KEY, "field1" varchar(4096), "field2" decimal(20, 2), "locked" boolean, "version" integer DEFAULT 0 ); INSERT INTO values ( "id", "field1", "field2", "locked", "version" ) VALUES ( 1,'record 1', 0.00, false, 0 );
      
      





どの測定が行われるかに基づいて、テストアプリケーションの実装を検討します。 実装では、Javaフレームワークで最も一般的なものの1つであるSpring FrameworkおよびHibernateテクノロジーを使用します。 フレームワークでデータを使用することで、テストコードをできる限り簡潔で理解しやすくすることができました。 まず、エンティティクラスを考慮します(値テーブルの行を反映します)。



 @Entity @Table(name="values", schema="public") public class Value { @Id private Integer id; private String field1; private BigDecimal field2; private Boolean locked; private Integer version; //   }
      
      





各オフラインロックパターンの実装は、DAOとTaskの2つのクラスで構成されています。 DAOは、データベースにアクセスしてロックをチェックするためのすべてのロジックを実装します。 タスクでは、同時アクセス制御の制御の種類とテスト用の特定のコードを考慮してデータを変更するためのコード。 注1:悲観的オフラインロックは、2つのパターン(悲観的および楽観的)の実装間の類似性を最大化するために、ロックマネージャーパターンを使用せずに実装されます。 注2:Optimistic Offline Lockパターンを実装する場合、類似の理由により、実際のアプリケーションでは手動のバージョン管理が再度使用され、組み込みのHibernateメカニズムを使用することが推奨されます。



楽観的オフラインロック実装コード



 @Repository("optimisticDao") @Scope("prototype") public class ValueDaoWithOptimisticControl implements ValueDao { //    @Transactional @Override public Value loadValue(Integer id) { //    Value value = (Value) sessionFactory.getCurrentSession().get( Value.class, id ); return value; } @Transactional @Override public void storeValue(Value value) { //          Value oldValue = (Value) sessionFactory.getCurrentSession().get( Value.class, value.getId(), new LockOptions(LockMode.PESSIMISTIC_WRITE) ); if (!value.getVersion().equals(oldValue.getVersion())) throw new OptimisticLockingFailureException("Value was modified"); oldValue.setField1(value.getField1()); oldValue.setField2(value.getField2()); oldValue.setVersion(value.getVersion() + 1); } } @Component("optimisticTest1") @Scope("prototype") public class ValueTaskWithOptimisticControl implements ValueTask { //    @Override public void run() { try { startLatch.await(); } catch (InterruptedException e2) { Thread.currentThread().interrupt(); } while (!Thread.currentThread().isInterrupted()) { Value value = null; //  try { value = valueDao.loadValue(id); } catch (NestedRuntimeException e) { continue; } value.setField2(value.getField2().add(BigDecimal.ONE)); //     try { valueDao.storeValue(value); countOfChanges++; } catch (NestedRuntimeException e) { //  try { Thread.sleep(300); } catch (InterruptedException e1) { Thread.currentThread().interrupt(); break; } continue; } } finishLatch.countDown(); } }
      
      





悲観的オフラインロック実装コード



 @Repository("pessimisticDao") @Scope("prototype") public class ValueDaoWithPessimisticControl implements ValueDao { //    @Transactional @Override public Value loadValue(Integer id) { //          Value value = (Value) sessionFactory.getCurrentSession().get( Value.class, id, new LockOptions(LockMode.PESSIMISTIC_WRITE) ); if (value.isLocked()) throw new PessimisticLockingFailureException("Value have already locked"); value.setLocked(true); return value; } @Transactional @Override public void storeValue(Value value) { //          Value oldValue = (Value) sessionFactory.getCurrentSession().get( Value.class, value.getId(), new LockOptions(LockMode.PESSIMISTIC_WRITE) ); if (!oldValue.isLocked()) throw new PessimisticLockingFailureException("Value have not locked"); oldValue.setField1(value.getField1()); oldValue.setField2(value.getField2()); oldValue.setLocked(false); } } @Component("pessimisticTest1") @Scope("prototype") public class ValueTaskWithPessimisticControl implements ValueTask { //    @Override public void run() { try { startLatch.await(); } catch (InterruptedException e2) { Thread.currentThread().interrupt(); } while (!Thread.currentThread().isInterrupted()) { Value value = null; //    . //       . while (value == null) { try { value = valueDao.loadValue(id); } catch (NestedRuntimeException e) { try { Thread.sleep(300); } catch (InterruptedException e1) { Thread.currentThread().interrupt(); break; } } } if (value == null) continue; value.setField2(value.getField2().add(BigDecimal.ONE)); try { valueDao.storeValue(value); countOfChanges++; } catch (NestedRuntimeException e) { continue; } } finishLatch.countDown(); } }
      
      





完全なテストコードに興味がある人は誰でもアーカイブをダウンロードして、自分で実験することができます。 プロジェクトをビルドするには、maven 3を使用します。プロジェクトディレクトリから次のコマンドを使用してテストを実行できます。



 export MAVEN_OPTS=-server mvn exec:java -Dexec.mainClass="concurrency.patterns.Application" \ -Dexec.args="-time ___ \ -concurrency_rate _ -id _ -action _"
      
      





さまざまなレベルの同時アクセス(10秒の実行間隔で指定)を使用したテストの結果を以下のグラフに示します。 測定は4コアCPUで行われました。











率直に言って、私にとってこの結果は少し予想外でした。 悲観的オフラインロックの低下の程度ははるかに少ないと期待していました。 これらの結果によると、両方のアプローチが同等のレベルの劣化を持っていることは明らかです。 最初のテスト実装では、見落としのため、ValueDaoWithOptimisticControlクラスのデータにロックキャプチャをロードしました。これにより、オプティミスティックオフラインロックの実装がさらに劣化しました。 最終的な実装では、アクションを実行するデータベースクエリの数が多くなるため、悲観的オフラインロックは急激に低下します。 悲観的なタイプの同時アクセス制御では、データベースへの4つのクエリが実行されます(2つの更新の選択と2つの更新のインストールとロックの解放)、楽観的な3つのクエリの実行(1つの選択、2つの更新と更新の選択)。 実際、パフォーマンスの全体的な違いはリクエストの違いに起因します。これは、リクエストに費やされる主な時間だからです。 実際、どちらのアプローチもデータベースの行に対するアクションの実行のシリアル化を提供するため、最終結果はテスト実行時間を1つのアクションの実行時間で割った値にほぼ等しくなります。 動作の主な違いは、結果が保存されることを保証せずにアクションを実行することです。 つまり、オプションのリソース消費。 このような動作は、楽観的な種類の制御の特徴です。 これは、異常終了時に占有されたリソースを解放する必要がないための料金です。



上記のパターンの実装は最も生産的ではありません。悲観的なアプローチのための2つの更新リクエストと、楽観的なアプローチの更新リクエストを含む選択リクエストにロードと保存を減らすことで、さらに絞り込めます。 この最適化は、私たちの文脈では基本的に重要ではありません。



この段落の終わりに、いくつかのポイントを強調したいと思います。 第一に、役に立たないアクションに問題がない場合は常に楽観的なアプローチを選択するというFullerの推奨は、最適と見なすことができます。 第二に、両方のアプローチはアクションのシリアル化を提供します。これにより、同時アクセスのレベルを上げながら、同程度の劣化を保証します。 最終的に、動作は実装と実行コンテキストに大きく依存します。 第三に、オフラインロックパターンを使用して、ユーザー入力に応じて依存関係のない実行スレッドの同時アクセスを制御する際のパフォーマンス要件を明確に区別する必要があります。



クラッシュ時にキャプチャされたリソース[悲観的オフラインロック]



悲観的オフラインロックパターンの重大な欠点は、緊急時にキャプチャされたリソースの問題です。 誰が、いつ異常終了した実行スレッドによって占有されているリソースを解放する必要があるか。 これらの質問は、アプリケーションのコンテキストで、ペシミスティックオフラインロックパターンを使用する開発者が回答する必要があります。



イベントロックをリセットする


明らかに、所有者が跡形もなく消えたロックをリセットするには、何らかの刺激信号(イベント)が必要です。 このようなイベントは、たとえば、クライアントからの切断、ロックを保持する期間の満了、または単にシステムにサービスを提供するユーザーのアクションです。



接続ドロップトラッキングアプローチは、クライアントとアプリケーションサーバーが接続ステータスをサポートするプロトコルを介して対話する場合に適用できます。 このコンテキストでは、接続状態のサポートがどのレベルで実装されるかは特に重要ではありません。 これは、tcp接続の中断またはWebクライアントのセッション状態を追跡できます。



ロックホールドタイムアウトは、問題を解決するためのかなり簡単な方法です。 ロックがキャプチャされると、タイムスタンプが設定され、期限切れのロック時間でリソースを解放できます。 この単純さにもかかわらず、このアプローチには欠点があります。 その1つは、長い時間間隔と、長時間ロックを保持する顧客のニーズとの間で妥協点を見つける必要があることです。



説明されている2つのアプローチは、指定された間隔でクライアントに信号を送信するか、ロックキャプチャタイムスタンプを更新することにより、接続状態を監視できる混合形式で組み合わせることができます。



悲観的なアプローチを使用しないでください


当たり前のことではありませんが、リソースの解放に関する問題は、リソースを占有することなく簡単に解決できます。 同時アクセスを制御するには、オプティミスティックオフラインロックを使用するか、ロックを破棄して、以前のロックをすべて消去するロックに基づいてデータを更新します。 ただし、2番目のオプションは汎用的ではなく、特定のクラスのアプリケーションでのみ使用できます。 さらに、ノンブロッキングアルゴリズムは非常に複雑であり、高いサポートコストがかかります。 ノンブロッキングアプローチのオプションは、実際に正当化されたパフォーマンス要件を持つ出口として使用する必要があります。



PoEAA並列プログラミングパターンの適用



ロックパターンは、ビジネストランザクション全体で同時アクセスを制御する手段です。 ビジネストランザクションは、いくつかのシステムトランザクションに拡張できます。これにより、限られたシステムトランザクションの範囲でDBMS同期メカニズムを使用できなくなります。 さらに、長いシステムトランザクションと長いロックキャプチャは、メンテナンスのためのデータベースの可用性に関する問題の原因になります。 この場合のパフォーマンスの問題は、特定の実装メカニズムではなく、ロックを使用するポリシーにより依存しているため、問題ではありません。



PoEAA並列プログラミングパターンの一般的な目的は、ビジネスレイヤーレベルでの同時アクセスの問題を解決することです。 つまり、別個の同期メカニズムの構築です。 それにもかかわらず、それらを正しく実装するには、DBMSを提供する低レベルのメカニズム(ロック、トランザクション分離)を理解する必要があります。



追加のソース



テストアプリケーションのコードでアーカイブする

悲観的なオフラインロック

楽観的オフラインロック



All Articles