在GRPC中为ASP.NET CORE 3.0压缩响应

本文的翻译是在“ C#ASP.NET Core Developer”课程开始之前准备的








在我的gRPC和ASP.NET Core系列文章的这一集中我们将研究如何连接gRPC服务的响应压缩功能。



注意:在本文中,我将介绍一些有关调用设置和方法的知识,这些细节是我通过学习了解的。可能会有更准确,更有效的方法来达到相同的结果。



本文是有关gRPC和ASP.NET Core系列文章的一部分



什么时候应该在GRPC中启用压缩?



简短的答案:这取决于您的有效载荷。

长答案:

gRPC使用协议缓冲区作为序列化通过网络发送的请求和响应消息的工具。协议缓冲区创建一个二进制序列化格式,默认情况下该二进制序列化格式设计用于小型有效负载。与常规JSON有效负载相比,protobuf提供了更适中的消息大小。 JSON非常详细且可读。结果,它在通过网络发送的数据中包含属性名称,这增加了必须传输的字节数。



协议缓冲区使用整数作为通过网络传输的数据的标识符。它使用基数128变体的概念,该变体允许值从0到127的字段仅需要一个字节进行传输。在许多情况下,可以将您的消息限制在此范围内。大整数需要一个以上的字节。



因此,请记住,protobuf有效负载已经很小,因为该格式旨在将通过网络发送的字节减少到最小可能的大小。但是,仍然存在使用诸如GZip之类的格式进行进一步无损压缩的潜力。这种潜力需要在有效负载上进行测试,因为只有在有效负载具有足够的重复文本数据以受益于压缩的情况下,您才会看到大小减小的情况。也许对于小的响应消息,尝试压缩它们可能比使用未压缩的消息产生更多的字节;这显然是不好的。



还要注意的是处理器的压缩开销,它可能超过您从减小尺寸中获得的收益。更改压缩级别后,您应该跟踪请求的CPU和内存开销,以全面了解服务。



默认情况下,ASP.NET Core Server Integration不使用压缩,但是我们可以为整个服务器或特定服务启用压缩。这似乎是一个合理的默认值,因为您可以随时间跟踪不同方法的响应并评估压缩它们的好处。



如何在GRPC中启用响应压缩?



到目前为止,我发现了两种主要方法来连接gRPC响应压缩。您可以在服务器级别配置此功能,以便所有gRPC服务将压缩应用于响应,或者在单个服务级别。



服务器级配置



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


Startup.cs GitHub



当使用AddGrpc内部方法依赖项注入容器中注册gRPC服务时ConfigureServices,我们将有机会配置GrpcServiceOptions在此级别上,参数会影响服务器实现的所有gRPC服务。



使用扩展方法重载AddGrpc,我们可以提供 Action<GrpcServiceOptions>在上面的代码片段中,我们选择了“ gzip”压缩算法。我们还可以CompressionLevel通过控制为减少数据压缩而牺牲的时间来确定时间。如果未指定参数,则当前实现默认为using CompressionLevel.Fastest在上一个代码段中,我们留出了更多的压缩时间,以将字节数减少到最小。



服务水平配置



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


Startup.cs GitHub



呼叫AddGrpc返回IGrpcServerBuilder我们可以调用在构建器上调用的扩展方法,AddServiceOptions以分别为每个服务提供参数。此方法是通用方法,接受参数应适用的gRPC服务类型。



在前面的示例中,我们选择为实现实现的调用提供参数WeatherService在此级别,可以使用我们讨论的服务器级别配置相同的选项。在这种情况下,该服务器上的其他gRPC服务将不会收到我们为该特定服务设置的压缩选项。



GRPC客户端请求



现在启用了响应压缩,我们需要确保我们的请求表明我们的客户端正在接受压缩的内容。实际上,GrpcChannel与创建的方法一起使用时,默认情况下启用此功能ForAddress,因此我们无需在客户端代码中做任何事情。



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


Program.cs GitHub



以这种方式创建的通道已经发送了包含gzip压缩类型的“ grpc-accept-encoding”标头。服务器读取此标头,并确定客户端允许返回压缩的响应。



可视化压缩效果的一种方法是在设计时启用我们应用程序的日志记录。可以通过appsettings.Development.json如下修改文件来完成



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


appsettings.Development.json GitHub



启动服务器时,我们会获得更多详细的控制台日志。



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



在此日志的第16行,我们看到WeatherReply(实际上,在此示例中为100个WeatherData元素的数组)被序列化为protobuf,大小为2851字节。



稍后,在第20行,我们看到消息是使用gzip编码压缩的,而在第26行,我们可以看到此调用的数据帧大小,即978字节。在这种情况下,数据被很好地压缩了(66%),因为重复的WeatherData元素包含文本并且消息中的许多值都被重复了。



在此示例中,gzip压缩对数据大小有很好的影响。



在服务方法实现中禁用响应压缩



可以在每种方法中控制响应的压缩。目前,我已经找到一种将其关闭的方法。为服务或服务器启用压缩后,我们可以选择不使用压缩作为服务方法实现的一部分。



让我们看一下调用服务方法从服务器传输WeatherData消息时的服务器日志。如果您想了解有关流式传输到服务器的更多信息,可以阅读我以前的文章“使用gRPC和.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



在第六行,我们看到单个WeatherData消息的大小为30个字节。第8行已压缩,第10行显示数据现在长50个字节-比原始消息多。在这种情况下,gzip压缩对我们没有好处,我们发现通过网络发送的消息的总大小有所增加。



我们可以通过将特定消息设置WriteOptions为在服务方法中进行调用来禁用其压缩



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

   //  ,    
}


WeatherService.cs GitHub



我们可以WriteOptionsServerCallContext服务方法的顶部设置我们传递一个新的实例WriteOptionsWriteFlags设置为NoCompress这些参数用于下一个条目。



流式传输响应时,此值也可以设置为IServerStreamWriter



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

   //     
}


WeatherService.cs GitHub



当我们使用此参数时,日志显示没有压缩应用于此服务方法的调用。



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



现在,一个30字节的消息在DATA帧中为35字节长。这里有一个小的开销,就是额外的5个字节,我们在这里不必担心。



禁用来自GRPC客户端的响应压缩



默认情况下,gRPC通道包含确定其接受哪种编码的参数。如果要禁用来自客户端的响应压缩,则可以在创建通道时配置这些功能。通常,我会避免这种情况,让服务器决定要做什么,因为它更了解可以压缩和不能压缩的内容。但是,有时您可能需要从客户端进行监视。



迄今为止,我在API研究中发现的唯一方法是通过传入实例来建立通道GrpcChannelOptions。此类的属性之一是CompressionProviders- IList<ICompressionProvider>。默认情况下,当该值为null时,客户端实现会自动添加Gzip压缩提供程序。如我们所见,这意味着服务器可以使用gzip压缩响应消息。



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

在此示例客户端代码中,我们正在设置GrpcChannel并推送新实例GrpcChannelOptions我们正在为该属性分配CompressionProviders一个空列表。由于在创建和通过该通道发送呼叫时,我们现在不在通道中指定提供程序,因此它们将不会在grpc-accept-encoding标头中包含任何压缩编码。服务器会看到此消息,并且不会gzip响应。



概要



在本文中,我们探讨了压缩来自gRPC服务器的响应消息的可能性。我们发现在某些(但不是全部)情况下,这可能导致较小的有效负载。我们已经看到,默认情况下,客户端调用在标头中包含gzip值“ grpc-accept-encoding”。如果将服务器配置为应用压缩,则仅在支持的压缩类型与请求标头匹配时才这样做。



我们可以GrpcChannelOptions在创建客户端通道以禁用gzip压缩时进行配置。在服务器上,我们可以一次配置整个服务器,也可以配置单独的服务来压缩响应。我们还可以在每种服务方法级别覆盖并禁用此功能。



要了解有关gRPC的更多信息,您可以阅读我的所有文章。gRPC和ASP.NET Core系列






关于课程






阅读更多






All Articles