GRPC: зробити клієнт з високою пропускною спроможністю в Java / Scala


9

У мене є сервіс, який передає повідомлення з досить високою швидкістю.

Наразі його обслуговує akka-tcp, і він повідомляє 3,5 млн повідомлень в хвилину. Я вирішив спробувати grpc. На жаль, це призвело до набагато меншої пропускної здатності: ~ 500k повідомлень в хвилину ще менше.

Чи можете ви порадити, як оптимізувати це?

Моя установка

Обладнання : 32 ядра, купа 24 Гбіт.

версія GRPC: 1.25.0

Формат повідомлення та кінцева точка

Повідомлення в основному є двійковим крапом. Клієнт передає 100K - 1М і більше повідомлень в один запит (асинхронно), сервер нічим не відповідає, клієнт використовує спостережника

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

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

message DummyResponse {
}

Проблеми: Частота повідомлень низька порівняно з реалізацією akka. Я спостерігаю низьке використання процесора, тому я підозрюю, що виклик grpc насправді блокується внутрішньо, незважаючи на те, що йдеться про інше. Виклик onNext()дійсно не повертається одразу, але на столі також є GC.

Я спробував породити більше відправників, щоб пом’якшити цю проблему, але не покращився.

Мої висновки Grpc фактично виділяє 8 Кбайт байтовий буфер на кожне повідомлення при його серіалізації. Дивіться стек-трек:

java.lang.Thread.State: BLOCKED (на моніторі об’єктів) на com.google.common.io.ByteStreams.createBuffer (ByteStreams.java:58) на com.google.common.io.ByteStreams.copy (ByteStreams.java: 105) на io.grpc.internal.MessageFramer.writeToOutputStream (MessageFramer.java:274) на io.grpc.internal.MessageFramer.writeKknownLengthUncompression (MessageFramer.java:230) at io.grpc.internal.MessageFramer.writeUncompression (Message : 168) на io.grpc.internal.MessageFramer.writePayload (MessageFramer.java:141) на io.grpc.internal.Ab абстрактStream.writeMessage (AbstractStream.java:53) на io.grpc.internal.ForwardingClientStream.writeMessage (ForwardingClientStream. java: 37) на io.grpc.internal.DelayedStream.writeMessage (DelayedStream.java:252) на io.grpc.internal.ClientCallImpl. (ForwardingClientCall.java:37) на io.grpc.stub.ClientCalls $ CallToStreamObserverAdapter.onNext (ClientCalls.java:346)

Була вдячна будь-яка допомога щодо передового досвіду створення клієнтів високої пропускної здатності.


Ви використовуєте Protobuf? Цей шлях коду слід приймати лише у тому випадку, якщо InputStream, повернутий MethodDescriptor.Marshaller.stream (), не реалізує Dravable. Протобуф Маршаллер підтримує Dravable. Якщо ви використовуєте Protobuf, чи можливо ClientInterceptor змінює MethodDescriptor?
Ерік Андерсон

@EricAnderson дякую за вашу відповідь. Я спробував стандартний протобуф з gradle (com.google.protobuf: protoc: 3.10.1, io.grpc: protoc-gen-grpc-java: 1.25.0) і також scalapb. Ймовірно, цей стек-трек був дійсно від коду, створеного scalapb. Я видалив усе, що стосується scalapb, але це не дуже допомогло Wrt.
simpadjo

@EricAnderson Я вирішив свою проблему. Pinging вас як розробника grpc. Чи має моя відповідь сенс?
simpadjo

Відповіді:


4

Я вирішив цю проблему, створивши кілька ManagedChannelпримірників на місце призначення. Незважаючи на статті, що на базі ManagedChannelможна породжувати достатньо з'єднань, тому одного екземпляра достатньо, в моєму випадку це не було правдою.

Продуктивність відповідає рівню реалізації akka-tcp.


1
ManagedChannel (із вбудованими політиками LB) не використовує більше ніж одне з'єднання за бекенд. Отже, якщо ви пропускаєтеся з кількома прорізами, то можливо наситити зв'язки з усіма перешкодами. Використання декількох каналів може підвищити продуктивність у цих випадках.
Ерік Андерсон

@EricAnderson дякую. У моєму випадку допомогло
нерестування

Чим менше обмежень і чим більша смуга пропускання, тим більше шансів на те, що вам знадобиться кілька каналів. Тож "єдиний бекенд" зробив би більш імовірним, що більше каналів буде корисним.
Ерік Андерсон

0

Цікаве запитання. Пакети комп'ютерної мережі кодуються за допомогою стека протоколів , і такі протоколи будуються на версії специфікацій попереднього. Отже, продуктивність (пропускна здатність) протоколу обмежена продуктивністю того, який використовується для його побудови, оскільки ви додаєте додаткові кроки кодування / декодування поверх нижнього.

Наприклад gRPC, побудовано поверх HTTP 1.1/2, що є протоколом на рівні додатка , або L7, і як його виконання пов'язана продуктивність HTTP. Тепер HTTPсама побудована поверх TCP, що знаходиться на транспортному шарі , або L4, тому ми можемо зробити висновок, що gRPCпропускна здатність не може бути більшою, ніж еквівалентний код, що подається в TCPшарі.

Іншими словами: якщо ваш сервер здатний обробляти необроблені TCPпакети, як додавання нових шарів складності ( gRPC) покращило б продуктивність?


Саме тому я використовую потоковий підхід: я плачу один раз за встановлення http-з'єднання і надсилаю ~ 300M повідомлень за його допомогою. Він використовує веб-розетки під кришкою, які, як я думаю, мають відносно низькі накладні витрати.
simpadjo

Для gRPCвас також заплатити один раз для встановлення з'єднання, але ви додали додатковий тягар розбору Protobuf. У будь-якому випадку важко робити здогадки без надто багато інформації, але я б погодився, що в цілому, оскільки ви додаєте додаткові кроки кодування / декодування у вашому трубопроводі, gRPCреалізація буде повільнішою, ніж еквівалентна веб-розетка.
Батато

Акка також додає деякі накладні витрати. У всякому разі, уповільнення x5 виглядає занадто багато.
simpadjo

Я думаю, що вам може бути це цікаво: github.com/REASY/akka-http-vs-akka-grpc , в його випадку (і я думаю, що це поширюється на ваше), вузьке місце може бути пов’язане з високим використанням пам'яті в протобуфі (de ) серіалізація, що, в свою чергу, викликає більше дзвінків до сміттєзбірника.
Батато

дякую, цікаво, незважаючи на те, що я вже вирішив своє питання
simpadjo

0

Я дуже вражений тим, як добре тут виступав Akka TCP: D

Наш досвід був дещо іншим. Ми працювали над значно меншими екземплярами, використовуючи кластер Akka. Для видалення Akka ми перейшли від Akka TCP до UDP за допомогою Artery і домоглися набагато вищої швидкості + нижчого та стабільного часу відгуку. В Artery є навіть конфігурація, яка допомагає збалансувати споживання процесора та час реакції з холодного початку.

Моя пропозиція полягає в тому, щоб використовувати деяку основу на основі UDP, яка також піклується про надійність передачі для вас (наприклад, UDP Artery), а також просто серіалізувати за допомогою Protobuf, а не використовувати gRPC з повною плоттю. Канал передачі HTTP / 2 насправді не має високої пропускної здатності з низьким часом відгуку.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.