Teilen über


Bewährte Methoden für Leistung mit gRPC

Von James Newton-King

gRPC ist für leistungsstarke Dienste konzipiert. In dieser Dokumentation wird erläutert, wie Sie die bestmögliche Leistung von gRPC erzielen.

Wiederverwenden von gRPC-Kanälen

Ein gRPC-Kanal sollte für gRPC-Aufrufe wiederverwendet werden. Durch die Wiederverwendung eines Kanals können Aufrufe für Multiplexing über eine vorhandene HTTP/2-Verbindung verwendet werden.

Wenn ein neuer Kanal für jeden gRPC-Aufruf erstellt wird, kann dies deutlich mehr Zeit beanspruchen. Jeder Aufruf erfordert mehrere Netzwerkroundtrips zwischen dem Client und dem Server, um eine neue HTTP/2-Verbindung herzustellen:

  1. Öffnen eines Sockets
  2. Herstellen der TCP-Verbindung
  3. TLS-Aushandlung
  4. Starten der HTTP/2-Verbindung
  5. Ausführen des gRPC-Aufrufs

Kanäle können problemlos für mehrere gRPC-Aufrufe gemeinsam genutzt und wiederverwendet werden:

  • gRPC-Clients werden mit Kanälen erstellt. Bei gRPC-Clients handelt es sich um Lightweightobjekte. Sie müssen nicht zwischengespeichert oder wiederverwendet werden.
  • Aus einem Kanal können mehrere gRPC-Clients erstellt werden, einschließlich verschiedener Clienttypen.
  • Ein Kanal und Clients, die aus dem Kanal erstellt wurden, können sicher von mehreren Threads verwendet werden.
  • Clients, die aus dem Kanal erstellt wurden, können mehrere gleichzeitige Aufrufe durchführen.

Die gRPC-Clientfactory bietet eine zentralisierte Möglichkeit zum Konfigurieren von Kanälen. Zugrunde liegende Kanäle werden automatisch wiederverwendet. Weitere Informationen finden Sie unter gRPC-Clientfactoryintegration in .NET.

Verbindungsparallelität

HTTP/2-Verbindungen weisen in der Regel eine Einschränkung für die maximale Anzahl gleichzeitiger Streams (aktive HTTP-Anforderungen) für eine Verbindung auf. Für die meisten Server liegt eine Standardbeschränkung auf 100 gleichzeitige Streams vor.

Ein gRPC-Kanal verwendet eine einzige HTTP/2-Verbindung, und parallele Aufrufe werden per Multiplexing für diese Verbindung ausgeführt. Wenn die Anzahl aktiver Aufrufe die Grenze für den Verbindungsstream erreicht, werden zusätzliche Aufrufe im Client in eine Warteschlange eingereiht. Aufrufe in der Warteschlange warten auf den Abschluss aktiver Aufrufe, bevor sie übermittelt werden. Bei Anwendungen mit hoher Auslastung oder zeitintensiven gRPC-Streamingaufrufen können Leistungsprobleme auftreten, die von Aufrufen ausgelöst werden, die aufgrund dieser Einschränkungen in die Warteschlange eingereiht werden.

Mit .NET 5 wurde die SocketsHttpHandler.EnableMultipleHttp2Connections-Eigenschaft eingeführt. Wenn true festgelegt wird, werden zusätzliche HTTP/2-Verbindungen von einem Kanal erstellt, wenn die maximale Anzahl gleichzeitiger Streams erreicht wird. Wenn ein GrpcChannel erstellt wird, wird der interne SocketsHttpHandler automatisch konfiguriert, um zusätzliche HTTP/2-Verbindungen herzustellen. Wenn eine App seinen eigenen Handler konfiguriert, sollten Sie EnableMultipleHttp2Connections auf true festlegen:

var channel = GrpcChannel.ForAddress("https://localhost", new GrpcChannelOptions
{
    HttpHandler = new SocketsHttpHandler
    {
        EnableMultipleHttp2Connections = true,

        // ...configure other handler settings
    }
});

.NET Framework-Apps, die gRPC-Aufrufe ausführen müssen für die Verwendung von WinHttpHandler konfiguriert werden. .NET Framework-Apps können die WinHttpHandler.EnableMultipleHttp2Connections-Eigenschaft auf true festlegen, um zusätzliche Verbindungen zu erstellen.

Es gibt einige Problemumgehungen für .NET Core 3.1-Apps:

  • Erstellen Sie separate gRPC-Kanäle für Bereiche der App, die eine hohe Auslastung aufweisen. Beispielsweise weist der gRPC-Dienst Logger möglicherweise eine hohe Auslastung auf. Verwenden Sie einen separaten Kanal, um LoggerClient in der App zu erstellen.
  • Verwenden Sie einen Pool von gRPC-Kanälen, erstellen Sie beispielsweise eine Liste mit gRPC-Kanälen. Random wird jedes Mal für die Auswahl eines Kanals aus der Liste verwendet, wenn ein gRPC-Kanal benötigt wird. Bei Verwendung von Random werden Aufrufe zufällig auf mehrere Verbindungen verteilt.

Wichtig

Das Erhöhen der Grenze für die maximale Anzahl gleichzeitiger Streams für den Server ist eine weitere Möglichkeit, das Problem zu lösen. Dies wird in Kestrel mit MaxStreamsPerConnection konfiguriert.

Von einer Erhöhung der maximalen Anzahl gleichzeitiger Streams wird abgeraten. Bei zu vielen Streams über eine einzelne HTTP/2-Verbindung treten neue Leistungsprobleme auf:

  • Threadkonflikte zwischen Streams, die versuchen in die Verbindung zu schreiben
  • Verlust von Verbindungspaketen, der dazu führt, dass alle Aufrufe auf der TCP-Ebene blockiert werden

ServerGarbageCollection in Client-Apps

Der .NET Garbage Collector verfügt über zwei Modi: Workstation Garbage Collection (GC) und Server Garbage Collection. Jeder ist auf unterschiedliche Workloads abgestimmt. ASP.NET Core-Apps verwenden standardmäßig den Server GC.

Hochgradig zeitgleiche Apps laufen in der Regel besser mit Server GC. Wenn eine gRPC-Client-App eine hohe Anzahl von zeitgleichen gRPC-Aufrufen sendet und empfängt, kann es einen Leistungsvorteil geben, wenn die App auf die Verwendung von Server GC aktualisiert wird.

Um Server GC zu aktivieren, legen Sie <ServerGarbageCollection> in der Projektdatei der App fest:

<PropertyGroup>
  <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

Weitere Informationen zu Garbage Collection finden Sie unter Arbeitsstation und Server Garbage Collection.

Hinweis

ASP.NET Core-Apps verwenden standardmäßig den Server GC. Die Aktivierung von <ServerGarbageCollection> ist nur bei serverlosen gRPC Client-Apps nützlich, z.B. in einer gRPC Clientkonsolen-App.

Lastenausgleich

Einige Lastenausgleiche funktionieren mit gRPC nicht effektiv. L4-Lastenausgleiche (Transport) agieren auf Verbindungsebene, indem sie TCP-Verbindungen endpunktübergreifend verteilen. Dieser Ansatz eignet sich für den Lastenausgleich von API-Aufrufen mit HTTP/1.1. Gleichzeitige HTTP/1.1-Aufrufe werden über verschiedene Verbindungen gesendet, sodass der Lastenausgleich für diese endpunktübergreifend erfolgen kann.

Da L4-Lastenausgleiche auf Verbindungsebene agieren, sind sie für gRPC nicht besonders gut geeignet. gRPC verwendet HTTP/2, sodass ein Multiplexing für mehrere Aufrufe über eine einzige TCP-Verbindung erfolgt. Alle gRPC-Aufrufe über diese Verbindung werden an einen Endpunkt gesendet.

Es gibt zwei Optionen für den effektiven Lastenausgleich von gRPC:

  • Clientseitiger Lastenausgleich
  • L7-Proxylastenausgleich (Anwendung)

Hinweis

Nur für gRPC-Aufrufe kann ein Lastenausgleich zwischen Endpunkten ausgeführt werden. Sobald ein gRPC-Streamingaufruf erstellt wurde, werden alle über den Stream gesendeten Nachrichten an einen Endpunkt weitergeleitet.

Clientseitiger Lastenausgleich

Beim clientseitigen Lastenausgleich kennt der Client die Endpunkte. Für jeden gRPC-Aufruf wählt er einen anderen Endpunkt aus, an den der Aufruf gesendet werden soll. Der clientseitige Lastenausgleich ist eine gute Wahl, wenn die Latenz entscheidend ist. Zwischen dem Client und dem Dienst ist kein Proxy vorhanden, sodass der Aufruf direkt an den Dienst gesendet wird. Der Nachteil beim clientseitigen Lastenausgleich besteht darin, dass jeder Client die verfügbaren Endpunkte nachverfolgen muss, die er verwenden soll.

Der clientseitige Lookaside-Lastenausgleich ist ein Verfahren, bei dem der Lastenausgleichzustand an einem zentralen Ort gespeichert wird. Clients fragen diesen zentralen Ort regelmäßig auf Informationen bezüglich Lastenausgleichsentscheidungen ab.

Weitere Informationen finden Sie unter gRPC: Clientseitiger Lastenausgleich.

Proxylastenausgleich

Ein L7-Proxy (Anwendung) agiert auf einer höheren Ebene als ein L4-Proxy (Transport). L7-Proxys sind mit HTTP/2 kompatibel. Der Proxy empfängt gRPC-Aufrufe, für die Multiplexing für eine HTTP/2-Verbindung ausgeführt wird, und verteilt sie auf mehrere Back-End-Endpunkte. Die Verwendung eines Proxys ist einfacher als der clientseitige Lastenausgleich, erhöht jedoch die Latenz für gRPC-Aufrufe.

Es gibt viele verfügbare L7-Proxys. Unter anderem gibt es folgende Optionen:

Prozessübergreifende Kommunikation

gRPC-Aufrufe zwischen einem Client und einem Dienst werden in der Regel über TCP-Sockets gesendet. TCP eignet sich ideal für die Kommunikation über ein Netzwerk, Inter-Process Communication (IPC) ist jedoch noch effizienter, wenn sich Client und Dienst auf demselben Computer befinden.

Erwägen Sie die Verwendung eines Transports wie Unix-Domänensockets oder Named Pipes für gRPC-Aufrufe zwischen Prozessen auf derselben Computer. Weitere Informationen finden Sie unter Inter-Process Communication mit gRPC.

Keep-Alive-Pings

Keep-Alive-Pings können dazu verwendet werden, HTTP/2-Verbindungen während Inaktivität aufrechtzuerhalten. Wenn Sie über eine bereite HTTP/2-Verbindung verfügen, wenn eine App ihre Aktivitäten fortsetzt, werden schnelle anfängliche gRPC-Aufrufe ermöglicht, ohne dass Verzögerungen aufgrund der Wiederherstellung der Verbindung auftreten.

Keep-Alive-Pings werden mit SocketsHttpHandler konfiguriert:

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
});

Im obigen Code wird ein Kanal konfiguriert, der alle 60 Sekunden während Inaktivität einen Keep-Alive-Ping an den Server sendet. Durch diesen Ping wird sichergestellt, dass der Server und jegliche verwendeten Proxys die Verbindung nicht aufgrund der Inaktivität beenden.

Hinweis

Keep-Alive-Pings tragen nur dazu bei, die Verbindung aufrecht zu erhalten. gRPC-Aufrufe mit langer Ausführung für die Verbindung können vom Server oder zwischengeschalteten Proxys weiterhin aufgrund von Inaktivität beendet werden.

Flusssteuerung

HTTP/2-Flowsteuerung ist ein Feature, das verhindert, dass Apps von Daten überwältigt werden. Während Sie Flowsteuerung verwenden:

  • Jede HTTP/2-Verbindung und Anforderung hat ein verfügbares Pufferfenster. Das Pufferfenster bestimmt, wie viele Daten die App gleichzeitig empfangen kann.
  • Wenn der Puffer gefüllt ist, wird die Flowsteuerung aktiviert. Beim Aktivieren hört die sendende App auf, weitere Daten zu senden.
  • Sobald die empfangende App die Daten verarbeitet hat, steht Speicherplatz im Pufferfenster zur Verfügung. Die sendende App setzt das Senden der Daten fort.

Flowsteuerung kann negative Auswirkungen auf die Leistung haben, wenn große Nachrichten empfangen werden. Wenn das Pufferfenster kleiner ist, als die eingehenden Nachrichtennutzdaten, oder eine Wartezeit zwischen dem Client und dem Server besteht, können Daten in Start-/ Stopp-Bursts gesendet werden.

Leistungsprobleme bei der Flowsteuerung können behoben werden, indem die Pufferfenstergröße erhöht wird. In Kestrel, dies ist mit InitialConnectionWindowSize und InitialStreamWindowSize beim App-Start konfiguriert:

builder.WebHost.ConfigureKestrel(options =>
{
    var http2 = options.Limits.Http2;
    http2.InitialConnectionWindowSize = 1024 * 1024 * 2; // 2 MB
    http2.InitialStreamWindowSize = 1024 * 1024; // 1 MB
});

Empfehlungen:

  • Wenn ein gRPC-Dienst häufig Nachrichten über 96 KB empfängt, die standardmäßige Datenstrom-Fenstergröße von Kestrel, sollten Sie überlegen, die Verbindungs- und Datenstrom-Fenstergröße zu erhöhen.
  • Die Verbindungsfenstergröße sollte immer gleich oder größer sein als die Datenstrom-Fenstergröße. Ein Datenstrom ist Teil einer Verbindung, und der Sender ist durch beides begrenzt.

Weitere Informationen zur Funktionsweise der Flowsteuerung finden Sie unter HTTP/2 Flow Control (Blogbeitrag).

Wichtig

Das Erhöhen der Fenstergröße von Kestrel ermöglicht Kestrel es, mehr Daten im Auftrag der App zu puffern, was möglicherweise die Speichernutzung erhöht. Vermeiden Sie es, eine unnötig große Fenstergröße zu konfigurieren.

Ordnungsgemäßes Abschließen von Streaminganrufen

Versuchen Sie, Streaminganrufe ordnungsgemäß abzuschließen. Das ordnungsgemäße Abschließen von Aufrufen vermeidet unnötige Fehler und ermöglicht es Servern, interne Datenstrukturen zwischen Anforderungen wiederzuverwenden.

Ein Anruf wird ordnungsgemäß abgeschlossen, wenn der Client und der Server das Senden von Nachrichten abgeschlossen haben und der Peer alle Nachrichten gelesen hat.

Clientanforderungsstream:

  1. Der Client hat das Schreiben von Nachrichten in den Anforderungsstream abgeschlossen und den Datenstrom mit call.RequestStream.CompleteAsync() abgeschlossen.
  2. Der Server hat alle Nachrichten aus dem Anforderungsstream gelesen. Je nachdem, wie Sie Nachrichten lesen, gibt requestStream.MoveNext()false zurück oder requestStream.ReadAllAsync() ist abgeschlossen.

Serverantwortdatenstrom:

  1. Der Server hat das Schreiben von Nachrichten in den Antwortdatenstrom abgeschlossen und die Servermethode wurde beendet.
  2. Der Client hat alle Nachrichten aus dem Antwortdatenstrom gelesen. Je nachdem, wie Sie Nachrichten lesen, gibt call.ResponseStream.MoveNext()false zurück oder call.ResponseStream.ReadAllAsync() ist abgeschlossen.

Ein Beispiel für eine ordnungsgemäße Durchführung eines bidirektionalen Streaminganrufs finden Sie unter Erstellen eines bidirektionalen Streaminganrufs.

Serverstreamingaufrufe verfügen nicht über einen Anforderungsstream. Dies bedeutet, dass ein Client nur mit dem Server kommunizieren kann, den der Datenstrom beenden soll, indem er abbricht. Wenn sich der Aufwand der abgebrochenen Anrufe auf die App auswirkt, sollten Sie den Serverstreamingaufruf in einen bidirektionalen Streaminganruf ändern. Bei einem bidirektionalen Streamingaufruf kann der Client, der den Anforderungsdatenstrom abschließt, ein Signal für den Server sein, um den Anruf zu beenden.

Verwerfen von Streaminganrufen

Löschen Sie Streaminganrufe immer, sobald sie nicht mehr benötigt werden. Der Typ, der beim Starten von Streamingaufrufen zurückgegeben wird, implementiert IDisposable. Durch das Löschen eines Anrufs, sobald er nicht mehr benötigt wird, wird sichergestellt, dass er angehalten wird und alle Ressourcen bereinigt werden.

Im folgenden Beispiel stellt die Using-Deklaration im AccumulateCount()-Aufruf sicher, dass sie immer gelöscht wird, wenn ein unerwarteter Fehler auftritt.

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

Idealerweise sollten Streaminganrufe ordnungsgemäß abgeschlossen werden. Durch das Löschen des Aufrufs wird sichergestellt, dass die HTTP-Anforderung zwischen dem Client und dem Server abgebrochen wird, wenn ein unerwarteter Fehler auftritt. Streamingaufrufe, die versehentlich weiterlaufen, verursachen nicht nur Arbeitsspeicher- und Ressourcenverluste auf dem Client, sondern werden auch auf dem Server weiterhin ausgeführt. Viele geleakte Streaminganrufe könnten sich auf die Stabilität der App auswirken.

Das Entfernen eines Streaminganrufs, der bereits ordnungsgemäß abgeschlossen wurde, hat keine negativen Auswirkungen.

Ersetzen unärer Anrufe durch Streaming

Das bidirektionale gRPC-Streaming kann dazu verwendet werden, unäre gRPC-Aufrufe in Hochleistungsszenarios zu ersetzen. Sobald ein bidirektionaler Stream gestartet wurde, ist das Streaming von Nachrichten hin und zurück schneller als das Senden von Nachrichten mit mehreren unären gRPC-Aufrufen. Streamnachrichten werden als Daten einer vorhandenen HTTP/2-Anforderung übermittelt und umgehen den Mehraufwand, der durch das Erstellen einer neuen HTTP/2-Anforderung für jeden unären Aufruf entsteht.

Beispieldienst:

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);
    }
}

Beispielclient:

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}");
}

Das Ersetzen unärer Aufrufe durch bidirektionales Streaming aus Leistungsgründen ist eine fortgeschrittene Vorgehensweise und eignet sich in vielen Situationen nicht.

In folgenden Fällen eignet sich die Verwendung von Streamingaufrufen:

  1. Es ist hoher Durchsatz oder niedrige Latenz erforderlich.
  2. gRPC und HTTP/2 wurden als Leistungsengpass identifiziert.
  3. Ein Worker im Client sendet oder empfängt regelmäßig Nachrichten mit einem gRPC-Dienst.

Achten Sie auf die zusätzliche Komplexität und Einschränkungen der Verwendung von Streamingaufrufen anstelle von unären Aufrufen:

  1. Ein Stream kann von einem Dienst oder Verbindungsfehler unterbrochen werden. Es ist Logik zum Neustarten des Streams erforderlich, wenn ein Fehler auftritt.
  2. RequestStream.WriteAsync ist für Multithreading nicht sicher. Es kann immer nur eine Nachricht in einem Stream geschrieben werden. Das Senden von Nachrichten aus mehreren Threads über einen einzelnen Stream erfordert eine Ersteller-/Verbraucherwarteschlange wie Channel<T> zum Marshallen von Nachrichten.
  3. Eine gRPC-Streamingmethode ist darauf beschränkt, eine Art von Nachricht zu empfangen und eine Art von Nachricht zu senden. rpc StreamingCall(stream RequestMessage) returns (stream ResponseMessage) empfängt beispielsweise RequestMessage und sendet ResponseMessage. Mithilfe der Protobuf-Unterstützung unbekannter oder bedingter Nachrichten mit Any und oneof kann diese Einschränkung umgangen werden.

Binäre Nutzlasten

Binäre Nutzlasten werden in Protobuf mit dem bytes-Skalarenwerttyp unterstützt. Eine generierte Eigenschaft in C# verwendet ByteString als Eigenschaftstyp.

syntax = "proto3";

message PayloadResponse {
    bytes data = 1;
}  

Protobuf ist ein Binärformat, das große binäre Nutzdaten mit minimalem Mehraufwand effizient serialisiert. Textbasierte Formate wie JSON erfordern das Codieren von Bytes in base64 und fügen 33 % zur Nachrichtengröße hinzu.

Beim Arbeiten mit großen ByteString-Nutzlasten gibt es einige bewährte Methoden, um unnötige Kopien und Zuordnungen zu vermeiden, die im Folgenden erläutert werden.

Binäre Nutzlasten senden

ByteString-Instanzen werden normalerweise mit ByteString.CopyFrom(byte[] data) erstellt. Durch diese Methode werden ein neuer ByteString und ein neuer byte[] zugeordnet. Daten werden in das neue Bytearray kopiert.

Zusätzliche Zuordnungen und Kopien können vermieden werden, indem UnsafeByteOperations.UnsafeWrap(ReadOnlyMemory<byte> bytes) zum Erstellen von ByteString-Instanzen verwendet wird.

var data = await File.ReadAllBytesAsync(path);

var payload = new PayloadResponse();
payload.Data = UnsafeByteOperations.UnsafeWrap(data);

Bytes werden nicht mit UnsafeByteOperations.UnsafeWrap kopiert, sodass sie nicht geändert werden dürfen, während ByteString verwendet wird.

UnsafeByteOperations.UnsafeWrap erfordert Version 3.15.0 oder höher von Google.Protobuf.

Lesen binärer Nutzlasten

Daten können effizient aus ByteString-Instanzen gelesen werden, indem ByteString.Memory- und ByteString.Span-Eigenschaften verwendet werden.

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]);
}

Diese Eigenschaften ermöglichen es dem Code, Daten direkt aus einem ByteString ohne Zuordnungen oder Kopien zu lesen.

Die meisten .NET-APIs verfügen über ReadOnlyMemory<byte>- und byte[]-Überladungen, daher ByteString.Memory ist die empfohlene Methode, um die zugrunde liegenden Daten zu verwenden. Es gibt jedoch Situationen, in denen eine App die Daten möglicherweise als Bytearray erhalten muss. Wenn ein Bytearray erforderlich ist, kann die MemoryMarshal.TryGetArray-Methode verwendet werden, um ein Array aus einem ByteString-Element abzurufen, ohne eine neue Kopie der Daten zuzuordnen.

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;

Der vorangehende Code:

  • Versucht, ein Array aus ByteString.Memory mit MemoryMarshal.TryGetArray abzurufen.
  • Verwendet ArraySegment<byte>, wenn das Array erfolgreich abgerufen wurde. Das Segment besitzt einen Verweis auf das Array, den Offset und die Anzahl.
  • Andernfalls erfolgt ein Fallback auf die Zuordnung eines neuen Arrays mit ByteString.ToByteArray().

gRPC-Dienste und umfangreiche binäre Nutzdaten

gRPC und Protobuf können umfangreiche binäre Nutzdaten senden und empfangen. Obwohl binäres Protobuf bei der Serialisierung binärer Nutzdaten effizienter ist als textbasiertes JSON, gibt es dennoch wichtige Leistungskriterien, die bei der Arbeit mit großen binären Nutzdaten beachtet werden müssen.

gRPC ist ein nachrichtenbasiertes RPC-Framework. Dies bedeutet:

  • Die gesamte Nachricht wird in den Arbeitsspeicher geladen, bevor gRPC sie senden kann.
  • Beim Empfang der Nachricht wird die gesamte Nachricht im Arbeitsspeicher deserialisiert.

Binäre Nutzdaten werden als Bytearray zugeordnet. Beispielsweise werden binäre Nutzdaten von 10 MB einem 10-MB-Bytearray zugeordnet. Nachrichten mit umfangreichen binären Nutzdaten können Bytearrays im Large-Object-Heap (LOH) zugeordnet werden. Große Zuordnungen wirken sich auf die Leistung und Skalierbarkeit des Servers aus.

Empfehlungen zum Erstellen leistungsstarker Anwendungen mit umfangreichen binären Nutzdaten: