みなさん、こんにちは!私はMY.GAMESでJava開発者としてRush Royaleというゲームに取り組んでいるDmitriy Apanasevichです。OpenTelemetryフレームワークをJavaバックエンドに統合した経験についてお話ししたいと思います。ここでは、実装に必要なコード変更や、インストールと構成に必要だった新しいコンポーネントについて取り上げ、もちろん、結果の一部もお伝えします。
私たちのケースについてもう少し説明しましょう。開発者として、私たちは監視、評価、理解しやすいソフトウェアを作りたいと思っています(そしてこれがまさにOpenTelemetryを実装する目的です。つまり、システムの信頼性を最大限に高めることです)。
アプリケーションのパフォーマンスに関する洞察を収集するための従来の方法では、多くの場合、イベント、メトリック、エラーを手動で記録する必要があります。
もちろん、ログを操作できるフレームワークは数多くあり、この記事を読んでいる皆さんもログを収集、保存、分析するためのシステムが構成されていると思います。
ログ記録も完全に構成されていたため、ログの操作に OpenTelemetry が提供する機能は使用しませんでした。
システムを監視するもう 1 つの一般的な方法は、メトリックを活用することです。
また、メトリックを収集して視覚化するための完全に構成されたシステムもあったので、ここでもメトリックの操作に関して OpenTelemetry の機能を無視しました。
しかし、このようなシステムデータを取得して分析するためのあまり一般的ではないツールは
トレースは、リクエストがシステム内でその存続期間中にたどる経路を表し、通常はシステムがリクエストを受信したときに始まり、応答で終わります。トレースは複数の
この説明では、OpenTelemetry のトレースの側面に焦点を当てます。
また、OpenTelemetryプロジェクトについても少し触れておきましょう。これは、
OpenTelemetry は現在、さまざまなプログラミング言語用の一連の API、SDK、およびツールを定義する標準に基づいて包括的なコンポーネントを提供しており、プロジェクトの主な目標は、データを生成、収集、管理、およびエクスポートすることです。
ただし、OpenTelemetry はデータ ストレージや視覚化ツール用のバックエンドを提供していません。
私たちはトレースにのみ興味があったので、トレースを保存および視覚化するための最も人気のあるオープンソース ソリューションを調査しました。
最終的に、優れた視覚化機能、迅速な開発ペース、およびメトリック視覚化のための既存の Grafana セットアップとの統合を理由に、Grafana Tempo を選択しました。単一の統合ツールを持つことも大きな利点でした。
OpenTelemetry のコンポーネントについても少し詳しく見てみましょう。
仕様:
API — データの種類、操作、列挙型
SDK — 仕様の実装、さまざまなプログラミング言語での API。言語が異なると、SDK の状態もアルファから安定まで異なります。
データプロトコル(OTLP)と
Java API SDK:
OpenTelemetry Collector は重要なコンポーネントであり、データを受信して処理し、渡すプロキシです。詳しく見てみましょう。
1 秒あたり数千のリクエストを処理する高負荷システムでは、データ量の管理が重要です。トレース データは多くの場合、量的にビジネス データを上回るため、収集して保存するデータの優先順位付けが不可欠です。ここで、当社のデータ処理およびフィルタリング ツールが役立ち、保存する価値のあるデータを判断できます。通常、チームは次のような特定の基準を満たすトレースを保存したいと考えています。
どのトレースを保存し、どのトレースを破棄するかを決定するために使用される 2 つの主なサンプリング方法を次に示します。
OpenTelemetry Collector は、必要なデータのみが保存されるようにデータ収集システムを構成するのに役立ちます。その構成については後で説明しますが、今は、トレースの生成を開始するためにコードで何を変更する必要があるかという問題に移りましょう。
トレース生成には最小限のコーディングしか必要ありませんでした。Javaエージェントでアプリケーションを起動し、
-javaagent:/opentelemetry-javaagent-1.29.0.jar
-Dotel.javaagent.configuration-file=/otel-config.properties
OpenTelemetryは膨大な数の
エージェントの設定では、トレースに表示したくないスパンを持つライブラリを無効にし、コードの動作に関するデータを取得するために、
@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]
コレクタープロセッサの構成はより複雑なので、そこを見てみましょう。
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 の視覚化機能がセットアップをさらに補完することがわかりました。お読みいただきありがとうございました。