表面下の動作 - AWS Flow Framework Java 用

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

表面下の動作

タスク

非同期コードの実行を管理するために AWS Flow Framework for Java で使用する基本的なプリミティブは Task クラスです。Task 型のオブジェクトは、非同期に実行する必要がある仕事を表します。非同期メソッドを呼び出すと、フレームワークでは、そのメソッドのコードを実行するための Task を作成し、それをリストに入れて後で実行できるようにします。同様に、Activity を呼び出すと、それに対する Task が作成されます。この後でメソッド呼び出しが戻り、通常、呼び出しの将来の結果として Promise<T> を返します。

Task クラスはパブリックであり、直接使用できます。たとえば、Hello World の例を書き換えて、非同期メソッドの代わりに Task を使用できます。

@Override public void startHelloWorld(){ final Promise<String> greeting = client.getName(); new Task(greeting) { @Override protected void doExecute() throws Throwable { client.printGreeting("Hello " + greeting.get() +"!"); } }; }

フレームワークでは、Task のコンストラクタに渡したすべての Promise が準備完了状態になると、doExecute() メソッドを呼び出します。Task クラスの詳細については、AWS SDK for Java のドキュメントを参照してください。

また、フレームワークには Functor というクラスも含まれています。このクラスは、Promise<T> でもある Task を表します。Functor オブジェクトは、Task が完了すると、準備完了状態になります。次の例では、挨拶メッセージを取得するための Functor を作成しています。

Promise<String> greeting = new Functor<String>() { @Override protected Promise<String> doExecute() throws Throwable { return client.getGreeting(); } }; client.printGreeting(greeting);

実行順

タスクを実行できるのは、対応する非同期メソッドやアクティビティに渡したすべての Promise<T> で型指定されたパラメータが準備完了状態になった場合に限ります。実行準備が完了した Task は、準備完了キューに論理的に移動されます。つまり、実行が予定されます。ワーカークラスは、タスクを実行するために、ユーザーが非同期メソッドの本文を記述したコードを呼び出します。または、アクティビティメソッドの場合は、アクティビティタスクを Amazon Simple Workflow Service (AWS) でスケジュールします。

タスクが実行されて結果が生成されると、それに伴って他のタスクが準備完了状態となり、プログラムが続行されます。フレームワークでタスクを実行する方法は、非同期コードの実行順を理解するために重要です。コードは、プログラムに表示される順で実際に実行されるとは限りません。

Promise<String> name = getUserName(); printHelloName(name); printHelloWorld(); System.out.println("Hello, Amazon!"); @Asynchronous private Promise<String> getUserName(){ return Promise.asPromise("Bob"); } @Asynchronous private void printHelloName(Promise<String> name){ System.out.println("Hello, " + name.get() + "!"); } @Asynchronous private void printHelloWorld(){ System.out.println("Hello, World!"); }

上のコードの出力は以下のとおりです。

Hello, Amazon! Hello, World! Hello, Bob

これは期待と異なる結果ですが、非同期メソッドのタスクがどのように実行されたかを考えると簡単に説明できます。

  1. getUserName への呼び出しで Task が作成されます。これを Task1 とします。getUserName はパラメータを取らないため、Task1 はすぐに準備完了キューに置かれます。

  2. 次に、printHelloName への呼び出しで作成される Task では、getUserName の結果を待つ必要があります。これを Task2 とします。必要な値の準備が完了していないので、Task2 は待機リストに置かれます。

  3. 次に、 printHelloWorld のタスクが作成されて、準備完了キューに追加されます。これを Task3 とします。

  4. その後、println ステートメントで「Hello, Amazon!」が コンソールに出力されます。

  5. この時点で、Task1Task3 は準備完了キューにあり、Task2 は待機リストにあります。

  6. ワーカーによって Task1 が実行され、その結果によって Task2 が準備状態になります。Task2 は、Task3 の背景のキューに追加されます。

  7. Task3Task2 が、この順序で実行されます。

アクティビティの実行も同じパターンに従います。アクティビティクライアントでメソッドを呼び出すと、Task が作成され、これが実行されると、Amazon SWF でアクティビティがスケジュールされます。

フレームワークは、コード生成や動的プロキシなどの機能に依存することで、メソッド呼び出しをアクティビティ呼び出しや非同期タスクに変換するロジックをプログラムに挿入します。

ワークフロー実行

ワークフロー実装の実行もワーカークラスによって管理されます。ワークフロークライアントでメソッドを呼び出すと、Amazon SWF が呼び出されてワークフローインスタンスが作成されます。Amazon SWF のタスクはフレームワークのタスクとは異なるので、混同しないでください。Amazon SWF のタスクは、アクティビティタスクまたは決定タスクです。アクティビティタスクの実行はシンプルです。アクティビティワーカークラスは、Amazon SWF からアクティビティタスクを受け取り、実装の適切なアクティビティメソッドを呼び出して、その結果を Amazon SWF に返します。

決定タスクの実行はもう少し複雑です。ワークフローワーカーは Amazon SWF から決定タスクを受け取ります。決定タスクは、実質的には、ワークフローロジックに次に何をするかを問い合わせるリクエストです。ワークフロークライアントを通じてワークフローインスタンスが開始されると、最初の決定タスクが生成されます。この決定タスクを受け取ると、フレームワークは @Execute 注釈が設定されたワークフローメソッドでコードの実行を開始します。このメソッドは、アクティビティをスケジュールする調整ロジックを実行します。ワークフローインスタンスの状態が変わると (例えばアクティビティが完了する)、さらに決定タスクがスケジュールされます。この時点で、ワークフローロジックはアクティビティの結果に基づいてアクションを実行することを決定できます。たとえば、別のアクティビティをスケジュールすることを決定できます。

フレームワークは、決定タスクをワークフローロジックにシームレスに変換することで、これらすべての詳細を開発者から隠します。開発者からは、コードが通常のプログラムのように見えます。表面下では、フレームワークは Amazon SWF に保持されている履歴を使用して、コードを Amazon SWF や決定タスクへの呼び出しにマッピングします。決定タスクが到着すると、フレームワークはプログラムの実行を再生し、その結果を現時点までの完了済みアクティビティの結果に追加します。これらの結果を待機していた非同期メソッドやアクティビティがブロック解除され、プログラムの実行が先に進みます。

イメージ処理ワークフロー例の実行および対応する履歴を次の表に示します。

サムネイルワークフローの実行
ワークフロープログラムの実行 Amazon SWF で管理される履歴
最初の実行
  1. ディスパッチループ

  2. getImageUrls

  3. downloadImage

  4. createThumbnail (待機キューのタスク)

  5. uploadImage (待機キューのタスク)

  6. <ループの次のイテレーション>

  1. ワークフローインスタンス開始、id= "1 "

  2. downloadImage スケジュール

再生
  1. ディスパッチループ

  2. getImageUrls

  3. downloadImage イメージ パス ="foo"

  4. createThumbnail

  5. uploadImage (待機キューのタスク)

  6. <ループの次のイテレーション>

  1. ワークフローインスタンス開始、id= "1 "

  2. downloadImage スケジュール

  3. downloadImage 完了、戻り値 ="foo"

  4. createThumbnail スケジュール

再生
  1. ディスパッチループ

  2. getImageUrls

  3. downloadImage イメージ パス ="foo"

  4. createThumbnail サムネイル パス ="bar"

  5. uploadImage

  6. <ループの次のイテレーション>

  1. ワークフローインスタンス開始、id= "1 "

  2. downloadImage スケジュール

  3. downloadImage 完了、戻り値 ="foo"

  4. createThumbnail スケジュール

  5. createThumbnail 完了、戻り値 ="bar"

  6. uploadImage スケジュール

再生
  1. ディスパッチループ

  2. getImageUrls

  3. downloadImage イメージ パス ="foo"

  4. createThumbnail サムネイル パス ="bar"

  5. uploadImage

  6. <ループの次のイテレーション>

  1. ワークフローインスタンス開始、id= "1 "

  2. downloadImage スケジュール

  3. downloadImage 完了、戻り値 ="foo"

  4. createThumbnail スケジュール

  5. createThumbnail 完了、戻り値 ="bar"

  6. uploadImage スケジュール

  7. uploadImage 完了

    ...

processImage を呼び出すと、フレームワークは Amazon SWF に新しいワークフローインスタンスを作成します。これは、開始するワークフローインスタンスの永続的なレコードです。プログラムは、downloadImage アクティビティへの呼び出しまで実行されます。この呼び出しでアクティビティをスケジュールすることを Amazon SWF にリクエストします。ワークフローはさらに実行され、後続のアクティビティのタスクを作成しますが、downloadImage アクティビティが完了するまで実行できません。したがって、このリプレイのエピソードは終了します。Amazon SWF は、実行のための downloadImage アクティビティのタスクをディスパッチし、タスクが完了すると、結果とともに履歴にレコードが作成されます。ワークフローは続行の準備が完了し、決定タスクが Amazon SWF で生成されます。フレームワークは決定タスクを受け取ってワークフローを再生し、その結果を履歴に記録されたダウンロード済みイメージの結果に追加します。これに伴って createThumbnail のタスクがブロック解除され、プログラムは Amazon SWF の createThumbnail アクティビティタスクがスケジュールされることで続行します。同じプロセスが uploadImage で繰り返されます。このようにプログラムの実行が継続され、最終的にワークフローですべてのイメージが処理され、保留中のタスクがなくなります。実行の状態はローカルに保存されないため、各決定タスクは異なるマシンで実行することもできます。これにより、フォールトトレラントでスケーラブルなプログラムを簡単に記述できます。

非決定論

フレームワークは再生に依存するため、オーケストレーションコード (アクティビティ実装を除くすべてのワークフローコード) は決定論的であることが重要です。たとえば、プログラムの制御フローは乱数や現在の時間に依存すべきではありません。これらは呼び出し間で変化するため、再生はオーケストレーションロジックを通じて同じパスに従わない場合があります。そのため、予期しない結果やエラーの原因となります。現在の時間を決定論的に取得するには、フレームワークが提供する WorkflowClock を使用できます。詳細については、「実行コンテキスト」セクションを参照してください。

注記

Spring のワークフロー実装オブジェクトのワイヤリングが不正確である場合にも非決定論につながることがあります。ワークフロー実装の Bean およびこれらが依存する Bean は、ワークフロースコープ (WorkflowScope) に存在する必要があります。たとえば、ワークフロー実装の Bean を、グローバルコンテキストで状態を保持する Bean にワイヤリングすると、予期しない動作が発生します。詳細については、「Spring との統合」セクションを参照してください。