GRPC: buat klien throughput tinggi di Java / Scala

9

Saya memiliki layanan yang mentransfer pesan pada tingkat yang cukup tinggi.

Saat ini dilayani oleh akka-tcp dan menghasilkan 3,5 juta pesan per menit. Saya memutuskan untuk mencoba grpc. Sayangnya itu menghasilkan throughput yang jauh lebih kecil: ~ 500rb pesan per menit bahkan lebih sedikit.

Bisakah Anda merekomendasikan cara mengoptimalkannya?

Pengaturan saya

Perangkat keras : 32 core, tumpukan 24Gb.

versi grpc : 1.25.0

Format pesan dan titik akhir

Pesan pada dasarnya adalah gumpalan biner. Klien melakukan stream 100K - 1M dan lebih banyak pesan ke permintaan yang sama (asinkron), server tidak menanggapi apa pun, klien menggunakan pengamat no-op

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

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

message DummyResponse {
}

Masalah: Tingkat pesan rendah dibandingkan dengan implementasi akka. Saya mengamati penggunaan CPU yang rendah jadi saya curiga bahwa panggilan grpc sebenarnya memblokir secara internal meskipun dikatakan sebaliknya. Memanggil onNext()memang tidak segera kembali tetapi ada juga GC di atas meja.

Saya mencoba menelurkan lebih banyak pengirim untuk mengurangi masalah ini tetapi tidak mendapatkan banyak perbaikan.

Temuan saya, Grpc, sebenarnya mengalokasikan buffer byte 8KB pada setiap pesan ketika membuat serialisasi. Lihat stacktrace:

java.lang.Thread.State: BLOCKED (pada objek monitor) di com.google.common.io.ByteStreams.createBuffer (ByteStreams.java:58) di com.google.common.io.ByteStreams.copy (ByteStreams.java: 105) di io.grpc.internal.MessageFramer.writeToOutputStream (MessageFramer.java:74) di io.grpc.internal.MessageFramer.writeKnownLengthUncompressed (MessageFramer.java.w30) di io.grpc.internal.MessageFramer.writeUnter : 168) di io.grpc.internal.MessageFramer.writePayload (MessageFramer.java:141) di io.grpc.internal.AbstrakStream.writeMessage (AbstractStream.java:53) di io.grpc.internal.ForwardingClientStream.writeMessage (ForwardingClientStream. java: 37) di io.grpc.internal.DelayedStream.writeMessage (DelayedStream.java:252) di io.grpc.internal.ClientCallImpl.sendMessageInternal (ClientCallImpl.java:473) di io.grpc.internal.ClientCallImpl.sendMessage (ClientCallImpl.java:457) di io.grpc.ForwardingClientCall.sendMessage (forwardingClient.call. (ForwardingClientCall.java:37) di io.grpc.stub.ClientCalls $ CallToStreamObserverAdapter.onNext (ClientCalls.java:346)

Setiap bantuan dengan praktik terbaik untuk membangun klien grpc throughput tinggi dihargai.

simpadjo
sumber
Apakah Anda menggunakan Protobuf? Jalur kode ini hanya boleh diambil jika InputStream dikembalikan oleh MethodDescriptor.Marshaller.stream () tidak mengimplementasikan Drainable. Protobuf Marshaller mendukung Drainable. Jika Anda menggunakan Protobuf, mungkinkah ClientInterceptor mengubah MethodDescriptor?
Eric Anderson
@EricAnderson terima kasih atas tanggapan Anda. Saya mencoba protobuf standar dengan gradle (com.google.protobuf: protoc: 3.10.1, io.grpc: protoc-gen-grpc-java: 1.25.0) dan juga scalapb. Mungkin stacktrace ini memang dari ke kode yang dihasilkan scalapb. Saya menghapus semua yang berhubungan dengan scalapb tetapi itu tidak membantu banyak kinerja.
simpadjo
@EricAnderson Saya memecahkan masalah saya. Ping Anda sebagai pengembang grpc. Apakah jawaban saya masuk akal?
simpadjo

Jawaban:

4

Saya memecahkan masalah dengan membuat beberapa ManagedChannelinstance per tujuan. Meskipun artikel mengatakan bahwa ManagedChanneldapat menelurkan cukup banyak koneksi itu sendiri sehingga satu contoh sudah cukup itu tidak benar dalam kasus saya.

Performanya setara dengan implementasi akka-tcp.

simpadjo
sumber
1
ManagedChannel (dengan kebijakan LB bawaan) tidak menggunakan lebih dari satu koneksi per backend. Jadi jika Anda throughput tinggi dengan beberapa backend, dimungkinkan untuk menjenuhkan koneksi ke semua backend. Menggunakan banyak saluran dapat meningkatkan kinerja dalam kasus tersebut.
Eric Anderson
@EricAnderson terima kasih. Dalam kasus saya menelurkan beberapa saluran bahkan ke satu backend node telah membantu
simpadjo
Semakin sedikit backend dan semakin tinggi bandwidth, semakin besar kemungkinan Anda membutuhkan banyak saluran. Jadi "backend tunggal" akan membuatnya lebih mungkin lebih banyak saluran bermanfaat.
Eric Anderson
0

Pertanyaan menarik. Paket-paket jaringan komputer dikodekan menggunakan setumpuk protokol , dan protokol tersebut dibangun di atas spesifikasi yang sebelumnya. Oleh karena itu kinerja (throughput) protokol dibatasi oleh kinerja yang digunakan untuk membangunnya, karena Anda menambahkan langkah-langkah pengkodean / dekode tambahan di atas yang mendasarinya.

Misalnya gRPCdibangun di atas HTTP 1.1/2, yang merupakan protokol pada lapisan Aplikasi , atau L7, dan karenanya kinerjanya terikat oleh kinerja HTTP. Sekarang HTTPitu sendiri dibangun di atas TCP, yang pada lapisan Transport , atau L4, jadi kita dapat menyimpulkan bahwa gRPCthroughput tidak boleh lebih besar dari kode setara yang disajikan di TCPlapisan.

Dengan kata lain: jika server Anda dapat menangani TCPpaket mentah , bagaimana menambahkan lapisan kompleksitas baru ( gRPC) akan meningkatkan kinerja?

Batato
sumber
Untuk alasan itulah saya menggunakan pendekatan streaming: Saya membayar sekali untuk membuat koneksi http dan mengirim ~ 300 juta pesan menggunakannya. Menggunakan websockets di bawah tenda yang saya perkirakan memiliki overhead yang relatif rendah.
simpadjo
Untuk gRPCAnda juga membayar satu kali untuk membuat koneksi, tetapi Anda telah menambahkan beban tambahan parsing protobuf. Bagaimanapun sulit untuk membuat dugaan tanpa terlalu banyak informasi, tetapi saya berani bertaruh bahwa, secara umum, karena Anda menambahkan langkah-langkah pengkodean / penguraian tambahan dalam pipa Anda, gRPCimplementasinya akan lebih lambat daripada soket web yang setara.
Batato
Akka menambahkan beberapa overhead juga. Pokoknya perlambatan x5 terlihat terlalu banyak.
simpadjo
Saya pikir Anda mungkin menemukan ini menarik: github.com/REASY/akka-http-vs-akka-grpc , dalam kasusnya (dan saya pikir ini meluas ke Anda), hambatannya mungkin karena penggunaan memori yang tinggi di protobuf (de ) serialisasi, yang pada gilirannya memicu lebih banyak panggilan ke pemulung.
Batato
terima kasih, menarik meskipun saya sudah menyelesaikan masalah saya
simpadjo
0

Saya cukup terkesan dengan seberapa baik Akka TCP telah dilakukan di sini: D

Pengalaman kami sedikit berbeda. Kami sedang mengerjakan contoh yang jauh lebih kecil menggunakan Akka Cluster. Untuk remoting Akka, kami mengubah dari Akka TCP ke UDP menggunakan Artery dan mencapai tingkat respons yang jauh lebih tinggi + lebih rendah dan lebih stabil. Bahkan ada konfigurasi di Artery yang membantu menyeimbangkan antara konsumsi CPU dan waktu respons dari awal yang dingin.

Saran saya adalah menggunakan beberapa kerangka kerja berbasis UDP yang juga menjaga keandalan transmisi untuk Anda (mis. Artery UDP), dan hanya membuat serial menggunakan Protobuf, alih-alih menggunakan gRPC daging lengkap. Saluran transmisi HTTP / 2 tidak benar-benar untuk keperluan waktu respons rendah throughput tinggi.

Wang Xian
sumber