コンテンツにスキップ

Java Spring Integration アプリケーションの計装

この記事では、Open TelemetryX-Ray を利用した Spring Integration アプリケーションの手動計装のアプローチについて説明します。

Spring Integration フレームワークは、イベント駆動アーキテクチャやメッセージ指向アーキテクチャの統合ソリューションの開発を可能にするように設計されています。一方、OpenTelemetry は HTTP リクエストを使用してサービス間で通信と調整を行うマイクロサービスアーキテクチャに焦点を当てる傾向があります。したがって、このガイドでは OpenTelemetry API を使用した手動計装による Spring Integration アプリケーションの計装例を提供します。

背景情報

トレーシングとは

OpenTelemetry のドキュメントからの以下の引用は、トレースの目的について良い概要を示しています。

Quote

トレースは、アプリケーションへのリクエストが行われたときに何が起こるかの全体像を示します。アプリケーションが単一のデータベースを持つモノリスであれ、精巧なサービスのメッシュであれ、トレースはアプリケーション内のリクエストの完全な「パス」を理解するうえで不可欠です。

トレーシングの主なメリットの 1 つがリクエストのエンドツーエンドの可視性であることを考えると、トレースがリクエストの発信元からバックエンドまで適切にリンクされていることが重要です。OpenTelemetry ではこれを実現する一般的な方法は、ネストされたスパン を利用することです。これは、スパンが最終的な宛先に到達するまでサービスからサービスへと渡されていくマイクロサービスアーキテクチャで機能します。Spring Integration アプリケーションでは、リモートでもローカルでも作成されたスパン間の親子関係を作成する必要があります。

コンテキスト伝播を利用したトレース

コンテキスト伝播を利用したアプローチをデモンストレーションします。このアプローチは通常、ローカルおよびリモートの場所で作成されたスパン間の親子関係を作成する必要がある場合に使用されますが、Spring Integration アプリケーションの場合に使用されます。これはコードを簡素化し、アプリケーションのスケールを可能にするためです。メッセージを複数のスレッドで並列に処理でき、異なるホストでメッセージを処理する必要がある場合は水平方向にスケールできます。

これを実現するために必要な概要は次のとおりです。

  • ChannelInterceptor を作成し、すべてのチャネル間で送信されるメッセージをキャプチャできるように GlobalChannelInterceptor として登録する。

  • ChannelInterceptor で:

  • preSend メソッドで:
    • 上流で生成されている前のメッセージからコンテキストを読み取ることを試みる。これは、上流のメッセージからスパンを接続できる場所です。コンテキストが存在しない場合、新しいトレースが開始されます(これは OpenTelemetry SDK によって実行されます)。
    • その操作を識別する一意の名前を持つ Span を作成する。これは、このメッセージが処理されているチャネルの名前にできます。
    • 現在のコンテキストをメッセージに保存する。
    • コンテキストとスコープを thread.local に格納して、後で閉じることができるようにする。
    • ダウンストリームに送信されるメッセージにコンテキストを挿入する。
  • afterSendCompletion で:
    • thread.local からコンテキストとスコープを復元する。
    • コンテキストからスパンを再作成する。
    • メッセージ処理中に発生した例外を登録する。
    • スコープを閉じる。
    • スパンの終了。

これは必要なことの簡略化された説明です。 Spring-Integration フレームワークを使用した機能サンプルアプリケーションを提供しています。このアプリケーションのコードはこちら にあります。

アプリケーションへの計装の変更のみを表示するには、この diff をご覧ください。

このサンプルアプリケーションを実行するには次のコマンドを使用します:

# build and run
mvn spring-boot:run
# create sample input file to trigger flow
echo 'testcontent\nline2content\nlastline' > /tmp/in/testfile.txt

このサンプルアプリケーションを試すには、アプリケーションと同じマシンで ADOT コレクター を次のような設定で実行する必要があります:

receivers:
  otlp:
    protocols:
      grpc: 
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
processors:
  batch/traces:
    timeout: 1s
    send_batch_size: 50
  batch/metrics:
    timeout: 60s
exporters:
  aws xray: region:us-west-2
  aws emf:
    region: us-west-2
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch/traces]
      exporters: [awsxray]
    metrics:
      receivers: [otlp]
      processors: [batch/metrics]
      exporters: [awsemf]

結果

サンプルアプリケーションを実行し、次のコマンドを実行すると、以下のような結果が得られます。

echo 'foo123\nbar123\nfoo1234' > /tmp/in/testfile.txt

X-Ray の結果

上記のセグメントは、サンプルアプリケーションで説明されているワークフローと一致していることがわかります。一部のメッセージの処理時に例外が発生することが予想されるため、適切に登録されており、X-Ray でトラブルシューティングできることが確認できます。

よくある質問

ネストされたスパンを作成するには?

OpenTelemetryには、スパンを接続するために使用できる3つのメカニズムがあります。

明示的に

親スパンを子スパンが作成される場所に渡し、次のように両方をリンクする必要があります:

    Span childSpan = tracer.spanBuilder("child")
    .setParent(Context.current().with(parentSpan)) 
    .startSpan();
暗黙的に

スパンコンテキストは内部的に thread.local に保存されます。 スパンを同じスレッドで作成していることが確実な場合に、この方法が示されます。

    void parentTwo() {
        Span parentSpan = tracer.spanBuilder("parent").startSpan(); 
        try(Scope scope = parentSpan.makeCurrent()) {
            childTwo(); 
        } finally {
        parentSpan.end(); 
        }
    }
    void childTwo() {
        Span childSpan = tracer.spanBuilder("child")
            // NOTE: setParent(...) is not required;
            // `Span.current()` is automatically added as the parent 
            .startSpan();
        try(Scope scope = childSpan.makeCurrent()) { 
            // do stuff
        } finally {
            childSpan.end();
        } 
    }
コンテキストの伝播

この方法では、コンテキストをどこか(HTTP ヘッダーまたはメッセージ内)に保存し、子スパンが作成されるリモートロケーションに転送できるようにします。リモートロケーションであることは厳密な要件ではありません。同じプロセス内でも使用できます。

OpenTelemetry のプロパティは X-Ray のプロパティにどのように変換されますか?

関係を見るには、次のガイドを参照してください。