Neste episódio da minha série sobre gRPC e ASP.NET Core, veremos como conectar a funcionalidade de compressão de resposta dos serviços gRPC.
NOTA : Neste artigo, abordo alguns dos detalhes de compressão que aprendi aprendendo sobre configurações e métodos de chamada. Provavelmente, existem abordagens mais precisas e eficazes para obter os mesmos resultados.
Este artigo faz parte de uma série sobre gRPC e ASP.NET Core .
Quando você deve habilitar a compactação no GRPC?
Resposta curta: depende de suas cargas úteis.
Resposta longa:
gRPC usa um buffer de protocolo como uma ferramenta para serializar mensagens de solicitação e resposta enviadas pela rede. O buffer de protocolo cria um formato de serialização binário projetado para cargas úteis pequenas e eficientes por padrão. Comparado aos payloads JSON regulares, protobuf oferece um tamanho de mensagem mais modesto. JSON é bastante detalhado e legível. Como resultado, inclui nomes de propriedades nos dados enviados pela rede, o que aumenta o número de bytes que devem ser transferidos.
O buffer de protocolo usa números inteiros como identificadores de dados transmitidos pela rede. Ele usa o conceito de variantes de base 128, que permite que campos com valores de 0 a 127 exijam apenas um byte para transporte. Em muitos casos, é possível limitar suas mensagens a campos neste intervalo. Inteiros grandes requerem mais de um byte.
Portanto, lembre-se, a carga útil do protobuf já é bem pequena, pois o formato visa reduzir os bytes enviados pela rede ao menor tamanho possível. No entanto, ainda há potencial para mais compactação sem perdas usando um formato como o GZip. Esse potencial precisa ser testado em suas cargas, pois você só verá uma redução de tamanho se sua carga tiver dados textuais repetitivos suficientes para se beneficiar da compactação. Talvez para pequenas mensagens de resposta, tentar compactá-las pode resultar em mais bytes do que usar uma mensagem não compactada; o que claramente não é bom.
Também digno de nota é a sobrecarga de compressão do processador, que pode superar o ganho obtido com a redução de tamanho. Você deve rastrear a sobrecarga da CPU e da memória para solicitações depois de alterar o nível de compactação para obter uma imagem completa de seus serviços.
O ASP.NET Core Server Integration não usa compactação por padrão, mas podemos habilitá-la para todo o servidor ou serviços específicos. Este parece ser um padrão razoável, pois você pode rastrear suas respostas para diferentes métodos ao longo do tempo e avaliar os benefícios de compactá-los.
Como habilito a compactação de resposta no GRPC?
Até agora, encontrei duas abordagens principais para conectar a compactação de resposta gRPC. Você pode configurar isso no nível do servidor para que todos os serviços gRPC apliquem compactação às respostas ou no nível de serviço individual.
Configuração de nível de servidor
services.AddGrpc(o =>
{
o.ResponseCompressionLevel = CompressionLevel.Optimal;
o.ResponseCompressionAlgorithm = "gzip";
});
Startup.cs GitHub
Ao registrar um serviço gRPC em um contêiner de injeção de dependência usando um método
AddGrpcinterno ConfigureServices, temos a oportunidade de configurar em GrpcServiceOptions. Nesse nível, os parâmetros afetam todos os serviços gRPC que o servidor implementa.
Usando uma sobrecarga de método de extensão
AddGrpc, podemos fornecer Action<GrpcServiceOptions>. No trecho de código acima, escolhemos o algoritmo de compressão “gzip”. Também podemos estabelecer CompressionLevel, manipulando o tempo que sacrificamos para compactar dados para obter um tamanho menor. Se o parâmetro não for especificado, a implementação atual é padronizada para using CompressionLevel.Fastest. No snippet anterior, permitimos mais tempo para a compactação para reduzir o número de bytes ao menor tamanho possível.
Configuração de nível de serviço
services.AddGrpc()
.AddServiceOptions<WeatherService>(o =>
{
o.ResponseCompressionLevel = CompressionLevel.Optimal;
o.ResponseCompressionAlgorithm = "gzip";
});
Startup.cs GitHub
A chamada
AddGrpcretorna IGrpcServerBuilder. Podemos chamar um método de extensão chamado no construtor AddServiceOptionspara fornecer parâmetros para cada serviço separadamente. Este método é genérico e usa o tipo de serviço gRPC ao qual os parâmetros devem ser aplicados.
No exemplo anterior, optamos por fornecer parâmetros para chamadas que são tratadas pela implementação
WeatherService. Nesse nível, estão disponíveis as mesmas opções que discutimos para a configuração no nível do servidor. Nesse cenário, os outros serviços gRPC neste servidor não receberão as opções de compactação que definimos para esse serviço específico.
Solicitações de clientes GRPC
Agora que a compressão de resposta está habilitada, precisamos ter certeza de que nossas solicitações indicam que nosso cliente está aceitando conteúdo compactado. Na verdade, isso é habilitado por padrão quando usado
GrpcChannelcom um método criado ForAddress, portanto, não precisamos fazer nada em nosso código de cliente.
var channel = GrpcChannel.ForAddress("https://localhost:5005");
Program.cs GitHub
Os canais criados desta forma já enviam um cabeçalho “grpc-accept-encoding” que inclui o tipo de compressão gzip. O servidor lê esse cabeçalho e determina que o cliente permite que respostas compactadas sejam retornadas.
Uma maneira de visualizar o efeito de compressão é habilitar o log para nosso aplicativo no momento do design. Isso pode ser feito modificando o arquivo da
appsettings.Development.jsonseguinte maneira:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Grpc": "Trace",
"Microsoft": "Trace"
}
}
}
appsettings.Development.json GitHub
Ao iniciar nosso servidor, obtemos logs de console muito mais detalhados.
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
Na 16ª linha desse log, vemos que WeatherReply (na verdade, uma matriz de 100 elementos WeatherData neste exemplo) foi serializada para protobuf e tem 2851 bytes de tamanho.
Posteriormente, na linha 20, vemos que a mensagem foi compactada usando a codificação gzip e, na linha 26, podemos ver o tamanho do quadro de dados para esta chamada, que é 978 bytes. Nesse caso, os dados foram bem compactados (em 66%) porque os elementos WeatherData repetidos contêm texto e muitos dos valores da mensagem são repetidos.
Neste exemplo, a compactação gzip teve um bom efeito no tamanho dos dados.
Desativar compressão de resposta na implementação do método de serviço
A compressão da resposta pode ser controlada em cada método. Atualmente, encontrei uma maneira de simplesmente desligá-lo. Quando a compactação é habilitada para um serviço ou servidor, podemos cancelar a compactação como parte da implementação do método de serviço.
Vamos dar uma olhada no log do servidor ao chamar um método de serviço que transmite mensagens WeatherData do servidor. Se você quiser saber mais sobre streaming para o servidor, pode ler meu artigo anterior Streaming de dados para um servidor com gRPC e .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
Na 6ª linha, vemos que a mensagem WeatherData individual tem 30 bytes de tamanho. A linha 8 está compactada e a linha 10 mostra que os dados agora têm 50 bytes - mais do que a mensagem original. Nesse caso, não há benefício para nós da compactação gzip, vemos um aumento no tamanho total da mensagem enviada pela rede.
Podemos desabilitar a compactação de uma mensagem específica configurando-a
WriteOptionspara chamar um método de serviço.
public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context)
{
context.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
// ,
}
WeatherService.cs GitHub
Podemos definir
WriteOptionsno ServerCallContexttopo do nosso método de serviço. Estamos passando em uma nova instância WriteOptionsque está WriteFlagsdefinido para NoCompress. Esses parâmetros são usados para a próxima entrada.
Ao fazer streaming de respostas, esse valor também pode ser definido como
IServerStreamWriter.
public override async Task GetWeatherStream(Empty _, IServerStreamWriter<WeatherData> responseStream, ServerCallContext context)
{
responseStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress);
//
}
WeatherService.cs GitHub
Quando usamos este parâmetro, os logs mostram que nenhuma compactação é aplicada às chamadas para este método de serviço.
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
Agora, uma mensagem de 30 bytes tem 35 bytes de comprimento no quadro DATA. Há uma pequena sobrecarga de 5 bytes extras com a qual não precisamos nos preocupar aqui.
Desativar compressão de resposta do cliente GRPC
Por padrão, um canal gRPC inclui parâmetros que determinam quais codificações ele aceita. Eles podem ser configurados ao criar um canal, se você quiser desabilitar a compactação das respostas de seu cliente. Geralmente, eu evitaria isso e deixaria o servidor decidir o que fazer, pois ele sabe melhor o que pode e o que não pode ser compactado. No entanto, às vezes você pode precisar monitorar isso do cliente.
A única maneira que encontrei em minha pesquisa de API até agora é configurar um canal passando em uma instância
GrpcChannelOptions. Uma das propriedades desta classe é para CompressionProviders- IList<ICompressionProvider>. Por padrão, quando esse valor é nulo, a implementação do cliente adiciona automaticamente um provedor de compactação Gzip. Isso significa que o servidor pode usar gzip para compactar as mensagens de resposta, como vimos.
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
Neste exemplo de código de cliente, estamos configurando
GrpcChannele enviando uma nova instância GrpcChannelOptions. Estamos atribuindo CompressionProvidersuma lista vazia à propriedade . Como agora não especificamos os provedores em nosso canal quando as chamadas são criadas e enviadas por meio desse canal, eles não incluirão nenhuma codificação de compactação no cabeçalho grpc-accept-encoding. O servidor vê isso e não gzip a resposta.
Resumo
Nesta postagem, exploramos a possibilidade de compactar mensagens de resposta do servidor gRPC. Descobrimos que em alguns casos (mas não em todos), isso pode levar a uma carga útil menor. Vimos que, por padrão, as chamadas do cliente incluem o valor gzip "grpc-accept-encoding" nos cabeçalhos. Se o servidor estiver configurado para aplicar compactação, só o fará se o tipo de compactação suportado corresponder ao cabeçalho da solicitação.
Podemos configurar
GrpcChannelOptionsao criar um canal para o cliente para desabilitar a compactação gzip. No servidor, podemos configurar todo o servidor de uma vez ou um serviço separado para compactar as respostas. Também podemos substituir e desativar isso no nível de cada método de serviço.
Para saber mais sobre o gRPC, você pode ler todos os artigos que fazem parte do meugRPC e séries ASP.NET Core .
TUDO SOBRE O CURSO