Azure Functions と Azure Event Hubs トリガーを使用して、堅牢で信頼性の高いサーバーレス ソリューションを構築する方法について説明します。 この記事では、チェックポイント、エラー処理、およびサーキット ブレーカー パターンの実装に関するベスト プラクティスについて説明します。これにより、イベントが失われず、イベントドリブン アプリケーションの安定性と回復性が維持されます。
分散システムにおけるイベント ストリームの課題
1 秒あたり 100 イベントの一定のレートでイベントを送信するシステムを考えてみましょう。 このペースでは、数分以内に複数の並列インスタンスが毎秒100件の受信イベントを処理できるようになります。
ただし、イベント ストリームを使用する場合は、次の課題を考慮してください。
- イベント発行元が破損したイベントを送信します。
- 関数コードでハンドルされない例外が発生します。
- ダウンストリーム システムがオフラインになり、イベント処理がブロックされます。
処理中にメッセージをロックする Azure Queue Storage トリガーとは異なり、Azure Event Hubs は、ストリーム内の単一のポイントからパーティションごとに読み取ります。 この読み取り動作は、ビデオ プレーヤーに似ていますが、高スループット、複数のコンシューマー グループ、再生機能の望ましい利点を提供します。 イベントはチェックポイントから前方または後方に読み取られますが、新しいイベントを処理するにはポインターを移動する必要があります。 詳細については、Event Hubs ドキュメントの チェックポイント を参照してください。
ストリームでエラーが発生し、ポインターを進めないことを選択した場合、それ以降のイベント処理はブロックされます。 つまり、1 つのイベントを処理する問題に対処するためにポインターを停止すると、未処理のイベントが重なり始めます。
関数は、成功または失敗に関係なく、ストリームのポインターを常に進めることでデッドロックを回避します。 ポインターは進み続けるため、関数はエラーに適切に対処する必要があります。
イベント ハブ トリガーがイベントを処理する方法
Azure Functions では、次の手順に従って、イベント ハブからのイベントを使用します。
- ポインターが作成され、イベント ハブのパーティションごとに Azure Storage に保持されます。
- 新しいイベントはバッチで受信され (既定)、ホストは処理のためにイベントのバッチを提供する関数をトリガーしようとします。
- 例外の有無にかかわらず関数の実行が完了すると、ポインターが詳細設定され、チェックポイントが既定のホスト ストレージ アカウントに保存されます。
- 条件によって関数の実行が完了しない場合、ホストはポインターを進められません。 ポインターを進めることができない場合は、後続の実行で同じイベントが再処理されます。
この動作により、いくつかの重要な点が明らかになります。
ハンドルされない例外により、イベントが失われる可能性があります。
例外を発生させる関数の実行では、ポインターが続行されます。 再試行ポリシーまたはその他の再試行ロジックを設定すると、再試行全体が完了するまでポインターが遅れます。
Functions では、 少なくとも 1 回の 配信が保証されます。
コードと依存システムでは、同じイベントが 2 回処理される可能性があるという事実を考慮する必要がある場合があります。 詳細については、 同一の入力に対する Azure Functions の設計に関するページを参照してください。
例外の処理
すべての関数コードには、最高レベルのコードで try/catch ブロック を含める必要があります。Event Hubs イベントを使用する関数では、 catch
ブロックを持つことがさらに重要です。 こうすることで、例外が発生すると、catch ブロックはポインターが進行する前にエラーを処理します。
再試行のメカニズムとポリシー
クラウド内の多くの例外は一時的なものであるため、エラー処理の最初の手順は常に操作を再試行することです。 組み込みの再試行ポリシーを適用することも、独自の再試行ロジックを定義することもできます。
再試行ポリシー
Functions には、Event Hubs の組み込みの再試行ポリシーが提供されています。 再試行ポリシーを使用する場合は、新しい例外を発生させるだけで、ホストは定義されたポリシーに基づいてイベントを再度処理しようとします。 この再試行動作には、バージョン 5.x 以降の Event Hubs 拡張機能が必要です。 詳細については、「再試行ポリシー」を参照してください。
カスタム再試行ロジック
関数自体で独自の再試行ロジックを定義することもできます。 たとえば、次の規則で示すワークフローに従うポリシーを実装できます。
- イベントを 3 回処理してみてください (再試行の間に遅延が発生する可能性があります)。
- すべての再試行の最終的な結果が失敗した場合は、ストリームで処理を続行できるように、キューにイベントを追加します。
- 破損したイベントまたは未処理のイベントは、後で処理されます。
注
Polly は、C# アプリケーションの回復性と一時的な障害処理ライブラリの例です。
例外以外のエラー
一部の問題は、例外が発生せずに発生する可能性があります。 たとえば、要求がタイムアウトしたり、関数を実行しているインスタンスがクラッシュしたりするケースを考えてみましょう。 関数が例外なしで完了しない場合、オフセット ポインターは進むことはありません。 ポインターが進まない場合、失敗した実行後に実行されるインスタンスは、引き続き同じイベントを読み取ります。 この状況では、 少なくとも 1 回の 保証が提供されます。
すべてのイベントが少なくとも 1 回処理されるという保証は、一部のイベントが複数回処理される可能性があることを意味します。 関数アプリでは、この可能性を認識する必要があり、アイデンポテンシーの原則に基づいて構築する必要があります。
エラー状態の処理
アプリは、イベント処理でいくつかのエラーを許容可能に処理できる場合があります。 ただし、ダウンストリーム処理で障害が発生した結果として発生する可能性がある永続的な障害状態を処理するように準備する必要もあります。 このような障害状態 (ダウンストリーム データ ストアがオフラインになっているなど) では、システムが正常な状態になるまで、関数はイベントのトリガーを停止する必要があります。
サーキット ブレーカー パターン
サーキット ブレーカー パターンを実装すると、アプリは効果的にイベント処理を一時停止し、後で問題が解決された後に再開できます。
イベント ストリーム プロセスでサーキット ブレーカーを実装するには、次の 2 つのコンポーネントが必要です。
- 回線の正常性を追跡および監視するために、すべてのインスタンスの共有状態。
open
またはclosed
として回線の状態を管理できるプライマリ プロセス。
実装の詳細は異なる場合がありますが、インスタンス間で状態を共有するには、ストレージ メカニズムが必要です。 状態は、Azure Storage、Redis Cache、または関数アプリ インスタンスからアクセスできるその他の永続的なサービスに格納できます。
Durable Functions と Azure Logic Apps の両方に、ワークフローと回線の状態を管理するためのインフラストラクチャが用意されています。 この記事では、Logic Apps を使用して関数の実行を一時停止および再起動し、サーキット ブレーカー パターンの実装に必要なコントロールを提供します。
インスタンス間で障害しきい値を定義する
複数のインスタンスがイベントを同時に処理している場合、回線の正常性を監視するには、永続化された共有外部状態が必要です。 その後、次のようなエラー状態を示すルールに基づいて、この永続化された状態を監視できます。
すべてのインスタンスで 30 秒間に 100 を超えるイベントエラーが発生した場合は、回線を中断して新しいイベントのトリガーを停止します。
この監視ロジックの実装の詳細は、特定のアプリのニーズによって異なりますが、一般的には次のシステムを作成する必要があります。
- 永続化されたストレージに対するエラーをログに記録します。
- 新しいエラーがログに記録されたときにローリング カウントを調べて、イベントエラーのしきい値が満たされているかどうかを判断します。
- このしきい値に達すると、回線を切断するようにシステムに指示するイベントを出力します。
Azure Logic Apps を使用した回線の状態の管理
Azure Logic Apps には、さまざまなサービス、機能、ステートフル オーケストレーションへの組み込みコネクタが付属しており、回線の状態を管理することは自然な選択です。 回線が中断する必要があるタイミングを検出したら、ロジック アプリを構築してこのワークフローを実装できます。
- 関数の処理を停止する Event Grid ワークフローをトリガーします。
- ワークフローを再起動するオプションを含む通知メールを送信します。
アプリ設定を使用して特定の関数を無効にして再び有効化する方法については、「 Azure Functions で関数を無効にする方法」を参照してください。
電子メール受信者は回線の正常性を調査し、必要に応じて通知メールのリンクを介して回線を再起動できます。 ワークフローが関数を再起動すると、イベントは最後のイベント ハブ チェックポイントから処理されます。
この方法を使用すると、イベントは失われず、イベントは順番に処理され、必要に応じて回線を中断できます。
Event Grid トリガーの移行戦略
リージョン間または一部のプラン間で既存の関数アプリを移行する場合は、移行プロセス中にアプリを再作成する必要があります。 この場合、移行プロセス中に、同じイベント ストリームから使用し、同じ出力先に書き込める 2 つのアプリがある場合があります。
移行プロセス中のイベント データの損失や重複を回避するには、 コンシューマー グループの使用 を検討する必要があります。
新しいターゲット アプリの新しいコンシューマー グループを作成します。
この新しいコンシューマー グループを使用するように、新しいアプリでトリガーを構成します。
これにより、両方のアプリが検証中に個別にイベントを処理できます。
新しいアプリがイベントを正しく処理していることを確認します。
元のアプリを停止するか、サブスクリプション/コンシューマー グループを削除します。