Как стать автором
Обновить
1294.25
OTUS
Цифровые навыки от ведущих экспертов

Сжатие ответов в GRPC для ASP.NET CORE 3.0

Время на прочтение9 мин
Количество просмотров4.2K
Автор оригинала: STEVE GORDON
Перевод статьи подготовлен в преддверии старта курса «C# ASP.NET Core разработчик».




В этом эпизоде моей серии статей о gRPC и ASP.NET Core мы рассмотрим подключение функции сжатия ответов (response compression) служб gRPC.

ПРИМЕЧАНИЕ: В этой статье я рассказываю о некоторых деталях касательно сжатия, которые я узнал, изучая параметры и методы настройки вызовов. Скорее всего есть более точные и более эффективные подходы для достижения тех же результатов.

Эта статья является частью серии о gRPC и ASP.NET Core.

Когда следует включать сжатие в GRPC?


Короткий ответ: это зависит от ваших полезных нагрузок (payloads).
Длинный ответ:
gRPC использует protocol buffer в качестве инструмента сериализации сообщений запросов и ответов, отправляемых по сети. Protocol buffer создает двоичный формат сериализации, который по умолчанию предназначен для небольших эффективных полезных нагрузок. По сравнению с обычными полезными нагрузками в формате JSON, protobuf дает более скромный размер сообщений. JSON довольно подробный и удобочитаемый. В результате он включает имена свойств в данные, передаваемые по сети, что увеличивает количество байтов, которые должны быть переданы.

В качестве идентификаторов данных, передаваемых по сети, protocol buffer использует целые числа. Он использует концепцию base 128 variants, которая позволяет полям со значениями от 0 до 127 требовать только один байт для транспортировки. Во многих случаях существует возможность ограничить ваши сообщения полями в этом диапазоне. Для больших целых чисел требуется более одного байта.

Итак, напомним, полезная нагрузка protobuf уже достаточно мала, поскольку формат нацелен на сокращение байтов, отправляемых по сети, до минимально возможного размера. Тем не менее, все еще остается потенциал для дальнейшего сжатия без потерь с помощью такого формата, как GZip. Этот потенциал необходимо тестировать на ваших полезных нагрузках, поскольку вы будете наблюдать сокращение размера только в том случае, если ваша полезная нагрузка имеет достаточное количество повторяющихся текстовых данных, чтобы извлечь из сжатия хоть какую-нибудь выгоду. Возможно, для небольших сообщений ответов попытка их сжатия может привести к получению большего количества байтов, нежели использование несжатого сообщения; что явно никуда не годится.

Также следует отметить накладные расходы процессора на сжатие, которые могут перевесить выигрыш, который вы достигаете за счет уменьшения размера. Вы должны отслеживать накладные расходы процессора и памяти на запросы после изменения уровня сжатия, чтобы оценить полную картину для ваших служб.

По умолчанию серверная интеграция ASP.NET Core не использует сжатие, но мы можем включить его для всего сервера или определенных служб. Это кажется разумным вариантом по умолчанию, поскольку вы можете отслеживать свои ответы для различных методов с течением времени и оценивать выгоду от их сжатия.

Как включить сжатие ответов в GRPC?


На данный момент я нашел два основных подхода подключения сжатия ответов gRPC. Вы можете настроить это на уровне сервера, чтобы все службы gRPC применяли сжатие к ответам, или же на уровне отдельных служб.

Настройка на уровне сервера


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

Startup.cs на GitHub

При регистрации сервиса gRPC в контейнере инъекции зависимостей с помощью метода AddGrpc внутри ConfigureServices, у нас есть возможность произвести настройку в GrpcServiceOptions. На этом уровне параметры влияют на все службы gRPC, которые реализует сервер.

Используя перегрузку расширяющего метода AddGrpc, мы можем предоставить Action<GrpcServiceOptions>. В приведенном выше фрагменте кода мы выбрали алгоритм сжатия “gzip”. Мы также можем установить CompressionLevel, манипулируя временем, которое мы жертвуем на сжатие данных для получения меньшего размера. Если параметр не уазан, текущая реализация по умолчанию использует 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

Созданные таким образом каналы уже отправляют заголовок “grpc-accept-encoding”, который включает тип сжатия gzip. Сервер считывает этот заголовок и определяет, что клиент разрешает возвращать сжатые ответы.

Один из способов визуализировать эффект сжатия — включить логирование для нашего приложения во время разработки. Этого можно сделать, изменив файл 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

В 6-й строке мы видим, что отдельное сообщение 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

Мы можем установить WriteOptions в ServerCallContext в верхней части нашего метода службы. Мы передаем новый экземпляр WriteOptions, для которого значение WriteFlags установлено в 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-байтное сообщение имеет длину 35 байтов в DATA фрейме. Есть небольшие накладные расходы, которые составляют дополнительные 5 байтов, о которых нам здесь не нужно беспокоиться.

Отключение сжатия ответа из клиента GRPC


По умолчанию канал gRPC включает параметры, которые определяют, какие кодировки он принимает. Их можно настроить при создании канала, если вы хотите отключить сжатие ответов из вашего клиента. Как правило, я бы избегал этого и позволял серверу решать, что делать, поскольку он лучше знает, что можно и что нельзя сжимать. Тем не менее, иногда вам может потребоваться контролировать это из клиента.

Единственный способ, который я нашел в своем исследовании API на сегодняшний день, — это настроить канал, передав экземпляр GrpcChannelOptions. Одно из свойств этого класса предназначено для CompressionProvidersIList<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.



ВСЁ О КУРСЕ



Читать ещё


Теги:
Хабы:
Всего голосов 5: ↑5 и ↓0+5
Комментарии0

Публикации

Информация

Сайт
otus.ru
Дата регистрации
Дата основания
Численность
101–200 человек
Местоположение
Россия
Представитель
OTUS