Проблема
Когда мы разрабатываем приложение, которое разделено на независимые автономные компоненты, мы говорим о микросервисной архитектуре. Для взаимодействия компонентов используется API. Самый популярным API является REST. Это обусловлено его гибкостью, эффективностью (в большинстве сценариев) и тем, что он легко масштабируется.
Большая часть реализаций REST использует стандарт JSON для обмена сообщениями. Обычно это удобно - сама по себе такая структура легко читается людьми и предоставляет независимость от языка программирования.
Недостатками такого решения является избыточность данных и сущностей из которых состоит запрос. В большинстве случаев это некритично. Проблемы начинаются в сценариях, когда нужно передавать много данных с низкой задержкой.
Зачем нужен gRPC
gRPC (Remote Procedure Calls) — это система удалённого вызова процедур (RPC) с открытым исходным кодом, первоначально разработанная в Google. В качестве основного протокола передачи применяется HTTP/2, для описания процедур применяется “Protocol Buffers”. Это в свою очередь приносит дополнительные преимущества: сжатие HTTP-заголовков и мультиплексирование запросов.
gRPC предполагает возможность аутентификации, потоковой передачи данных в любую сторону, управление потоками, отмену и time-out запросов, при этом выделяется кроссплатформенностью за счет генерации исходного кода классов для всех популярных языков программирования.
![Типичная реализация gRPC Типичная реализация gRPC](https://habrastorage.org/getpro/habr/upload_files/ad7/dda/bf2/ad7ddabf284660a305c061c0e6474191.png)
Когда использовать 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 Скорость ответа REST и gRPC](https://habrastorage.org/getpro/habr/upload_files/31d/09e/84c/31d09e84cb583fa05c9eb2efd35cb15e.png)
При этом в 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 Поддержка gRPC в Postman](https://habrastorage.org/getpro/habr/upload_files/aec/dd1/791/aecdd179140fe5fe5ee7c595e8af4657.png)
Выводы
В отличие от REST, gRPC - решение, которое не зависит от платформы и языка. И благодаря HTTP/2 и протоколу Protobuf решает проблемы, связанные со скоростью передачи данных, их весом, в результате повышая эффективность обмена сообщениями.