Komprimieren von Antworten in GRPC für ASP.NET CORE 3.0

Die Übersetzung des Artikels wurde im Vorfeld des Beginns des Kurses "C # ASP.NET Core Developer" vorbereitet .








In dieser Episode meiner Serie zu gRPC und ASP.NET Core werden wir uns ansehen, wie die Antwortkomprimierungsfunktion von gRPC-Diensten angeschlossen wird.



HINWEIS : In diesem Artikel werde ich einige der Komprimierungsdetails behandeln, die ich durch das Erlernen der Anrufeinstellungen und -methoden gelernt habe. Es gibt wahrscheinlich genauere und effektivere Ansätze, um die gleichen Ergebnisse zu erzielen.



Dieser Artikel ist Teil einer Reihe zu gRPC und ASP.NET Core .



Wann sollten Sie die Komprimierung in GRPC aktivieren?



Kurze Antwort: Es hängt von Ihren Nutzlasten ab.

Lange Antwort:

gRPC verwendet einen Protokollpuffer als Tool zum Serialisieren von über das Netzwerk gesendeten Anforderungs- und Antwortnachrichten. Der Protokollpuffer erstellt ein binäres Serialisierungsformat, das standardmäßig für kleine, effiziente Nutzdaten ausgelegt ist. Im Vergleich zu normalen JSON-Nutzdaten bietet protobuf eine bescheidenere Nachrichtengröße. JSON ist sehr ausführlich und lesbar. Infolgedessen enthält es Eigenschaftsnamen in den über das Netzwerk gesendeten Daten, wodurch sich die Anzahl der zu übertragenden Bytes erhöht.



Der Protokollpuffer verwendet Ganzzahlen als Bezeichner für Daten, die über das Netzwerk übertragen werden. Es verwendet das Konzept der Basis-128-Varianten, bei dem Felder mit Werten von 0 bis 127 nur ein Byte für den Transport benötigen. In vielen Fällen ist es möglich, Ihre Nachrichten auf Felder in diesem Bereich zu beschränken. Große Ganzzahlen erfordern mehr als ein Byte.



Denken Sie also daran, dass die Protobuf-Nutzlast bereits recht klein ist, da das Format darauf abzielt, die über das Netzwerk gesendeten Bytes auf die kleinstmögliche Größe zu reduzieren. Es besteht jedoch immer noch das Potenzial für eine weitere verlustfreie Komprimierung unter Verwendung eines Formats wie GZip. Dieses Potenzial muss an Ihren Nutzdaten getestet werden, da Sie nur dann eine Größenreduzierung feststellen, wenn Ihre Nutzdaten über genügend sich wiederholende Textdaten verfügen, um von der Komprimierung zu profitieren. Bei kleinen Antwortnachrichten führt der Versuch, sie zu komprimieren, möglicherweise zu mehr Bytes als die Verwendung einer nicht komprimierten Nachricht. das ist eindeutig nicht gut.



Bemerkenswert ist auch der Komprimierungsaufwand des Prozessors, der den durch die Größenreduzierung erzielten Gewinn überwiegen kann. Sie sollten den CPU- und Speicheraufwand für Anforderungen nach dem Ändern der Komprimierungsstufe verfolgen, um ein vollständiges Bild Ihrer Dienste zu erhalten.



Die ASP.NET Core Server-Integration verwendet standardmäßig keine Komprimierung, wir können sie jedoch für den gesamten Server oder bestimmte Dienste aktivieren. Dies scheint eine vernünftige Standardeinstellung zu sein, da Sie Ihre Antworten für verschiedene Methoden im Laufe der Zeit verfolgen und die Vorteile der Komprimierung bewerten können.



Wie aktiviere ich die Antwortkomprimierung in GRPC?



Bisher habe ich zwei Hauptansätze gefunden, um die gRPC-Antwortkomprimierung zu verbinden. Sie können dies auf Serverebene so konfigurieren, dass alle gRPC-Dienste die Komprimierung auf Antworten anwenden, oder auf der Ebene der einzelnen Dienste.



Konfiguration auf Serverebene



services.AddGrpc(o =>
{
   o.ResponseCompressionLevel = CompressionLevel.Optimal;
   o.ResponseCompressionAlgorithm = "gzip";
});


Startup.cs GitHub



Wenn ein gRPC Dienst in einem Dependency Injection Container unter Verwendung eines Verfahrens Registrierung AddGrpcinnen ConfigureServices, haben wir die Möglichkeit zu konfigurieren in GrpcServiceOptions. Auf dieser Ebene wirken sich Parameter auf alle vom Server implementierten gRPC-Dienste aus.



Mithilfe einer Überlastungsmethode AddGrpckönnen wir bereitstellen Action<GrpcServiceOptions>. Im obigen Code-Snippet haben wir den Komprimierungsalgorithmus "gzip" gewählt. Wir können dies auch feststellen, CompressionLevelindem wir die Zeit manipulieren, die wir für die Datenkomprimierung opfern, um eine kleinere Größe zu erhalten. Wenn der Parameter nicht angegeben wird, wird standardmäßig die aktuelle Implementierung verwendet CompressionLevel.Fastest. Im vorherigen Snippet haben wir mehr Zeit für die Komprimierung eingeräumt, um die Anzahl der Bytes auf die kleinstmögliche Größe zu reduzieren.



Service Level Konfiguration



services.AddGrpc()
   .AddServiceOptions<WeatherService>(o =>
       {
           o.ResponseCompressionLevel = CompressionLevel.Optimal;
           o.ResponseCompressionAlgorithm = "gzip";
       });


Startup.cs GitHub



Der Anruf AddGrpckehrt zurück IGrpcServerBuilder. Wir können eine Erweiterungsmethode aufrufen, die vom Builder aufgerufen wird AddServiceOptions, um Parameter für jeden Service separat bereitzustellen. Diese Methode ist generisch und akzeptiert den Typ des gRPC-Dienstes, für den die Parameter gelten sollen.



Im vorherigen Beispiel haben wir Parameter für Aufrufe angegeben, die von der Implementierung verarbeitet werden WeatherService. Auf dieser Ebene stehen dieselben Optionen zur Verfügung, die wir für die Konfiguration auf Serverebene erläutert haben. In diesem Szenario erhalten die anderen gRPC-Dienste auf diesem Server nicht die Komprimierungsoptionen, die wir für diesen bestimmten Dienst festgelegt haben.



GRPC-Client-Anfragen



Nachdem die Antwortkomprimierung aktiviert ist, müssen wir sicherstellen, dass unsere Anforderungen darauf hinweisen, dass unser Client komprimierte Inhalte akzeptiert. Tatsächlich ist dies standardmäßig aktiviert, wenn es GrpcChannelmit einer erstellten Methode verwendet wird ForAddress, sodass wir in unserem Client-Code nichts tun müssen.



var channel = GrpcChannel.ForAddress("https://localhost:5005");


Program.cs GitHub



Auf diese Weise erstellte Kanäle senden bereits einen "grpc-accept-encoding" -Header, der den Komprimierungstyp gzip enthält. Der Server liest diesen Header und stellt fest, dass der Client die Rückgabe komprimierter Antworten zulässt.



Eine Möglichkeit, den Komprimierungseffekt zu visualisieren, besteht darin, die Protokollierung für unsere Anwendung zur Entwurfszeit zu aktivieren. Dies kann durch Ändern der Datei appsettings.Development.jsonwie folgt erfolgen:



{
 "Logging": {
   "LogLevel": {
       "Default": "Debug",
       "System": "Information",
       "Grpc": "Trace",
       "Microsoft": "Trace"
   }
 }
}


appsettings.Development.json GitHub



Beim Starten unseres Servers erhalten wir viel detailliertere Konsolenprotokolle.



info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
     Executing endpoint 'gRPC - /WeatherForecast.WeatherForecasts/GetWeather'
dbug: Grpc.AspNetCore.Server.ServerCallHandler[1]
     Reading message.
dbug: Microsoft.AspNetCore.Server.Kestrel[25]
     Connection id "0HLQB6EMBPUIA", Request id "0HLQB6EMBPUIA:00000001": started reading request body.
dbug: Microsoft.AspNetCore.Server.Kestrel[26]
     Connection id "0HLQB6EMBPUIA", Request id "0HLQB6EMBPUIA:00000001": done reading request body.
trce: Grpc.AspNetCore.Server.ServerCallHandler[3]
     Deserializing 0 byte message to 'Google.Protobuf.WellKnownTypes.Empty'.
trce: Grpc.AspNetCore.Server.ServerCallHandler[4]
     Received message.
dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]
     Sending message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[9]
     Serialized 'WeatherForecast.WeatherReply' to 2851 byte message.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQB6EMBPUIA" sending HEADERS frame for stream ID 1 with length 104 and flags END_HEADERS
trce: Grpc.AspNetCore.Server.ServerCallHandler[10]
     Compressing message with 'gzip' encoding.
trce: Grpc.AspNetCore.Server.ServerCallHandler[7]
     Message sent.
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
     Executed endpoint 'gRPC - /WeatherForecast.WeatherForecasts/GetWeather'
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQB6EMBPUIA" sending DATA frame for stream ID 1 with length 978 and flags NONE
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQB6EMBPUIA" sending HEADERS frame for stream ID 1 with length 15 and flags END_STREAM, END_HEADERS
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
     Request finished in 2158.9035ms 200 application/grpc


Log.txt GitHub



In der 16. Zeile dieses Protokolls sehen wir, dass WeatherReply (in diesem Beispiel ein Array von 100 WeatherData-Elementen) in protobuf serialisiert wurde und eine Größe von 2851 Byte hat.



Später in Zeile 20 sehen wir, dass die Nachricht mithilfe der GZIP-Codierung komprimiert wurde, und in Zeile 26 sehen wir die Datenrahmengröße für diesen Aufruf, die 978 Byte beträgt. In diesem Fall wurden die Daten gut komprimiert (um 66%), da die sich wiederholenden WeatherData-Elemente Text enthalten und viele der Werte in der Nachricht wiederholt werden.



In diesem Beispiel hatte die gzip-Komprimierung einen guten Einfluss auf die Größe der Daten.



Deaktivieren Sie die Antwortkomprimierung bei der Implementierung der Servicemethode



Die Komprimierung der Antwort kann bei jeder Methode gesteuert werden. Derzeit habe ich einen Weg gefunden, es einfach auszuschalten. Wenn die Komprimierung für einen Dienst oder Server aktiviert ist, können wir die Komprimierung im Rahmen der Implementierung der Dienstmethode deaktivieren.



Schauen wir uns das Serverprotokoll an, wenn Sie eine Dienstmethode aufrufen, die WeatherData-Nachrichten vom Server überträgt. Wenn Sie mehr über das Streaming auf den Server erfahren möchten, lesen Sie meinen vorherigen Artikel Streaming von Daten auf einen Server mit gRPC und .NET Core .



info: WeatherForecast.Grpc.Server.Services.WeatherService[0]
     Sending WeatherData response
dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]
     Sending message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[9]
     Serialized 'WeatherForecast.WeatherData' to 30 byte message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[10]
     Compressing message with 'gzip' encoding.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQBMRRH10JQ" sending DATA frame for stream ID 1 with length 50 and flags NONE
trce: Grpc.AspNetCore.Server.ServerCallHandler[7]
     Message sent.


Log.txt GitHub



In der 6. Zeile sehen wir, dass die einzelne WeatherData-Nachricht 30 Byte groß ist. Zeile 8 wird komprimiert und Zeile 10 zeigt, dass die Daten jetzt 50 Byte lang sind - mehr als die ursprüngliche Nachricht. In diesem Fall hat die gzip-Komprimierung keinen Vorteil für uns. Wir sehen eine Zunahme der Gesamtgröße der über das Netzwerk gesendeten Nachricht.



Wir können die Komprimierung für eine bestimmte Nachricht deaktivieren, indem wir festlegen, dass eine Dienstmethode WriteOptionsaufgerufen wird.



public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context)
{
   context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);

   //  ,    
}


WeatherService.cs GitHub



Wir können festgelegt WriteOptionsan ServerCallContextder Spitze unserer Service - Methode. Wir übergeben eine neue Instanz WriteOptions, WriteFlagsdie auf gesetzt ist NoCompress. Diese Parameter werden für den nächsten Eintrag verwendet.



Beim Streaming von Antworten kann dieser Wert auch auf eingestellt werden IServerStreamWriter.



public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context)
{   
   responseStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);

   //     
}


WeatherService.cs GitHub



Wenn wir diesen Parameter verwenden, zeigen die Protokolle, dass keine Komprimierung auf Aufrufe dieser Dienstmethode angewendet wird.



info: WeatherForecast.Grpc.Server.Services.WeatherService[0]
     Sending WeatherData response
dbug: Grpc.AspNetCore.Server.ServerCallHandler[6]
     Sending message.
trce: Grpc.AspNetCore.Server.ServerCallHandler[9]
     Serialized 'WeatherForecast.WeatherData' to 30 byte message.
trce: Microsoft.AspNetCore.Server.Kestrel[37]
     Connection id "0HLQBMTL1HLM8" sending DATA frame for stream ID 1 with length 35 and flags NONE
trce: Grpc.AspNetCore.Server.ServerCallHandler[7]
     Message sent.


Log.txt GitHub



Jetzt ist eine 30-Byte-Nachricht im DATA-Frame 35 Byte lang. Es gibt einen kleinen Overhead von zusätzlichen 5 Bytes, über den wir uns hier keine Sorgen machen müssen.



Deaktivieren Sie die Antwortkomprimierung vom GRPC-Client



Standardmäßig enthält ein gRPC-Kanal Parameter, die bestimmen, welche Codierungen er akzeptiert. Diese können beim Erstellen eines Kanals konfiguriert werden, wenn Sie die Komprimierung von Antworten von Ihrem Client deaktivieren möchten. Im Allgemeinen würde ich dies vermeiden und den Server entscheiden lassen, was zu tun ist, da er besser weiß, was komprimiert werden kann und was nicht. Manchmal müssen Sie dies jedoch vom Client aus überwachen.



Die einzige Möglichkeit, die ich bisher in meiner API-Forschung gefunden habe, besteht darin, einen Kanal einzurichten, indem ich eine Instanz übergebe GrpcChannelOptions. Eine der Eigenschaften dieser Klasse ist für CompressionProviders- IList<ICompressionProvider>. Wenn dieser Wert null ist, fügt die Client-Implementierung standardmäßig automatisch einen Gzip-Komprimierungsanbieter hinzu. Dies bedeutet, dass der Server gzip verwenden kann, um Antwortnachrichten zu komprimieren, wie wir gesehen haben.



private static async Task Main()
{
   using var channel = GrpcChannel.ForAddress("https://localhost:5005", new GrpcChannelOptions { CompressionProviders = new List<ICompressionProvider>() });
   var client = new WeatherForecastsClient(channel);
   var reply = await client.GetWeatherAsync(new Empty());
   foreach (var forecast in reply.WeatherData)
  {
       Console.WriteLine($"{forecast.DateTimeStamp.ToDateTime():s} | {forecast.Summary} | {forecast.TemperatureC} C");
   }
   Console.WriteLine("Press a key to exit");
   Console.ReadKey();
}


Program.cs GitHub

In diesem Beispiel-Clientcode setzen GrpcChannelund pushen wir eine neue Instanz GrpcChannelOptions. Wir weisen der Immobilie CompressionProviderseine leere Liste zu . Da wir jetzt keine Anbieter in unserem Kanal angeben, wenn Anrufe erstellt und über diesen Kanal gesendet werden, enthalten sie keine Komprimierungscodierungen im grpc-accept-encoding-Header. Der Server sieht dies und gzip die Antwort nicht.



Zusammenfassung



In diesem Beitrag haben wir die Möglichkeit untersucht, Antwortnachrichten vom gRPC-Server zu komprimieren. Wir haben festgestellt, dass dies in einigen (aber nicht allen) Fällen zu einer geringeren Nutzlast führen kann. Wir haben gesehen, dass Clientaufrufe standardmäßig den gzip-Wert "grpc-accept-encoding" in den Headern enthalten. Wenn der Server für die Anwendung der Komprimierung konfiguriert ist, erfolgt dies nur, wenn der unterstützte Komprimierungstyp mit dem Anforderungsheader übereinstimmt.



Wir können GrpcChannelOptionsbeim Erstellen eines Kanals für den Client konfigurieren , dass die gzip-Komprimierung deaktiviert wird. Auf dem Server können wir den gesamten Server auf einmal oder einen separaten Dienst konfigurieren, um Antworten zu komprimieren. Wir können dies auch auf der Ebene jeder Servicemethode überschreiben und deaktivieren.



Um mehr über gRPC zu erfahren, können Sie alle Artikel lesen, die Teil meiner sindgRPC- und ASP.NET Core-Serien .






ALLES ÜBER DEN KURS






Weiterlesen






All Articles