Mam usługę, która przesyła wiadomości z dość wysoką prędkością.
Obecnie jest obsługiwany przez akka-tcp i wytwarza 3,5 miliona wiadomości na minutę. Postanowiłem dać grpc szansę. Niestety spowodowało to znacznie mniejszą przepustowość: ~ 500 000 wiadomości na minutę, a nawet mniej.
Czy mógłbyś polecić jak to zoptymalizować?
Moja konfiguracja
Sprzęt komputerowy : 32 rdzenie, stos 24 Gb.
wersja grpc grpc: 1.25.0
Format wiadomości i punkt końcowy
Wiadomość to w zasadzie binarny obiekt blob. Klient przesyła strumieniowo 100K - 1M i więcej wiadomości do tego samego żądania (asynchronicznie), serwer nic nie odpowiada, klient używa obserwatora bez operacji
service MyService {
rpc send (stream MyMessage) returns (stream DummyResponse);
}
message MyMessage {
int64 someField = 1;
bytes payload = 2; //not huge
}
message DummyResponse {
}
Problemy: Szybkość wiadomości jest niska w porównaniu do implementacji akka. Obserwuję niskie zużycie procesora, więc podejrzewam, że połączenie grpc faktycznie blokuje wewnętrznie, mimo że mówi inaczej. Calling onNext()
rzeczywiście nie wraca natychmiast, ale na stole jest także GC.
Próbowałem odrodzić więcej nadawców, aby złagodzić ten problem, ale nie uzyskałem dużej poprawy.
Moje ustalenia Grpc faktycznie przydziela bufor bajtów 8 KB na każdą wiadomość, gdy jest ona serializowana. Zobacz stacktrace:
java.lang.Thread.State: BLOCKED (na monitorze obiektów) w com.google.common.io.ByteStreams.createBuffer (ByteStreams.java:58) w com.google.common.io.ByteStreams.copy (ByteStreams.java: 105) w io.grpc.internal.MessageFramer.writeToOutputStream (MessageFramer.java:274) w io.grpc.internal.MessageFramer.writeKnownLengthUncompressed (MessageFramer.java:230) w io.grpc.internal.MessageFramer.write : 168) w io.grpc.internal.MessageFramer.writePayload (MessageFramer.java:141) w io.grpc.internal.AbstractStream.writeMessage (AbstractStream.java:53) w io.grpc.internal.ForwardingClientStream.reamiteream (ForwardingClientStream. java: 37) w io.grpc.internal.DelayedStream.writeMessage (DelayedStream.java:252) w io.grpc.internal.ClientCallImpl.sendMessageInternal (ClientCallImpl.java:473) pod adresem io.grpc.internal.ClientCallImpl.sendMessage (ClientCallImpl.java:457) pod adresem io.grpc.ForwardingClientCall.sendMessage (ForwardingClientCall.orva.grva37) (ForwardingClientCall.java:37) w io.grpc.stub.ClientCalls $ CallToStreamObserverAdapter.onNext (ClientCalls.java:346)
Doceniono wszelką pomoc dotyczącą najlepszych praktyk w budowaniu wysokoprzepustowych klientów grpc.
scalapb
. Prawdopodobnie ten stacktrace rzeczywiście był od kodu generowanego przez scalapb. Usunąłem wszystko związane ze skalapb, ale nie pomogło to w dużej wydajności wrt.Odpowiedzi:
Rozwiązałem problem, tworząc kilka
ManagedChannel
instancji dla każdego miejsca docelowego. Mimo artykułów mówi się, że samManagedChannel
może wygenerować wystarczającą liczbę połączeń, więc wystarczy jedna instancja, co nie było prawdą w moim przypadku.Wydajność jest na równi z implementacją akka-tcp.
źródło
Interesujące pytanie. Pakiety sieci komputerowej są kodowane przy użyciu stosu protokołów , a takie protokoły są zbudowane zgodnie ze specyfikacjami poprzedniego. Dlatego wydajność (przepustowość) protokołu jest ograniczona wydajnością tego, który został użyty do jego zbudowania, ponieważ dodajesz dodatkowe kroki kodowania / dekodowania na szczycie protokołu bazowego.
Na przykład
gRPC
jest zbudowany na podstawieHTTP 1.1/2
, który jest protokołem w warstwie aplikacji , lubL7
, i jako taki, jego wydajność jest związana z wydajnościąHTTP
. TerazHTTP
sam jest zbudowany na wierzchuTCP
, który jest w warstwie Transportu , lubL4
, więc możemy wywnioskować, żegRPC
przepustowość nie może być większa niż równoważny kod obsługiwany wTCP
warstwie.Innymi słowy: jeśli Twój serwer jest w stanie obsłużyć
TCP
pakiety raw , jak dodanie nowych warstw złożoności (gRPC
) poprawiłoby wydajność?źródło
gRPC
zaplacisz jednokrotnie za ustanowienie polaczenia, ale dodales dodatkowe obciazenie parsowania protobuf. W każdym razie trudno zgadywać bez zbyt dużej ilości informacji, ale założę się, że ogólnie, ponieważ dodajesz dodatkowe kroki kodowania / dekodowania w swoim potoku,gRPC
implementacja byłaby wolniejsza niż równoważne gniazdo sieciowe.Jestem pod dużym wrażeniem tego, jak dobrze spisała się tutaj Akka TCP: D
Nasze doświadczenie było nieco inne. Pracowaliśmy nad znacznie mniejszymi instancjami przy użyciu klastra Akka. W przypadku zdalnego sterowania Akka zmieniliśmy z Akka TCP na UDP za pomocą Artery i osiągnęliśmy znacznie wyższą szybkość + niższy i bardziej stabilny czas reakcji. W Artery jest nawet konfiguracja pomagająca zrównoważyć zużycie procesora i czas reakcji od zimnego startu.
Sugeruję, aby użyć frameworka opartego na UDP, który również dba o niezawodność transmisji dla Ciebie (np. Artery UDP), i po prostu serializować przy użyciu Protobuf, zamiast pełnego gRPC. Kanał transmisji HTTP / 2 nie jest tak naprawdę przeznaczony do celów o wysokiej przepustowości i krótkim czasie odpowiedzi.
źródło