Проблема

Когда мы разрабатываем приложение, которое разделено на независимые автономные компоненты, мы говорим о микросервисной архитектуре. Для взаимодействия компонентов используется API. Самый популярным API является REST. Это обусловлено его гибкостью, эффективностью (в большинстве сценариев) и тем, что он легко масштабируется. 

Большая часть реализаций REST использует стандарт JSON для обмена сообщениями. Обычно это удобно - сама по себе такая структура легко читается людьми и предоставляет независимость от языка программирования.

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

Зачем нужен gRPC

gRPC (Remote Procedure Calls) — это система удалённого вызова процедур (RPC) с открытым исходным кодом, первоначально разработанная в Google. В качестве основного протокола передачи применяется HTTP/2, для описания процедур применяется “Protocol Buffers”. Это в свою очередь приносит дополнительные преимущества: сжатие HTTP-заголовков и мультиплексирование запросов.

gRPC предполагает возможность аутентификации, потоковой передачи данных в любую сторону, управление потоками, отмену и time-out запросов, при этом выделяется кроссплатформенностью за счет генерации исходного кода классов для всех популярных языков программирования. 

Типичная реализация gRPC

Когда использовать gRPC

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

Стоит взглянуть на таблицу их основных отличий.

Сравнение gRPC и REST

Технология

gRPC

REST

Протокол

http 2.0

http 1.1

Формат обмена данными

protobuf

JSON

Избыточность

Нет

Да

Декларативность

Единый .proto файл

Нет единого соглашения

Хотя REST подход также может использовать HTTP 2 для обмена данными, тем не менее он будет ограничен моделью запрос-ответ, при которой не применяется поддержка двусторонней потоковой передачи данных возможной для HTTP При этом, конечно же сохраняется возможность унарных взаимодействий, которые реализует HTTP 1.1 (один запрос - один ответ).

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

Скорость ответа REST и gRPC

При этом в gRPC наименования полей, ожидаемых запросов и возвращаемых ответов определяется и описывается в одном месте - в файле .proto. И если вы хотя бы раз разрешали конфликты в наименовании полей вида ObjectID и object_id, то вам определенно понравится работать с gRPC.

Пример .proto файла:

syntax = "proto3";

/* Наименование пакета данных */

message Calculator {

    int32 xxx = 1;

    int32 yyy = 2;

}

message CalculatorRequest {

    Calculator calculator = 1;

}

message CalculatorResponse {

    int32 result = 1;

}

Когда не использовать gRPC

gRPC при всех своих достоинствах не лишен недостатков. Вот парочка:

  • Использование HTTP/2 в gRPC, что делает невозможным реализацию клиента gRPC в браузере - вместо этого приходится использовать прокси. 

  • Более сложная отладка по сравнению с REST из-за бинарного обмена сообщениями

Реальный кейс

Как упоминалось выше, для работы с gRPC достаточно одного .proto файла.

Мы делали реализацию API на Python, где использовали дуплексное (двунаправленное соединение) для сервиса потокового распознавания речи в текст.

Дуплексное соединение gRPC на Python:

def stream_recorder(self) -> Union[RecognitionRequest, str]:

    """Потоковый захват речи"""

    for _ in range(0, int(self.rate / self.chunk * self.seconds)):

        data = self.stream.read(self.chunk, exception_on_overflow=False)

        yield RecognitionRequest(audio=data)

def stream_recognize(self) -> str:

    """Потоковое распознавание речи"""

    credentials = grpc.ssl_channel_credentials()

    with grpc.secure_channel(f'{settings.SPEECH_SERVER}:{settings.SPEECH_PORT}',

                            credentials=credentials) as chennel:

        stub = VoiceAssistantServiceStub(chennel)

        metadata = [('authorization', f'Bearer {settings.SPEECH_API_TOKEN}'), ('audioformat', 'lpcm')]

        for response in stub.RecognizeAudio(self.stream_recorder(), metadata=metadata):

            if response.chunks[0].final:

                return response.chunks[0].text.strip()

Эти декларативные преимущества gRPC работают не только в момент написания кода. Особенно удобно делать отладку соединения в Postman, который предоставляет поддержку протокола. 

Даже не имея доступа к удаленному серверу мы можем имитировать запросы с моковыми данными и эмулировать ответ нажатием одной кнопки.

Поддержка gRPC в Postman

Выводы

В отличие от REST, gRPC - решение, которое не зависит от платформы и языка. И благодаря HTTP/2 и протоколу Protobuf решает проблемы, связанные со скоростью передачи данных, их весом, в результате повышая эффективность обмена сообщениями.