Javaにおけるアンバサダーパターン:リモートリソース管理の簡素化
アンバサダーデザインパターンの目的
Javaのアンバサダーパターンは、監視、ロギング、ルーティングなどの共通機能を共有リソースからヘルパーサービスインスタンスにオフロードすることで、分散システムのパフォーマンスと保守性を向上させます。
実際の例を用いたアンバサダーパターンの詳細な説明
実際の例
レストランの予約、イベントチケット、または交通手段の手配をゲストが頻繁にリクエストする、賑やかなホテルを想像してみてください。 各ゲストが個別にこれらのサービスに連絡する代わりに、ホテルはコンシェルジュを提供します。 コンシェルジュは、ゲストに代わってこれらのタスクを処理し、予約がスムーズに行われ、チケットが時間通りに予約され、交通手段が効率的にスケジュールされるようにします。
この例えでは、ゲストはクライアントサービス、外部プロバイダー(レストラン、チケット販売業者、交通機関)はリモートサービス、コンシェルジュはアンバサダーサービスを表しています。 この設定により、ゲストは滞在を楽しむことに集中でき、コンシェルジュは外部とのやり取りの複雑さを管理し、シームレスで強化されたエクスペリエンスを提供します。
簡単な言葉で
アンバサダーパターンを使用すると、クライアントからのポーリング頻度を減らし、レイテンシチェックとロギングを実装できます。
Microsoftのドキュメントによると
アンバサダーサービスは、クライアントと同じ場所に配置されたアウトオブプロセスプロキシと考えることができます。 このパターンは、監視、ロギング、ルーティング、セキュリティ(TLSなど)、および回復性パターンなどの一般的なクライアント接続タスクを、言語に依存しない方法でオフロードするのに役立ちます。 多くの場合、レガシーアプリケーション、または変更が困難な他のアプリケーションで使用され、ネットワーク機能を拡張します。 また、専門チームがこれらの機能を実装することもできます。
Javaにおけるアンバサダーパターンのプログラム例
Javaのアンバサダーパターンのこの例では、システムの信頼性を向上させるために、レイテンシチェック、ロギング、および再試行メカニズムを実装する方法を示します。
リモートサービスには、提供する関数にアクセスする多くのクライアントがあります。 サービスはレガシーアプリケーションであり、更新できません。 ユーザーからの大量のリクエストにより、接続の問題が発生しています。 リクエスト頻度の新しいルールを、レイテンシチェックとクライアント側のロギングと共に実装する必要があります。
上記の導入を念頭に置いて、この例では機能を模倣します。 リモートサービスとアンバサダーサービスの両方によって実装されるインターフェースがあります。
interface RemoteServiceInterface {
long doRemoteFunction(int value) throws Exception;
}
シングルトンとして表されるリモートサービス。
@Slf4j
public class RemoteService implements RemoteServiceInterface {
private static RemoteService service = null;
static synchronized RemoteService getRemoteService() {
if (service == null) {
service = new RemoteService();
}
return service;
}
private RemoteService() {
}
@Override
public long doRemoteFunction(int value) {
long waitTime = (long) Math.floor(Math.random() * 1000);
try {
sleep(waitTime);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep interrupted", e);
}
return waitTime >= 200 ? value * 10 : -1;
}
}
サービスアンバサダーは、ロギングやレイテンシチェックなどの追加機能を追加します。
@Slf4j
public class ServiceAmbassador implements RemoteServiceInterface {
private static final int RETRIES = 3;
private static final int DELAY_MS = 3000;
ServiceAmbassador() {
}
@Override
public long doRemoteFunction(int value) {
return safeCall(value);
}
private long checkLatency(int value) {
var startTime = System.currentTimeMillis();
var result = RemoteService.getRemoteService().doRemoteFunction(value);
var timeTaken = System.currentTimeMillis() - startTime;
LOGGER.info("Time taken (ms): " + timeTaken);
return result;
}
private long safeCall(int value) {
var retries = 0;
var result = (long) FAILURE;
for (int i = 0; i < RETRIES; i++) {
if (retries >= RETRIES) {
return FAILURE;
}
if ((result = checkLatency(value)) == FAILURE) {
LOGGER.info("Failed to reach remote: (" + (i + 1) + ")");
retries++;
try {
sleep(DELAY_MS);
} catch (InterruptedException e) {
LOGGER.error("Thread sleep state interrupted", e);
}
} else {
break;
}
}
return result;
}
}
クライアントには、リモートサービスとの対話に使用されるローカルサービスアンバサダーがあります。
@Slf4j
public class Client {
private final ServiceAmbassador serviceAmbassador = new ServiceAmbassador();
long useService(int value) {
var result = serviceAmbassador.doRemoteFunction(value);
LOGGER.info("Service result: " + result);
return result;
}
}
サービスを使用している2人のクライアントを次に示します。
public class App {
public static void main(String[] args) {
var host1 = new Client();
var host2 = new Client();
host1.useService(12);
host2.useService(73);
}
}
例を実行した出力は次のとおりです
Time taken(ms):111
Service result:120
Time taken(ms):931
Failed to reach remote:(1)
Time taken(ms):665
Failed to reach remote:(2)
Time taken(ms):538
Failed to reach remote:(3)
Service result:-1
Javaでアンバサダーパターンを使用する場合
- アンバサダーパターンは、Javaのクラウドネイティブおよびマイクロサービスアーキテクチャに特に役立ちます。 サービス間の通信の監視、ロギング、およびセキュリティ保護に役立ち、分散システムに最適です。
- レガシーシステムの統合:必要ではあるがコアではない機能を処理することにより、新しいサービスとの通信を促進します。
- パフォーマンスの向上:結果をキャッシュしたり、データを圧縮したりして、通信効率を向上させるために使用できます。
典型的なユースケースには、次のものがあります
- 別のオブジェクトへのアクセスを制御する
- ロギングを実装する
- サーキットブレーカーを実装する
- リモートサービスタスクをオフロードする
- ネットワーク接続を容易にする
アンバサダーパターンの利点とトレードオフ
利点
- 関心の分離:サービスロジックから横断的な関心事をオフロードし、よりクリーンで保守しやすいコードを実現します。
- 再利用可能なインフラストラクチャロジック:アンバサダーパターンにより、同じロジック(ロギング、監視など)を複数のサービスで再利用できます。
- セキュリティの向上:SSLターミネーションや認証などのセキュリティ機能を一元化し、構成ミスによるリスクを軽減します。
- 柔軟性:サービスコードを変更せずに、インフラストラクチャの懸念事項を更新または置換しやすくします。
トレードオフ
- 複雑さの増加:アーキテクチャに別のレイヤーが追加されるため、システムの設計とデバッグが複雑になる可能性があります。
- 潜在的なパフォーマンスのオーバーヘッド:特に最適化されていない場合、追加のネットワークホップによってレイテンシとオーバーヘッドが発生する可能性があります。
- 展開のオーバーヘッド:アンバサダーサービスの展開とスケーリングには、追加のリソースと管理が必要です。
Javaにおけるアンバサダーパターンの実際のアプリケーション
- サービスメッシュの実装:IstioやLinkerdなどのサービスメッシュアーキテクチャでは、アンバサダーパターンは、サービス間の通信を処理するサイドカープロキシとしてよく使用されます。 これには、サービスディスカバリ、ルーティング、ロードバランシング、テレメトリ(メトリクスとトレース)、およびセキュリティ(認証と承認)などのタスクが含まれます。
- APIゲートウェイ:APIゲートウェイは、アンバサダーパターンを使用して、レート制限、キャッシング、リクエストシェーピング、認証などの共通機能をカプセル化できます。 これにより、バックエンドサービスは、これらの横断的な懸念事項に悩まされることなく、コアビジネスロジックに集中できます。
- ロギングと監視:アンバサダーは、さまざまなサービスからログとメトリクスを集約し、PrometheusやELK Stack(Elasticsearch、Logstash、Kibana)などの集中型監視ツールに転送できます。 これにより、各サービスのロギングと監視のセットアップが簡素化され、システムの健全性に関する統一されたビューが提供されます。
- セキュリティ:SSL / TLSターミネーション、ID検証、暗号化などのセキュリティ関連の機能は、アンバサダーによって管理できます。 これにより、サービス全体で一貫したセキュリティプラクティスが保証され、構成ミスによるセキュリティ侵害の可能性が低くなります。
- 回復力:アンバサダーは、サーキットブレーカー、再試行、タイムアウトなどの回復力パターンを実装できます。 たとえば、NetflixのHystrixライブラリをアンバサダー内で使用して、マイクロサービスエコシステムでのカスケード障害を防ぐことができます。
- データベースプロキシ:アンバサダーは、データベース接続のプロキシとして機能し、接続プーリング、レプリカの読み取り/書き込み分割、クエリキャッシングなどの機能を提供します。 これにより、アプリケーションサービスからかなりの複雑さが軽減されます。
- レガシーシステムの統合:最新のマイクロサービスがレガシーシステムと通信する必要があるシナリオでは、アンバサダーは、プロトコルを変換し、必要な変換を処理し、最新のセキュリティプラクティスを実装する仲介役として機能し、統合プロセスを容易にします。
- ネットワークの最適化:異なる地理的な場所またはクラウドリージョンに展開されたサービスの場合、アンバサダーは、データを圧縮し、リクエストをバッチ処理し、さらにはスマートルーティングを実装することにより、通信を最適化して、レイテンシとコストを削減できます。
- マイクロサービス向けのKubernetesネイティブAPIゲートウェイ
関連するJavaデザインパターン
- サーキットブレーカー:応答のないサービスへの呼び出しを停止することにより、フォールトトレランスの管理に関連して使用されることがよくあります。
- デコレータ:デコレータパターンは、オブジェクトに動的に機能を追加するために使用されますが、アンバサダーパターンは、機能を別のオブジェクトにオフロードするために使用されます。
- プロキシ:プロキシパターンと類似点を共有しますが、アンバサダーパターンは、補助的な機能のオフロードに特化しています。
- サイドカー:コンテナ化されたアプリケーションのコンテキストで使用される同様のパターン。サイドカーコンテナは、メインアプリケーションコンテナに追加機能を提供します。