paint-brush
OpenTelemetry トレースと最小限のコードによる Java バックエンドの可観測性@apanasevich
新しい歴史

OpenTelemetry トレースと最小限のコードによる Java バックエンドの可観測性

Dmitriy Apanasevich10m2024/11/15
Read on Terminal Reader
Read this story w/o Javascript

長すぎる; 読むには

OpenTelemetry フレームワークを Java バックエンドに統合し、最小限のコーディングでトレースを実現する方法。

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - OpenTelemetry トレースと最小限のコードによる Java バックエンドの可観測性
Dmitriy Apanasevich HackerNoon profile picture

みなさん、こんにちは!私はMY.GAMESでJava開発者としてRush Royaleというゲームに取り組んでいるDmitriy Apanasevichです。OpenTelemetryフレームワークをJavaバックエンドに統合した経験についてお話ししたいと思います。ここでは、実装に必要なコード変更や、インストールと構成に必要だった新しいコンポーネントについて取り上げ、もちろん、結果の一部もお伝えします。

私たちの目標:システムの可観測性の実現

私たちのケースについてもう少し説明しましょう。開発者として、私たちは監視、評価、理解しやすいソフトウェアを作りたいと思っています(そしてこれがまさにOpenTelemetryを実装する目的です。つまり、システムの信頼性を最大限に高めることです)。観測可能性)。


アプリケーションのパフォーマンスに関する洞察を収集するための従来の方法では、多くの場合、イベント、メトリック、エラーを手動で記録する必要があります。



もちろん、ログを操作できるフレームワークは数多くあり、この記事を読んでいる皆さんもログを収集、保存、分析するためのシステムが構成されていると思います。


ログ記録も完全に構成されていたため、ログの操作に OpenTelemetry が提供する機能は使用しませんでした。


システムを監視するもう 1 つの一般的な方法は、メトリックを活用することです。


また、メトリックを収集して視覚化するための完全に構成されたシステムもあったので、ここでもメトリックの操作に関して OpenTelemetry の機能を無視しました。


しかし、このようなシステムデータを取得して分析するためのあまり一般的ではないツールは痕跡


トレースは、リクエストがシステム内でその存続期間中にたどる経路を表し、通常はシステムがリクエストを受信したときに始まり、応答で終わります。トレースは複数のスパン、それぞれが開発者または選択したライブラリによって決定された特定の作業単位を表します。これらのスパンは階層構造を形成し、システムがリクエストを処理する方法を視覚化するのに役立ちます。


この説明では、OpenTelemetry のトレースの側面に焦点を当てます。

OpenTelemetry の背景について

また、OpenTelemetryプロジェクトについても少し触れておきましょう。これは、オープントレーシングそしてオープンセンサスプロジェクト。


OpenTelemetry は現在、さまざまなプログラミング言語用の一連の API、SDK、およびツールを定義する標準に基づいて包括的なコンポーネントを提供しており、プロジェクトの主な目標は、データを生成、収集、管理、およびエクスポートすることです。


ただし、OpenTelemetry はデータ ストレージや視覚化ツール用のバックエンドを提供していません。


私たちはトレースにのみ興味があったので、トレースを保存および視覚化するための最も人気のあるオープンソース ソリューションを調査しました。

  • イェーガー
  • ジプキン
  • グラファナテンポ


最終的に、優れた視覚化機能、迅速な開発ペース、およびメトリック視覚化のための既存の Grafana セットアップとの統合を理由に、Grafana Tempo を選択しました。単一の統合ツールを持つことも大きな利点でした。

OpenTelemetry コンポーネント

OpenTelemetry のコンポーネントについても少し詳しく見てみましょう。


仕様:

  • API — データの種類、操作、列挙型

  • SDK — 仕様の実装、さまざまなプログラミング言語での API。言語が異なると、SDK の状態もアルファから安定まで異なります。

  • データプロトコル(OTLP)と意味上の慣習


Java API SDK:

  • コード計測ライブラリ
  • エクスポーター — 生成されたトレースをバックエンドにエクスポートするためのツール
  • クロス サービス プロパゲーター — 実行コンテキストをプロセス (JVM) 外に転送するためのツール


OpenTelemetry Collector は重要なコンポーネントであり、データを受信して処理し、渡すプロキシです。詳しく見てみましょう。

オープンテレメトリコレクター

1 秒あたり数千のリクエストを処理する高負荷システムでは、データ量の管理が重要です。トレース データは多くの場合、量的にビジネス データを上回るため、収集して保存するデータの優先順位付けが不可欠です。ここで、当社のデータ処理およびフィルタリング ツールが役立ち、保存する価値のあるデータを判断できます。通常、チームは次のような特定の基準を満たすトレースを保存したいと考えています。


  • 応答時間が特定のしきい値を超えたトレース。
  • 処理中にエラーが発生したトレース。
  • 特定のマイクロサービスを通過したものや、コード内で疑わしいものとしてフラグが付けられたものなど、特定の属性を含むトレース。
  • システムの通常の動作の統計スナップショットを提供する定期的なトレースのランダムな選択。典型的な動作を理解し、傾向を特定するのに役立ちます。

どのトレースを保存し、どのトレースを破棄するかを決定するために使用される 2 つの主なサンプリング方法を次に示します。

  • ヘッドサンプリング- トレースの開始時にそれを保持するかどうかを決定します
  • テールサンプリング- 完全なトレースが利用可能になった後にのみ決定します。これは、決定がトレースの後半に表示されるデータに依存する場合に必要です。たとえば、エラースパンを含むデータなどです。これらのケースは、最初にトレース全体を分析する必要があるため、ヘッドサンプリングでは処理できません。


OpenTelemetry Collector は、必要なデータのみが保存されるようにデータ収集システムを構成するのに役立ちます。その構成については後で説明しますが、今は、トレースの生成を開始するためにコードで何を変更する必要があるかという問題に移りましょう。

ゼロコードインストルメンテーション

トレース生成には最小限のコーディングしか必要ありませんでした。Javaエージェントでアプリケーションを起動し、構成:


-javaagent:/opentelemetry-javaagent-1.29.0.jar

-Dotel.javaagent.configuration-file=/otel-config.properties


OpenTelemetryは膨大な数のライブラリとフレームワークそのため、エージェントを使用してアプリケーションを起動すると、サービス間や DBMS 内などでのリクエストの処理段階に関するデータを含むトレースがすぐに受信されました。


エージェントの設定では、トレースに表示したくないスパンを持つライブラリを無効にし、コードの動作に関するデータを取得するために、注釈:


 @WithSpan("acquire locks") public CompletableFuture<Lock> acquire(SortedSet<Object> source) { var traceLocks = source.stream().map(Object::toString).collect(joining(", ")); Span.current().setAttribute("locks", traceLocks); return CompletableFuture.supplyAsync(() -> /* async job */); }


この例では、メソッドに@WithSpanアノテーションが使用され、" acquire locks " という名前の新しいスパンを作成する必要があることが示され、メソッド本体で作成されたスパンに " locks " 属性が追加されます。


メソッドが動作を終了すると、スパンが閉じられます。非同期コードでは、この詳細に注意することが重要です。アノテーション付きメソッドから呼び出されるラムダ関数で非同期コードの動作に関連するデータを取得する必要がある場合は、これらのラムダを個別のメソッドに分離し、追加のアノテーションでマークする必要があります。

トレース収集の設定

ここで、トレース収集システム全体を構成する方法について説明します。すべての JVM アプリケーションは、OpenTelemetry コレクターにデータを送信する Java エージェントを使用して起動されます。


ただし、単一のコレクターでは大量のデータ フローを処理できないため、システムのこの部分を拡張する必要があります。JVM アプリケーションごとに個別のコレクターを起動すると、トレース分析は 1 つのコレクターで実行する必要があるため、テール サンプリングが機能しなくなり、リクエストが複数の JVM を通過すると、1 つのトレースの範囲が別のコレクターに渡され、それらの分析が不可能になります。


ここでは、コレクターが設定されているバランサーが救助に来ます。


その結果、次のシステムが得られます。各 JVM アプリケーションは同じバランサー コレクターにデータを送信します。バランサー コレクターの唯一のタスクは、異なるアプリケーションから受信したが特定のトレースに関連するデータを同じコレクター プロセッサに配布することです。次に、コレクター プロセッサはデータを Grafana Tempo に送信します。



このシステムのコンポーネントの構成を詳しく見てみましょう。

負荷分散コレクター

コレクター バランサー構成では、次の主要部分を構成しました。


 receivers:  otlp:    protocols:      grpc: exporters:  loadbalancing:    protocol:      otlp:        tls:          insecure: true    resolver:      static:        hostnames:          - collector-1.example.com:4317          - collector-2.example.com:4317          - collector-3.example.com:4317 service:  pipelines:    traces:      receivers: [otlp]      exporters: [loadbalancing]


  • レシーバー— コレクターがデータを受信する方法を設定します。データ受信はOTLP形式のみで設定しました。(データの受信はOTLP形式のみで設定できます。他の多くのプロトコル(例: Zipkin、Jaeger)
  • エクスポータ— データ バランシングが構成される構成部分。このセクションで指定されたコレクター プロセッサ間で、データはトレース識別子から計算されたハッシュに応じて分散されます。
  • サービス セクションでは、サービスがどのように動作するかの構成を指定します。トレースのみ、上部に構成された OTLP レシーバーの使用、バランサーとしてのデータの送信、つまり処理なしなどです。

データ処理を備えたコレクター

コレクタープロセッサの構成はより複雑なので、そこを見てみましょう。


 receivers:  otlp:    protocols:      grpc:        endpoint: 0.0.0.0:14317 processors:  tail_sampling:    decision_wait: 10s    num_traces: 100    expected_new_traces_per_sec: 10    policies:      [          {            name: latency500-policy,            type: latency,            latency: {threshold_ms: 500}          },          {            name: error-policy,            type: string_attribute,            string_attribute: {key: error, values: [true, True]}          },          {            name: probabilistic10-policy,            type: probabilistic,            probabilistic: {sampling_percentage: 10}          }      ]  resource/delete:    attributes:      - key: process.command_line        action: delete      - key: process.executable.path        action: delete      - key: process.pid        action: delete      - key: process.runtime.description        action: delete      - key: process.runtime.name        action: delete      - key: process.runtime.version        action: delete exporters:  otlp:    endpoint: tempo:4317    tls:      insecure: true service:  pipelines:    traces:      receivers: [otlp]      exporters: [otlp]


コレクター バランサー構成と同様に、処理構成はレシーバー、エクスポーター、およびサービス セクションで構成されます。ただし、ここでは、データの処理方法を説明するプロセッサ セクションに焦点を当てます。


まず、 tail_samplingセクションでは、 構成保存と分析に必要なデータをフィルタリングできます。


  • latency500-policy : このルールは、レイテンシが 500 ミリ秒を超えるトレースを選択します。

  • error-policy : このルールは、処理中にエラーが発生したトレースを選択します。トレース範囲内で、値が「true」または「True」である「error」という名前の文字列属性を検索します。

  • probabilistic10-policy : このルールは、すべてのトレースの 10% をランダムに選択して、通常のアプリケーション操作、エラー、および長いリクエスト処理に関する洞察を提供します。


この例では、tail_sampling に加えて、データ分析や保存に必要のない不要な属性を削除するためのresource/deleteセクションを示します。

結果

表示される Grafana トレース検索ウィンドウでは、さまざまな基準でデータをフィルタリングできます。この例では、ゲーム メタデータを処理するロビー サービスから受信したトレースのリストを表示するだけです。この構成により、レイテンシ、エラー、ランダム サンプリングなどの属性で将来フィルタリングできるようになります。


トレース ビュー ウィンドウには、要求を構成するさまざまなスパンを含む、ロビー サービスの実行タイムラインが表示されます。


図からわかるように、イベントのシーケンスは次のようになります。ロックが取得され、次にオブジェクトがキャッシュから取得され、その後、リクエストを処理するトランザクションが実行され、その後、オブジェクトが再びキャッシュに保存され、ロックが解除されます。


データベース要求に関連するスパンは、標準ライブラリのインストルメンテーションにより自動的に生成されました。一方、ロック管理、キャッシュ操作、トランザクション開始に関連するスパンは、前述のアノテーションを使用してビジネス コードに手動で追加されました。



スパンを表示すると、データベース内のクエリを確認するなど、処理中に何が起こったかをよりよく理解できる属性が表示されます。



Grafana Tempoの興味深い機能の1つは、サービスグラフは、トレースをエクスポートするすべてのサービス、それらの間の接続、リクエストのレートとレイテンシをグラフィカルに表示します。


まとめ

これまで見てきたように、OpenTelemetry トレースを使用することで、観測能力がかなり向上しました。最小限のコード変更と適切に構造化されたコレクター設定により、深い洞察が得られました。さらに、Grafana Tempo の視覚化機能がセットアップをさらに補完することがわかりました。お読みいただきありがとうございました。