GRPC:Java / Scalaで高スループットクライアントを作成する


9

非常に高速でメッセージを転送するサービスがあります。

現在、それはakka-tcpによって提供され、1分あたり350万のメッセージを作成します。grpcを試してみることにしました。残念ながら、その結果、スループットははるかに小さくなりました。1分あたり約50万メッセージはさらに少なくなります。

それを最適化する方法をお勧めしていただけませんか?

私のセットアップ

ハードウェア:32コア、24 Gbヒープ。

grpcバージョン:1.25.0

メッセージ形式とエンドポイント

メッセージは基本的にバイナリBLOBです。クライアントは100K-1M以上のメッセージを同じリクエストに(非同期で)ストリーミングし、サーバーは何も応答せず、クライアントは何もしないオブザーバーを使用します

service MyService {
    rpc send (stream MyMessage) returns (stream DummyResponse);
}

message MyMessage {
    int64 someField = 1;
    bytes payload = 2;  //not huge
}

message DummyResponse {
}

問題:akka実装と比較してメッセージレートが低い。CPU使用率が低いので、別の言い方をしても、grpc呼び出しが実際に内部的にブロックされているのではないかと思います。onNext()確かに呼び出しはすぐには戻りませんが、テーブルにGCもあります。

この問題を緩和するために、より多くの送信者を生成しようとしましたが、あまり改善されませんでした。

私の調査結果 Grpcは、メッセージをシリアル化するときに、実際に各メッセージに8KBバイトのバッファーを割り当てます。スタックトレースを見てください:

java.lang.Thread.State:BLOCKED(オブジェクトモニター上)com.google.common.io.ByteStreams.createBuffer(ByteStreams.java:58)at com.google.common.io.ByteStreams.copy(ByteStreams.java: 105)io.grpc.internal.MessageFramer.writeUncompressed(MessageFramer.javaでio.grpc.internal.MessageFramer.writeKnownLengthUncompressed(MessageFramer.java:230)でio.grpc.internal.MessageFramer.writeToOutputStream(MessageFramer.java:274)に:168)io.grpc.internal.MessageFramer.writePayload(MessageFramer.java:141)at io.grpc.internal.AbstractStream.writeMessage(AbstractStream.java:53)at io.grpc.internal.ForwardingClientStream.writeMessage(ForwardingClientStream。 java:37)io.grpc.internal.DelayedStream.writeMessage(DelayedStream.java:252)at io.grpc.internal。ClientCallImpl.sendMessageInternal(ClientCallImpl.java:473)at io.grpc.internal.ClientCallImpl.sendMessage(ClientCallImpl.java:457)at io.grpc.ForwardingClientCall.sendMessage(ForwardingClientCall.java:37)at io.grpc.ForwardingClientCall.sendMessage (ForwardingClientCall.java:37)at io.grpc.stub.ClientCalls $ CallToStreamObserverAdapter.onNext(ClientCalls.java:346)

高スループットのgrpcクライアントを構築するためのベストプラクティスに関する支援があれば幸いです。


Protobufを使用していますか?このコードパスは、MethodDescriptor.Marshaller.stream()によって返されたInputStreamがDrainableを実装していない場合にのみ使用する必要があります。Protobuf MarshallerはDrainableをサポートしています。Protobufを使用している場合、ClientInterceptorがMethodDescriptorを変更している可能性はありますか?
エリックアンダーソン

@EricAndersonご返信ありがとうございます。Gradle(com.google.protobuf:protoc:3.10.1、io.grpc:protoc-gen-grpc-java:1.25.0)と標準のprotobufを試しましたscalapb。おそらく、このスタックトレースは実際にscalapb生成コードからのものでした。私はscalapbに関連するすべてのものを削除しましたが、それはwrtパフォーマンスにあまり役立ちませんでした。
simpadjo

@EricAnderson私は私の問題を解決しました。grpcの開発者としてあなたにpingします。私の答えは意味がありますか?
simpadjo

回答:


4

ManagedChannel宛先ごとに複数のインスタンスを作成することで問題を解決しました。記事にもかかわらず、ManagedChannelそれ自体は十分な接続を生成できるため、1つのインスタンスで十分であると述べていますが、私の場合はそうではありませんでした。

パフォーマンスはakka-tcp実装と同等です。


1
ManagedChannel(組み込みLBポリシーを使用)は、バックエンドごとに複数の接続を使用しません。したがって、バックエンドの数が少ないハイスループットの場合、すべてのバックエンドへの接続を飽和させることが可能です。これらの場合、複数のチャネルを使用するとパフォーマンスが向上します。
エリックアンダーソン

@EricAndersonありがとう。私の場合、単一のバックエンドノードにさえいくつかのチャネルを生成することが助けになりました
simpadjo

バックエンドが少なく帯域幅が広いほど、複数のチャネルが必要になる可能性が高くなります。したがって、「単一のバックエンド」を使用すると、より多くのチャネルが役立つ可能性が高くなります。
エリックアンダーソン

0

興味深い質問です。コンピュータネットワークパッケージは、プロトコルのスタックを使用してエンコードされ、そのようなプロトコルは、以前のプロトコルの仕様の上に構築されています。したがって、基礎となるプロトコルの上に追加のエンコーディング/デコーディングステップを追加しているため、プロトコルのパフォーマンス(スループット)は、プロトコルの構築に使用されたもののパフォーマンスによって制限されます。

例えば、gRPC上に構築されているHTTP 1.1/2上のプロトコルであり、アプリケーション層、またはL7、そのようなものとして、その性能は性能によって結合されますHTTP。これHTTP自体はTCPトランスポートレイヤーまたはにあるの上に構築されているL4ため、レイヤーで提供される同等のコードよりもgRPCスループット大きくすることはできませんTCP

つまり、サーバーがraw TCPパッケージを処理できる場合、複雑さの新しいレイヤー(gRPC)を追加すると、パフォーマンスがどのように向上しますか?


そのため、ストリーミングアプローチを使用しています。http接続の確立に1回支払い、それを使用して約3億のメッセージを送信します。それは私が比較的低いオーバーヘッドを期待しているフードの下でウェブソケットを使用します。
simpadjo

以下のためにgRPCあなたにも接続を確立するために、一度に支払うが、あなたはいるProtobufを解析するの余分な負担を追加しました。とにかく、あまり多くの情報なしに推測するのは難しいですが、一般的に、パイプラインに追加のエンコード/デコード手順を追加しているgRPCため、同等のWebソケットよりも実装が遅くなります。
Batato

Akkaはオーバーヘッドも加算します。とにかく、x5のスローダウンが多すぎます。
simpadjo

私はあなたがこれを興味深いと思うかもしれません:github.com/REASY/akka-http-vs-akka-grpc、彼の場合(そしてこれはあなたにも及ぶと思います)、ボトルネックはprotobufのメモリ使用量が多いためかもしれません(de )シリアライズ。これにより、ガベージコレクタへの呼び出しがさらにトリガーされます。
Batato

おかげで、私はすでに問題を解決しましたが、興味深い
simpadjo

0

私はAkka TCPがここでどれほど優れたパフォーマンスを示したかに非常に感銘を受けました:D

私たちの経験は少し異なりました。私たちはAkka Clusterを使用して、はるかに小さなインスタンスに取り組んでいました。Akkaリモーティングでは、Arteryを使用してAkka TCPからUDPに変更し、はるかに高いレート+より低く、より安定した応答時間を実現しました。Arteryには、CPU消費とコールドスタートからの応答時間のバランスを取るのに役立つ構成もあります。

私の提案は、送信の信頼性も処理するUDPベースのフレームワーク(Artery UDPなど)を使用し、フルフレッシュgRPCを使用する代わりに、Protobufを使用してシリアル化することです。HTTP / 2伝送チャネルは、実際には高スループット、低応答時間を目的としたものではありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.