対象者

システム開発チーム、システム運用チーム、SRE

効果

パブリッククラウドベンダーの提供するマネージドなメッセージキューやKafkaRabbitMQといったOSSのツールが普及するにつれ、要件に合わせた分散システムが作りやすくなってきています。しかし、そのような分散システムのObservabilityはどのように獲得すればいいでしょうか。その方法の一つとして分散トレーシングがあります。分散トレーシングによって、サービス間がどの様に呼び出しあっているか、それぞれどこにボトルネックがあるのか、といった情報を実際に実行されているリクエストを元に可視化します。New RelicではそれぞれのサービスにAPM Agentを入れ設定を有効にすることで分散トレーシングの機能を利用できます。New Relicはサービス間のgRPCを含むHTTP呼び出しは多くの言語・フレームワークにおいてデフォルトでトレースを取得、可視化できます。より疎結合なシステムとなるメッセージキューを挟む呼び出しを分散トレーシングとして可視化することができるとさらに分散トレーシングの価値が高まります。この記事では、RabbitMQやKafkaと言ったミドルウェア製品や、Amazon SQSAzure Service BusGoogle Cloud Pub/Subと言ったクラウドサービスを経由して、複数のアプリケーションで連携して動作するトランザクションを「疎結合な繋がりをもつトランザクション」と表現しています。New Relicの分散トレーシングは特定の製品に依存しない構成が可能ですので、多くのツールを同時に複数利用しているケースでも分散トレーシングとしてまとめて可視化することが可能です。

例えばRabbitMQを挟んで、メッセージを送信する側と受信する側の(単純な)二つのトランザクションを分散トレーシングで見るとこの様に見えます。

HTTPで繋がりをもつ場合、通常呼び出した側と呼び出された側のSpanは重なりますが、メッセージキューなどの場合は滞留する時間があります。分散トレーシングを設定することで滞留時間を含めて可視化することができます。また、HTTPの場合はデフォルトでService Mapで繋がりが可視化されますが、手動で設定した場合も繋がりが確認できます。

また、メッセージキューをInfrastructureのIntegrationで可視化することで、全体の状況の把握の可能になります。分散トレーシングで特定のリクエストに遅延が発生していた時、メッセージキュー全体の状況はどうだったのか、あるいはその逆をすぐに調査することが可能です。

New Relic 製品

New Relic APM, New Relic Infrastructure

実装方法

分散トレーシングのペイロードを扱うAPIを利用します。APIは各言語のAPM Agentに用意されており、それを利用します。APIを利用するためには、呼び出す側と呼び出される側の双方にNew Relic APM Agentをインストールし、分散トレーシングを有効化しておく必要があります。また、呼び出す側も呼び出される側もトランザクションが開始されている必要があります。Webアプリケーションから呼び出す場合は多くのフレームワークでデフォルトでトランザクションが設定されていますが、Queueを受け取る側のアプリケーションはコンソールアプリケーションの様なバックグラウンドプロセスとして処理すること多いでしょう。その場合は、手動でnon-webトランザクションとして設定する必要があります。

呼び出し側では分散トレーシングのペイロードを生成するAPIを呼び出します。この返り値はJSONフォーマットの文字列です。取得した文字列を適切にフォーマットしてメッセージの一部としてメッセージキューに送信します。一例として.NET Coreで設定する場合は次の様になります。

var messageObject = new Message() {/** メッセージ本体 **/};

//...

var agent = NewRelic.Api.Agent.NewRelic.GetAgent();

var payload = agent.CurrentTransaction.CreateDistributedTracePayload();

var payloadJson = JsonConvert.DeserializeObject(payload.Text());

var messageObject.payload = payloadJson;

var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(payloadJson));‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

呼び出された側では、メッセージに含まれるペイロードを受け取りAPIを呼び出します。APIはJSONフォーマット、もしくはそれをBase64エンコードした文字列を受け取れるため、適切にフォーマットして引き渡します。例えばJavaの場合は次の様になります。

byte[] body; //メッセージを表現するバイト配列。

//payloadを含むJSONオブジェクトを表現する文字列として格納されている想定。

String message = new String(body, "UTF-8");

JSONObject jsonObject = new JSONObject(message);

NewRelic.getAgent().getTransaction().acceptDistributedTracePayload(jsonObject.getJSONObject("payload").toString());‍‍‍‍‍‍‍‍‍‍‍‍‍

また、goの場合はこの様になります。

type Message struct {

  Name string `json:"name"`

  Payload interface{} `json:"payload"`

}



var message Message

err := json.Unmarshal([]byte(*m.Body), &message)

t, err := json.Marshal(message.Payload)

hdrs := http.Header{}

hdrs.Add("Newrelic", base64.StdEncoding.EncodeToString(t))

txn.AcceptDistributedTraceHeaders(newrelic.TransportQueue, hdrs)‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

この様にコードを多少記述しペイロードを伝搬させる必要はありますが、ミドルウェアやクラウドサービスの実装によらず幅広うことができます。

 


New Relic 担当者にもう少し話を聞いてみたい場合はこちらまで!