Najlepsze rozwiązania dotyczące wydajności za pomocą usługi gRPC
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z bieżącą wersją, zobacz wersję tego artykułu na platformę .NET 9.
Ostrzeżenie
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z bieżącą wersją, zobacz wersję artykułu dla .NET 9.
Ważne
Te informacje odnoszą się do produktu w wersji wstępnej, który może zostać znacząco zmodyfikowany, zanim zostanie wydany komercyjnie. Firma Microsoft nie udziela żadnych gwarancji, jawnych lub domniemanych, w odniesieniu do informacji podanych w tym miejscu.
Aby zapoznać się z bieżącą wersją, zobacz wydanie .NET 9 tego artykułu.
Autor: James Newton-King
GRPC jest przeznaczony dla usług o wysokiej wydajności. W tym dokumencie wyjaśniono, jak uzyskać najlepszą wydajność z gRPC.
Ponowne wykorzystanie kanałów gRPC
Podczas wykonywania wywołań gRPC należy ponownie użyć kanału gRPC. Ponowne użycie kanału umożliwia multipleksowanie wywołań za pośrednictwem istniejącego połączenia HTTP/2.
Jeśli dla każdego wywołania gRPC zostanie utworzony nowy kanał, czas potrzebny do ukończenia może znacznie wzrosnąć. Każde wywołanie wymaga wielu rund sieci między klientem a serwerem w celu utworzenia nowego połączenia HTTP/2:
- Otwieranie gniazda
- Nawiązywanie połączenia TCP
- Negocjowanie protokołu TLS
- Uruchamianie połączenia HTTP/2
- Wykonywanie wywołania gRPC
Kanały są bezpieczne do udostępniania i ponownego użycia między wywołaniami gRPC:
- Klienci gRPC są tworzeni za pomocą kanałów. Klienci gRPC są lekkimi obiektami i nie muszą być buforowane ani ponownie używane.
- Z jednego kanału można utworzyć wielu klientów gRPC, w tym klientów różnych typów.
- Kanał i klienci utworzoni na podstawie kanału mogą być bezpiecznie używane przez wiele wątków.
- Klienci utworzoni na podstawie kanału mogą wykonywać wiele równoczesnych wywołań.
Fabryka klienta gRPC oferuje scentralizowany sposób konfigurowania kanałów. Automatycznie ponownie używa podstawowych kanałów. Aby uzyskać więcej informacji, zapoznaj się z integracją fabryki klienta gRPC w .NET.
Współbieżność połączenia
Połączenia HTTP/2 zwykle mają limit liczby maksymalnych współbieżnych strumieni (aktywnych żądań HTTP) w połączeniu jednocześnie. Domyślnie większość serwerów ustawia ten limit na 100 współbieżnych strumieni.
Kanał gRPC używa pojedynczego połączenia HTTP/2, a współbieżne wywołania są multipleksowane w tym połączeniu. Gdy liczba aktywnych połączeń osiągnie limit strumienia, dodatkowe połączenia są kolejkowane w systemie klienta. Wywołania w kolejce czekają, aż aktywne rozmowy zostaną zakończone, zanim zostaną wysłane. Aplikacje z dużym obciążeniem lub długotrwałymi wywołaniami gRPC przesyłania strumieniowego mogą doświadczać problemów z wydajnością spowodowanych kolejkowaniem wywołań z powodu tego limitu.
Platforma .NET 5 wprowadza SocketsHttpHandler.EnableMultipleHttp2Connections właściwość . Po ustawieniu true
, dodatkowe połączenia HTTP/2 są tworzone przez kanał, gdy osiągnięty zostanie limit współbieżności strumienia. Po utworzeniu GrpcChannel
, jego wewnętrzny SocketsHttpHandler
jest automatycznie konfigurowany do tworzenia dodatkowych połączeń HTTP/2. Jeśli aplikacja konfiguruje własną procedurę obsługi, rozważ ustawienie EnableMultipleHttp2Connections
na true
:
var channel = GrpcChannel.ForAddress("https://localhost", new GrpcChannelOptions
{
HttpHandler = new SocketsHttpHandler
{
EnableMultipleHttp2Connections = true,
// ...configure other handler settings
}
});
Aplikacje .NET Framework, które tworzą wywołania gRPC, muszą być skonfigurowane tak, aby używały polecenia WinHttpHandler
. Aplikacje .NET Framework mogą ustawić właściwość WinHttpHandler.EnableMultipleHttp2Connections na true
, aby utworzyć dodatkowe połączenia.
Istnieje kilka obejść dla aplikacji platformy .NET Core 3.1:
- Utwórz oddzielne kanały gRPC dla obszarów aplikacji z dużym obciążeniem. Na przykład
Logger
usługa gRPC może mieć duże obciążenie. Użyj oddzielnego kanału, aby utworzyć elementLoggerClient
w aplikacji. - Użyj puli kanałów gRPC, na przykład utwórz listę kanałów gRPC.
Random
służy do wybierania kanału z listy za każdym razem, gdy potrzebny jest kanał gRPC. UżycieRandom
losowo dystrybuuje wywołania za pośrednictwem wielu połączeń.
Ważne
Zwiększenie maksymalnego limitu współbieżnego strumienia na serwerze jest innym sposobem rozwiązania tego problemu. W Kestrel jest to skonfigurowane z MaxStreamsPerConnection.
Zwiększenie maksymalnego limitu współbieżnych strumieni nie jest zalecane. Zbyt wiele strumieni w jednym połączeniu HTTP/2 wprowadza nowe problemy z wydajnością:
- Rywalizacja wątków między strumieniami próbującymi zapisać w połączeniu.
- Utrata pakietów połączenia powoduje zablokowanie wszystkich wywołań w warstwie TCP.
ServerGarbageCollection
w aplikacjach klienckich
Kolektor śmieci platformy .NET ma dwa tryby: kolekcję śmieci stacji roboczej i kolekcję śmieci serwera. Każdy z nich jest dostrojony dla różnych obciążeń. aplikacje ASP.NET Core domyślnie używają GC serwera.
Wysoce współbieżne aplikacje zazwyczaj działają lepiej przy serwerowym GC. Jeśli aplikacja kliencka gRPC wysyła i odbiera dużą liczbę wywołań gRPC jednocześnie, może wystąpić korzyść z wydajności podczas aktualizowania aplikacji w celu korzystania z GC serwera.
Aby włączyć GC serwera, ustaw <ServerGarbageCollection>
w pliku projektu aplikacji:
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>
Aby uzyskać więcej informacji na temat zarządzania pamięcią, zobacz Zarządzanie pamięcią w stacjach roboczych i serwerach.
Uwaga
aplikacje ASP.NET Core domyślnie używają GC serwera.
<ServerGarbageCollection>
Włączenie jest przydatne tylko w aplikacjach klienckich gRPC innych niż serwer, na przykład w aplikacji konsolowej klienta gRPC.
Wywołania asynchroniczne w aplikacjach klienckich
Zaleca się używanie programowania asynchronicznego z async i await podczas wywoływania metod gRPC. Wykonywanie wywołań gRPC z blokowaniem, jak przy użyciu Task.Result
lub Task.Wait()
, uniemożliwia innym zadaniom korzystanie z wątku. Może to prowadzić do głodu puli wątków, niskiej wydajności i zakleszczenia aplikacji.
Wszystkie typy metod gRPC generują asynchroniczne interfejsy API na klientach gRPC. Wyjątkiem są metody unarne, które generują zarówno asynchroniczne metody , jak i blokujące metody.
Rozważmy następującą usługę gRPC zdefiniowaną w pliku .proto:
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}
Wygenerowany typ GreeterClient
ma dwie metody .NET do wywoływania SayHello
:
-
GreeterClient.SayHelloAsync
— asynchronicznie wywołuje usługęGreeter.SayHello
. Można oczekiwać. -
GreeterClient.SayHello
— wywołuje usługęGreeter.SayHello
i blokuje do ukończenia.
Metoda blokująca GreeterClient.SayHello
nie powinna być używana w kodzie asynchronicznym. Może to powodować problemy z wydajnością i niezawodnością.
Równoważenie obciążenia
Niektóre moduły równoważenia obciążenia nie działają efektywnie z usługą gRPC. Moduły równoważenia obciążenia L4 (transport) działają na poziomie połączenia, dystrybuując połączenia TCP między punktami końcowymi. Takie podejście dobrze sprawdza się w przypadku ładowania wywołań interfejsu API równoważenia wykonanych przy użyciu protokołu HTTP/1.1. Współbieżne wywołania wykonywane za pomocą protokołu HTTP/1.1 są wysyłane w różnych połączeniach, co umożliwia równoważenie obciążenia wywołań między punktami końcowymi.
Ponieważ moduły równoważenia obciążenia L4 działają na poziomie połączenia, nie działają dobrze z gRPC. Usługa gRPC używa protokołu HTTP/2, który multipleksuje wiele wywołań w jednym połączeniu TCP. Wszystkie wywołania gRPC przez to połączenie przechodzą do jednego punktu końcowego.
Istnieją dwie opcje efektywnego równoważenia obciążenia gRPC:
- Równoważenie obciążenia po stronie klienta
- Równoważenie obciążenia serwera proxy L7 (aplikacji)
Uwaga
Tylko wywołania gRPC mogą być równoważone między punktami końcowymi. Po nawiązaniu połączenia gRPC strumieniowego, wszystkie komunikaty przesyłane strumieniem trafiają do jednego punktu końcowego.
Równoważenie obciążenia po stronie klienta
W przypadku równoważenia obciążenia po stronie klienta klient wie o punktach końcowych. Dla każdego wywołania gRPC wybiera inny punkt końcowy do wysłania wywołania. Równoważenie obciążenia po stronie klienta jest dobrym wyborem, gdy opóźnienie jest ważne. Nie ma proxy między klientem a usługą, więc wywołanie jest wysyłane bezpośrednio do usługi. Wadą równoważenia obciążenia po stronie klienta jest to, że każdy klient musi śledzić dostępne punkty końcowe, których powinien używać.
Równoważenie obciążenia klienta Lookaside to technika, w której stan równoważenia obciążenia jest przechowywany w centralnej lokalizacji. Klienci okresowo wysyłają zapytania do centralnej lokalizacji, aby uzyskać informacje do użycia podczas podejmowania decyzji dotyczących równoważenia obciążenia.
Aby uzyskać więcej informacji, zobacz równoważenie obciążenia po stronie klienta gRPC.
Równoważenie obciążenia serwera proxy
Serwer proxy L7 (aplikacji) działa na wyższym poziomie niż serwer proxy L4 (transport). Serwery proxy L7 obsługują protokół HTTP/2. Serwer proxy odbiera wywołania gRPC multipleksowane w jednym połączeniu HTTP/2 i dystrybuuje je między wiele punktów końcowych zaplecza. Użycie serwera proxy jest prostsze niż równoważenie obciążenia po stronie klienta, ale powoduje dodatkowe opóźnienie wywołań gRPC.
Dostępnych jest wiele serwerów proxy L7. Oto niektóre opcje:
- Envoy — popularny serwer proxy typu open source.
- Linkerd — siatka usługi dla platformy Kubernetes.
- YARP: Kolejny zwrotny serwer proxy — serwer proxy typu open source napisany na platformie .NET.
Komunikacja między procesami
Wywołania gRPC między klientem a usługą są zwykle wysyłane za pośrednictwem gniazd TCP. Protokół TCP doskonale nadaje się do komunikacji między siecią, ale komunikacja między procesami (IPC) jest wydajniejsza, gdy klient i usługa znajdują się na tym samym komputerze.
Rozważ użycie transportu, takiego jak gniazda domeny systemu Unix lub nazwanych potoków dla wywołań gRPC między procesami na tym samym komputerze. Aby uzyskać więcej informacji, zobacz Komunikacja między procesami z gRPC.
Utrzymuj pingi aktywne
Polecenia ping typu 'keep alive' mogą być używane do utrzymania połączeń HTTP/2 podczas okresów bezczynności. Posiadanie istniejącego połączenia HTTP/2 gotowego, gdy aplikacja wznawia działanie, pozwala na szybkie wykonanie początkowych wywołań gRPC bez opóźnienia spowodowanego ponownym nawiązaniem połączenia.
Sygnały keep-alive są konfigurowane na SocketsHttpHandler:
var handler = new SocketsHttpHandler
{
PooledConnectionIdleTimeout = Timeout.InfiniteTimeSpan,
KeepAlivePingDelay = TimeSpan.FromSeconds(60),
KeepAlivePingTimeout = TimeSpan.FromSeconds(30),
EnableMultipleHttp2Connections = true
};
var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
HttpHandler = handler
});
Poprzedni kod konfiguruje kanał, który wysyła sygnał keep-alive do serwera co 60 sekund w okresach braku aktywności. Polecenie ping gwarantuje, że serwer i wszystkie używane serwery proxy nie zamkną połączenia z powodu braku aktywności.
Uwaga
Sygnały podtrzymania aktywności służą jedynie do utrzymania połączenia żywego. Długotrwałe wywołania gRPC na połączeniu mogą być nadal przerywane przez serwer lub pośredniczące serwery proxy z powodu braku aktywności.
Sterowanie przepływem
Sterowanie przepływem HTTP/2 to funkcja, która uniemożliwia aplikacjom przeciążenie danymi. W przypadku korzystania z sterowania przepływem:
- Każde połączenie HTTP/2 i żądanie ma dostępne okno buforu. Okno buforu to ilość danych, które aplikacja może odbierać jednocześnie.
- Kontrolka przepływu aktywuje się, jeśli zostanie wypełnione okno buforu. Po aktywowaniu aplikacja wysyłająca wstrzymuje wysyłanie większej ilości danych.
- Po przetworzeniu danych przez aplikację odbieraną miejsce w oknie buforu jest dostępne. Aplikacja wysyłająca wznawia wysyłanie danych.
Sterowanie przepływem może mieć negatywny wpływ na wydajność podczas odbierania dużych komunikatów. Jeśli okno buforu jest mniejsze niż ładunki komunikatów przychodzących lub występuje opóźnienie między klientem a serwerem, dane mogą być wysyłane w trybie uruchamiania/zatrzymywania.
Problemy z wydajnością sterowania przepływem można rozwiązać przez zwiększenie rozmiaru okna buforu. W Kestrelprogramie jest to skonfigurowane za pomocą polecenia InitialConnectionWindowSize i InitialStreamWindowSize podczas uruchamiania aplikacji:
builder.WebHost.ConfigureKestrel(options =>
{
var http2 = options.Limits.Http2;
http2.InitialConnectionWindowSize = 1024 * 1024 * 2; // 2 MB
http2.InitialStreamWindowSize = 1024 * 1024; // 1 MB
});
Rekomendacje:
- Jeśli usługa gRPC często odbiera komunikaty większe niż 768 KB, co przekracza domyślny rozmiar okna strumienia dla Kestrel, rozważ zwiększenie rozmiaru okna połączenia i strumienia.
- Rozmiar okna połączenia powinien być zawsze równy lub większy niż rozmiar okna strumienia. Strumień jest częścią połączenia, a nadawca jest ograniczony przez oba te elementy.
Aby uzyskać więcej informacji na temat działania sterowania przepływem, zobacz HTTP/2 Flow Control (wpis w blogu).
Ważne
Zwiększenie Kestrelrozmiaru okna umożliwia Kestrel buforowanie większej ilości danych w imieniu aplikacji, co może zwiększyć użycie pamięci. Unikaj konfigurowania niepotrzebnie dużego rozmiaru okna.
Bezproblemowo ukończone wywołania przesyłania strumieniowego
Spróbuj bezpiecznie ukończyć połączenia przesyłane strumieniowo. Bezproblemowe kończenie wywołań pozwala uniknąć niepotrzebnych błędów i umożliwić serwerom ponowne używanie wewnętrznych struktur danych między żądaniami.
Wywołanie jest zakończone płynnie, gdy klient i serwer zakończyli wysyłanie komunikatów, a interlokutor odczytał wszystkie komunikaty.
Strumień żądania klienta:
- Klient zakończył zapisywanie komunikatów w strumieniu żądania i kończy strumień za pomocą polecenia
call.RequestStream.CompleteAsync()
. - Serwer odczytał wszystkie komunikaty ze strumienia żądań. W zależności od tego, jak czytasz wiadomości,
requestStream.MoveNext()
zwracafalse
lubrequestStream.ReadAllAsync()
zwraca, że została zakończona.
Strumień odpowiedzi serwera:
- Serwer zakończył zapisywanie komunikatów do strumienia odpowiedzi, a metoda serwera została zamknięta.
- Klient odczytał wszystkie komunikaty ze strumienia odpowiedzi. W zależności od tego, jak odczytujesz komunikaty, albo
call.ResponseStream.MoveNext()
zwracafalse
, albocall.ResponseStream.ReadAllAsync()
została zakończona.
Aby zapoznać się z przykładem bezproblemowego ukończenia dwukierunkowego wywołania przesyłania strumieniowego, zobacz wykonywanie dwukierunkowego wywołania przesyłania strumieniowego.
Wywołania przesyłania strumieniowego serwera nie mają strumienia żądań. Oznacza to, że jedynym sposobem, w jaki klient może komunikować się z serwerem, że strumień powinien zostać zatrzymany, jest anulowanie go. Jeśli obciążenie wynikające z anulowanych wywołań wpływa niekorzystnie na aplikację, rozważ zmianę wywołania przesyłania strumieniowego serwera na wywołanie przesyłania strumieniowego dwukierunkowego. W dwukierunkowym wywołaniu przesyłania strumieniowego, zakończenie strumienia żądania przez klienta może być sygnałem dla serwera, aby zakończyć wywołanie.
Usuwanie wywołań przesyłania strumieniowego
Zawsze zakończ przesyłanie strumieniowe, gdy nie jest już potrzebne. Typ zwracany podczas uruchamiania wywołań przesyłania strumieniowego implementuje IDisposable
. Zakończenie połączenia, kiedy nie jest już potrzebne, gwarantuje, że zostanie zatrzymane i wszystkie zasoby zostaną wyczyszczone.
W poniższym przykładzie zastosowanie deklaracji using w wywołaniu AccumulateCount()
zapewnia, że zasób zawsze zostanie usunięty, jeśli wystąpi nieoczekiwany błąd.
var client = new Counter.CounterClient(channel);
using var call = client.AccumulateCount();
for (var i = 0; i < 3; i++)
{
await call.RequestStream.WriteAsync(new CounterRequest { Count = 1 });
}
await call.RequestStream.CompleteAsync();
var response = await call;
Console.WriteLine($"Count: {response.Count}");
// Count: 3
Idealnie wywołania przesyłania strumieniowego powinny być zakończone w sposób łagodny. Usunięcie wywołania gwarantuje, że żądanie HTTP między klientem a serwerem zostanie anulowane, jeśli wystąpi nieoczekiwany błąd. Wywołania przesyłania strumieniowego, które są przypadkowo pozostawione, nie tylko przeciekają pamięci i zasobów na kliencie, ale są również uruchomione na serwerze. Wiele ujawnionych wywołań przesyłania strumieniowego może mieć wpływ na stabilność aplikacji.
Usuwanie połączenia strumieniowego, które zostało już ukończone prawidłowo, nie ma żadnego negatywnego wpływu.
Zastąp wywołania unarne przesyłaniem strumieniowym
Dwukierunkowe przesyłanie strumieniowe gRPC może służyć do zastępowania jednokierunkowych wywołań gRPC w scenariuszach o wysokiej wydajności. Po rozpoczęciu dwukierunkowego strumienia strumieniowe przesyłanie komunikatów jest szybsze niż wysyłanie komunikatów z wieloma pojedynczymi wywołaniami gRPC. Komunikaty przesyłane strumieniowo są wysyłane jako dane w istniejącym żądaniu HTTP/2 i eliminują obciążenie związane z tworzeniem nowego żądania HTTP/2 dla każdego pojedynczego wywołania.
Przykładowa usługa:
public override async Task SayHello(IAsyncStreamReader<HelloRequest> requestStream,
IServerStreamWriter<HelloReply> responseStream, ServerCallContext context)
{
await foreach (var request in requestStream.ReadAllAsync())
{
var helloReply = new HelloReply { Message = "Hello " + request.Name };
await responseStream.WriteAsync(helloReply);
}
}
Przykładowy klient:
var client = new Greet.GreeterClient(channel);
using var call = client.SayHello();
Console.WriteLine("Type a name then press enter.");
while (true)
{
var text = Console.ReadLine();
// Send and receive messages over the stream
await call.RequestStream.WriteAsync(new HelloRequest { Name = text });
await call.ResponseStream.MoveNext();
Console.WriteLine($"Greeting: {call.ResponseStream.Current.Message}");
}
Zastępowanie jednokierunkowych wywołań dwukierunkowym przesyłaniem strumieniowym ze względów wydajnościowych jest zaawansowaną techniką i nie jest odpowiednie w wielu sytuacjach.
Użycie wywołań przesyłania strumieniowego jest dobrym wyborem w następujących przypadkach:
- Wymagana jest wysoka przepływność lub małe opóźnienie.
- gRPC i HTTP/2 zostały zidentyfikowane jako wąskie gardło wydajności.
- Pracownik w kliencie wysyła lub odbiera regularne komunikaty z usługą gRPC.
Należy pamiętać o dodatkowej złożoności i ograniczeniach używania wywołań strumieniowych zamiast pojedynczych:
- Strumień może zostać przerwany przez usługę lub błąd połączenia. Aby ponownie uruchomić strumień, konieczne jest logiczne podejście w przypadku błędu.
-
RequestStream.WriteAsync
nie jest bezpieczne dla wielowątkowości. Jednocześnie można zapisać tylko jedną wiadomość do strumienia. Wysyłanie komunikatów z wielu wątków za pośrednictwem jednego strumienia wymaga kolejki producenta/konsumenta, takiej jak Channel<T>, aby uporządkować komunikaty. - Metoda przesyłania strumieniowego gRPC jest ograniczona do odbierania jednego typu komunikatu i wysyłania jednego typu komunikatu. Na przykład
rpc StreamingCall(stream RequestMessage) returns (stream ResponseMessage)
odbieraRequestMessage
i wysyła polecenieResponseMessage
. Obsługa nieznanych lub warunkowych komunikatów Protobuf przy użyciuAny
ioneof
może obejść to ograniczenie.
Ładunki binarne
Ładunki binarne są obsługiwane w narzędziu Protobuf z typem wartości skalarnych bytes
. Wygenerowana właściwość w języku C# używa ByteString
jako typu właściwości.
syntax = "proto3";
message PayloadResponse {
bytes data = 1;
}
Protobuf to format binarny, który efektywnie serializuje duże ładunki binarne z minimalnym obciążeniem. Formaty oparte na tekście, takie jak JSON, wymagają kodowania bajtów do base64 i dodają 33% do rozmiaru komunikatu.
Podczas pracy z dużymi ByteString
ładunkami istnieją pewne najlepsze rozwiązania, aby uniknąć niepotrzebnych kopii i alokacji, które zostały omówione poniżej.
Wysyłanie ładunków binarnych
ByteString
wystąpienia są zwykle tworzone przy użyciu ByteString.CopyFrom(byte[] data)
. Ta metoda przydziela nowy element ByteString
i nowy element byte[]
. Dane są kopiowane do nowej tablicy bajtów.
Dodatkowych alokacji i kopii można uniknąć, używając UnsafeByteOperations.UnsafeWrap(ReadOnlyMemory<byte> bytes)
do tworzenia instancji ByteString
.
var data = await File.ReadAllBytesAsync(path);
var payload = new PayloadResponse();
payload.Data = UnsafeByteOperations.UnsafeWrap(data);
Bajty nie są kopiowane, więc nie wolno ich modyfikować podczas używania ByteString
.
UnsafeByteOperations.UnsafeWrap
wymaga narzędzia Google.Protobuf w wersji 3.15.0 lub nowszej.
Odczytywanie ładunków binarnych
Dane mogą być efektywnie odczytywane z ByteString
wystąpień przy użyciu właściwości ByteString.Memory
i ByteString.Span
.
var byteString = UnsafeByteOperations.UnsafeWrap(new byte[] { 0, 1, 2 });
var data = byteString.Span;
for (var i = 0; i < data.Length; i++)
{
Console.WriteLine(data[i]);
}
Te właściwości umożliwiają kodowi odczytywanie danych bezpośrednio z ByteString
bez alokacji lub kopiowania.
Większość API platformy .NET ma ReadOnlyMemory<byte>
i byte[]
przeciążenia, dlatego ByteString.Memory
jest zalecanym sposobem korzystania z danych podstawowych. Istnieją jednak okoliczności, w których aplikacja może wymagać pobrania danych jako tablicy bajtów. Jeśli wymagana jest tablica bajtów, można użyć metody MemoryMarshal.TryGetArray, aby pobrać tablicę z obiektu ByteString
bez przydzielania nowej kopii danych.
var byteString = GetByteString();
ByteArrayContent content;
if (MemoryMarshal.TryGetArray(byteString.Memory, out var segment))
{
// Success. Use the ByteString's underlying array.
content = new ByteArrayContent(segment.Array, segment.Offset, segment.Count);
}
else
{
// TryGetArray didn't succeed. Fall back to creating a copy of the data with ToByteArray.
content = new ByteArrayContent(byteString.ToByteArray());
}
var httpRequest = new HttpRequestMessage();
httpRequest.Content = content;
Poprzedni kod:
- Próba pobrania tablicy z
ByteString.Memory
przy użyciu MemoryMarshal.TryGetArray. - Używa obiektu
ArraySegment<byte>
, jeśli został pomyślnie pobrany. Segment ma odniesienie do tablicy, offsetu i liczby. - W przeciwnym razie wróć do przydzielenia nowej tablicy za pomocą polecenia
ByteString.ToByteArray()
.
Usługi gRPC i duże ładunki binarne
GRPC i Protobuf mogą wysyłać i odbierać duże ładunki binarne. Chociaż plik binarny Protobuf jest bardziej wydajny niż tekstowy kod JSON podczas serializacji ładunków binarnych, nadal istnieją ważne cechy wydajności, które należy wziąć pod uwagę podczas pracy z dużymi ładunkami binarnymi.
gRPC to oparta na komunikatach struktura RPC, co oznacza:
- Cały komunikat jest ładowany do pamięci, zanim usługa gRPC będzie mogła ją wysłać.
- Po odebraniu komunikatu cały komunikat jest deserializowany w pamięci.
Ładunki binarne są przydzielane jako tablica bajtów. Na przykład dla ładunku binarnego o rozmiarze 10 MB przydzielana jest tablica bajtów o wielkości 10 MB. Komunikaty z dużymi ładunkami binarnymi mogą przydzielać tablice bajtów na stercie dużych obiektów. Duże alokacje wpływają na wydajność i skalowalność serwera.
Porady dotyczące tworzenia aplikacji o wysokiej wydajności z dużymi ładunkami binarnymi:
- Unikaj dużych ładunków binarnych w komunikatach gRPC. Tablica bajtów większa niż 85 000 bajtów jest uważana za duży obiekt. Utrzymanie się poniżej tego rozmiaru pozwala uniknąć przydzielania w dużej stercie obiektów.
- Rozważ podzielenie dużych ładunków binarnych przy użyciu przesyłania strumieniowego gRPC. Dane binarne są fragmentowane i przesyłane strumieniowo w wielu komunikatach. Aby uzyskać więcej informacji na temat przesyłania strumieniowego plików, zobacz przykłady w repozytorium grpc-dotnet:
- Rozważ, aby nie używać gRPC dla dużych danych binarnych. W ASP.NET Core interfejsy API sieci Web mogą być używane razem z usługami gRPC. Punkt końcowy HTTP może uzyskać bezpośredni dostęp do treści żądania i strumienia odpowiedzi: