Привет, Хабр. Для будущих студентов курса "Highload Architect" подготовили перевод материала.

Также приглашаем зарегистрироваться на открытый вебинар на тему «Репликация как паттерн горизонтального масштабирования хранилищ» с Владиславом Родиным (руководитель группы и специалист по Java Enterprise разработке). На занятии участники разберут репликацию — одну из техник масштабирования баз данных.


У меня есть несколько микросервисов, которые общаются друг с другом с помощью JSON через REST, и мне кажется, что скоро они достигнут предела производительности VPS, и мне нужно будет его апгрейдить, чтобы не было просадки по производительности.

И я решил оценить, какой выигрыш в производительности я смогу получить от gRPC (если вообще смогу) и, что более важно, сколько усилий мне придется приложить, чтобы полностью перевести все мои микросервисы на gRPC.

Что такое gRPC?

Для тех, кто еще не слышал о gRPC, gRPC — это не зависящий от языка фреймворк для удаленного вызова процедур (RPC, remote procedure calls), разработанный Google, которая серьезно вкладывается в производительность и масштабирование. Он появился довольно давно, но многие (а, может быть, только я) отложили его на второй план из-за затрат на написание IDL и дополнительного кода стабов (stub), который тоже необходимо поддерживать. В то же время REST очень легко реализуется с помощью ASP.NET Core WebAPI.

Аналогично использованию JSON для REST, для gRPC используется Protocol Buffers — не зависящий от языка формат для сериализации структурированных данных. Этим gRPC отличается от REST. Поддержка Protocol Buffers есть для всех основных языков благодаря компилятору protoc, который генерирует необходимый исходный код классов из определений в proto-файле. Еще важный момент состоит в том, что для связи gRPC использует HTTP/2, а это приносит дополнительные преимущества, такие как сжатие HTTP-заголовков и мультиплексирование запросов.

Ограничения бенчмарка

Было несколько ограничений, которые я хотел реализовать при бенчмаркинге:

  • Тестировать только чистую пропускную способность связи и не включать в процесс бизнес-логику. Я чувствовал, что это отвлечет от главного.

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

  • Отключить логгирование, чтобы оно не влияло на тесты. Оно может сильно исказить результаты.

Структура проекта

  • ModelLibrary содержит REST и gRPC-модели. Чтобы сделать тест более обобщенным, я выбрал набор данных, который включает в себя строки, целые и вещественные числа, дату и время. Я взял данные NASA о метеоритах, состоящие из 1000 единиц данных, отсюда.

Пример данных NASA о метеоритах
Пример данных NASA о метеоритах

Также для gRPC-сервиса были созданы следующие определения Protocol Buffers для сообщений и сервисов.

Определения Protocol Buffer для сообщений
Определения Protocol Buffer для сообщений

Я также тестирую производительность потоковой передачи (stream) gRPC с помощью сервиса GetLargePayload.

Определения Protocol Buffer для сервисов
Определения Protocol Buffer для сервисов
  • RestAPI содержит WebAPI, который предоставляет три метода, направленные на следующие сценарии: получение небольшой полезной нагрузки, получение большой полезной нагрузки и отправка большой полезной нагрузки.

Маршрутизация ASP.NET MVC Core
Маршрутизация ASP.NET MVC Core

Как я уже упоминал выше в разделе об ограничениях, я убрал лишнее логирование, чтобы оно не влияло на результаты бенчмарка.

Уменьшение уровня логирования для ASP.NET MVC Core WebAPI
Уменьшение уровня логирования для ASP.NET MVC Core WebAPI
  • GrpcAPI содержит реализацию сервиса и некоторый код инициализации для запуска gRPC-сервера. Мне также было интересно сравнить потоковую передачу, поэтому я реализовал дополнительный метод GetLargePayload, который итерируется по данным и за один раз отправляет одну единицу данных.

Реализация сервиса GrpcAPI
Реализация сервиса GrpcAPI
  • RESTvsGRPC содержит бенчмарк, который вызывает все эти методы двумя партиями по 100 и 200 итераций каждая, чтобы уменьшить погрешность измерения от малого времени выполнения одиночного запроса. Таким образом, приведенное ниже время выполнения тестов на самом деле больше в 100 и 200 раз.

Бенчмарк RESTvsGRPC
Бенчмарк RESTvsGRPC

Результаты 

Как и ожидалось, gRPC победил, за исключением потоковой передачи данных. Потоковая передача была немного хуже по сравнению с REST. gRPC также показал лучше производительность при отправке данных, чем при получении. Я предполагаю, что это связано со сжатием заголовков HTTP/2, но мне еще предстоит это проверить, проанализировав данные HTTP.

Результаты бенчмарка в Linux
Результаты бенчмарка в Linux

Я также запустил этот бенчмарк на Windows и получил аналогичные результаты. Однако в Windows весь тест выполнялся на минуту дольше.

Результаты бенчмарка в Windows
Результаты бенчмарка в Windows
Производительность REST vs GRPC
Производительность REST vs GRPC

Протестируйте сами

Если вы хотите повторить тест самостоятельно или подстроить его под себя, то не стесняйтесь клонировать мой репозиторий EmperorRXF/RESTvsGRPC

Для теста запустите оба сервиса, как показано ниже.

Запуск RestAPI: dotnet run -p RestAPI -c Release

Запуск GrpcAPI: dotnet run -p GrpcAPI -c Release

И запустите бенчмарк.

Запуск бенчмарка: dotnet run -p RESTvsGRPC -c Release

Выводы

Для этой конкретной полезной нагрузки gRPC примерно в семь раз быстрее REST при получении данных и в десять при отправке. В основном это связано с плотной упаковкой Protocol Buffers и использованием HTTP/2 для gRPC.

Однако на реализацию этого простого gRPC сервиса я потратил около 45 минут и всего 10 минут на создание WebAPI. Это связано с тем, что REST давно уже стал мейнстримом, и в большинстве популярных фреймворков (в том числе, в ASP.NET Core MVC) есть встроенная поддержка для быстрой реализации подобных сервисов (с помощью соглашений и шаблонов). Но и gRPC, хотя и не так сильно, тоже продвинулся вперед с тех пор, как я использовал его в последний раз, поскольку на тот момент мне пришлось вручную запускать компилятор protoc, а теперь он встроен в Visual Studio в процесс сборки. Все, что мне нужно было сделать, это использовать ItemGroup "Protobuf" в csproj-файле. Кроме того, мы можем ожидать более тесной интеграции gRPC в ASP.NET Core 3.0.

Для меня это хорошие цифры, чтобы перевести свои микросервисы на gRPC. 


Узнать подробнее о курсе "Highload Architect".

Смотреть открытый вебинар на тему «Репликация как паттерн горизонтального масштабирования хранилищ»