ゲートウェイ集約パターン

Azure の Traffic Manager

ゲートウェイを使用して、複数の個々の要求を 1 つの要求に集約します。 このパターンは、クライアントが操作を実行するために、さまざまなバックエンド システムに複数の呼び出しを行う必要がある場合に便利です。

コンテキストと問題

1 つのタスクを実行するには、クライアントはさまざまなバックエンド サービスへの呼び出しを行う必要があります。 タスクを実行するのに多くのサービスに依存するアプリケーションでは、要求ごとにリソースを展開する必要があります。 アプリケーションに新しい機能やサービスが追加されると、追加の要求が必要になり、リソース要件およびネットワーク呼び出しが増加します。 このクライアントとバックエンド間の頻繁な通信が、アプリケーションのパフォーマンスとスケールに悪影響を及ぼす可能性があります。 サービス間で大量の呼び出しがある小さなサービスを中心にアプリケーションが構築されるマイクロサービス アーキテクチャでは、より頻繁にこの問題が発生します。

次の図では、クライアントは、各サービスに要求を送信します (1、2、3)。 サービスはそれぞれ要求メッセージを処理し、アプリケーションに応答を送り返します (4、5、6)。 一般的に待機時間の長い携帯ネットワークでは、この方法で個々の要求を使用することは効率が悪く、接続が切断されたり、要求が不完全になったりする可能性があります。 要求は並列で実行できますが、アプリケーションは各要求のデータをすべて個別の接続で送信、待機、および処理する必要があるため、エラーが発生する可能性が高くなります。

ゲートウェイ集約パターンの問題の図

解決策

ゲートウェイを使用すると、クライアントとサービス間の頻繁な通信を削減できます。 ゲートウェイはクライアント要求を受信し、さまざまなバックエンド システムに要求をディスパッチして結果を集計し、それらを要求元のクライアントに送り返します。

このパターンでは、アプリケーションがバックエンド サービスに対して行う要求の数を削減して、待機時間の長いネットワーク経由でのアプリケーションのパフォーマンスを向上することができます。

次の図では、アプリケーションはゲートウェイに要求を送信します (1)。 要求には、追加の要求のパッケージが含まれています。 ゲートウェイがこれらを分解し、関連するサービスに各要求を送信して (2) 処理します。 各サービスが応答をゲートウェイに返します (3)。 ゲートウェイは、各サービスからの応答を結合し、アプリケーションに応答を送信します (4)。 アプリケーションは、1 つの要求を行い、ゲートウェイから 1 つの応答のみを受信します。

ゲートウェイ集約パターンの解決策の図

問題と注意事項

  • ゲートウェイがバックエンド間のサービス結合を行わないようにする必要があります。
  • できる限り待機時間を短縮するため、ゲートウェイはバックエンド サービスの近くに配置する必要があります。
  • ゲートウェイ サービスによって、単一障害点が生じる可能性があります。 アプリケーションの可用性の要件が満たされるように、ゲートウェイが適切に設計されていることを確認してください。
  • ゲートウェイによって、ボトルネックが生じる可能性があります。 負荷を処理するための適切なパフォーマンスがゲートウェイに備わっており、予測した成長に応じて拡張できることを確認してください。
  • サービスの連鎖的なエラーが発生しないように、ゲートウェイに対してロード テストを実行します。
  • バルクヘッドサーキット ブレーク再試行、タイムアウトなどの手法を使用して、耐障害性のある設計を実装します。
  • 1つまたは複数のサービス コールに時間がかかりすぎる場合は、タイムアウトして一部のデータ セットを返すこともできます。 アプリケーションがこのシナリオを処理する方法を検討してください。
  • 非同期 I/O を使用して、バックエンドでの遅延によってアプリケーションでパフォーマンスの問題が発生しないようにしてください。
  • 相関 ID を使用して分散トレースを実装し、個々の呼び出しを追跡します。
  • 要求メトリックおよび応答のサイズを監視します。
  • エラーを処理するため、キャッシュ データをフェールオーバー戦略として返すことを検討してください。
  • ゲートウェイに集約を構築する代わりに、ゲートウェイの背後に集約サービスを配置することを検討してください。 要求の集約には、ゲートウェイの他のサービスとは異なるリソース要件がある可能性があり、ゲートウェイのルーティングおよびオフロード機能に影響を及ぼすことがあります。

このパターンを使用する状況

このパターンは次の状況で使用します。

  • クライアントは、操作を実行するために、複数のバックエンド サービスと通信する必要があります。
  • クライアントは、携帯ネットワークなどの待機時間が長いネットワークを使用することがあります。

このパターンは、次の状況では適切でない可能性があります。

  • 複数の操作で、クライアントと 1 つのサービス間の呼び出しの回数を削減したい場合。 このシナリオでは、サービスにバッチ操作を追加すると効果的である可能性があります。
  • クライアントまたはアプリケーションはバックエンドサービスの近くにあり、レイテンシは重要な要素ではありません。

ワークロード設計

設計者は、e Gateway Aggregation pattern can be used in their workloads's design to address the goals and principles covered in the Azure Well-Architected Framework の柱で説明されている目標と原則に対処するために、ゲートウェイ集約パターンをワークロードの設計でどのように使用できるかを評価する必要があります。 次に例を示します。

重要な要素 このパターンが柱の目標をサポートする方法
信頼性設計の決定により、ワークロードが誤動作に対して復元力を持ち、障害発生後も完全に機能する状態に回復することができます。 このトポロジを使用すると、クライアント間で一時的な障害処理を分散実装から集中実装に移行できます。

- RE:07 一時的な障害
セキュリティ設計の決定により、ワークロードのデータとシステムの機密性整合性、および可用性が確保されます。 このトポロジにより、クライアントがシステムに接続するタッチ ポイントの数が減少し、パブリックサーフェス領域と認証ポイントが減少します。 集約バックエンドは、クライアントから完全にネットワーク分離されたままにすることができます。

- SE:04 セグメンテーション
- SE:08 強化
オペレーショナルエクセレンス は、標準化されたプロセスとチームの結束によってワークロードの品質を提供します。 このパターンにより、バックエンドロジックはクライアントから独立して進化することができ、クライアントのタッチポイントを変更することなく、チェーン化されたサービス実装やデータソースまで変更できます。

- OE:04 ツールとプロセス
パフォーマンスの効率化は、スケーリング、データ、コードを最適化することによって、ワークロードが効率的にニーズを満たすのに役立ちます。 この設計では、クライアントが複数の接続を確立する設計よりもレイテンシが小さくなります。 集約実装でのキャッシュにより、バックエンドシステムへのコールが最小限に抑えられます。

- PE:03 サービスの選択
- PE:08 データパフォーマンス

設計決定と同様に、このパターンで導入される可能性のある他の柱の目標とのトレードオフを考慮してください。

次の例では、Lua を使用してシンプルなゲートウェイ集約 NGINX サービスを作成する方法を示します。

worker_processes  4;

events {
  worker_connections 1024;
}

http {
  server {
    listen 80;

    location = /batch {
      content_by_lua '
        ngx.req.read_body()

        -- read json body content
        local cjson = require "cjson"
        local batch = cjson.decode(ngx.req.get_body_data())["batch"]

        -- create capture_multi table
        local requests = {}
        for i, item in ipairs(batch) do
          table.insert(requests, {item.relative_url, { method = ngx.HTTP_GET}})
        end

        -- execute batch requests in parallel
        local results = {}
        local resps = { ngx.location.capture_multi(requests) }
        for i, res in ipairs(resps) do
          table.insert(results, {status = res.status, body = cjson.decode(res.body), header = res.header})
        end

        ngx.say(cjson.encode({results = results}))
      ';
    }

    location = /service1 {
      default_type application/json;
      echo '{"attr1":"val1"}';
    }

    location = /service2 {
      default_type application/json;
      echo '{"attr2":"val2"}';
    }
  }
}