Pull to refresh

Землю — крестьянам, gRPC — питонистам

Level of difficultyMedium
Reading time5 min
Views18K

Всем привет, меня зовут Олег, я старший бэкенд разработчик, а также по совместительству ментор бэкенда в команде Sapphire в Битве пет-проектов. Вот уже на протяжении 10 лет Python является моим основным языком программирования.

В нашей компании, где я работаю над проектом для бессерверных вычислений, Python также - основной язык программирования (наряду с Go). Одним из корпоративных стандартов является внутренний обмен информацией по протоколу gRPC. Причины просты - данных огромное количество, нагрузка на сеть колоссальная, отсюда и потребность в экономии размера передаваемых данных.

Что такое gRPC (из вики)

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

Только правильное питание для сервисов!
Только правильное питание для сервисов!

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

  1. Для начала надо освоить protocol buffers и составить корректный .proto файл, чтобы описать интерфейс будущего gRPC сервиса.

  2. Потом с помощью Python библиотеки Protobuf нужно на основе .proto файлов сгенерировать Python модули (pb2.py и pb2_grpc.py).

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

  4. А, да, при этом сгенерированные модули практически не читабельны и потребуют модификации (как минимум потому что там могут быть некорректно указаны импорты, как максимум - линтеры не пропустят).

Да, gRPC является более сложным протоколом по своей структуре (хотя бы потому что он не текстовый и передаваемые пакеты нельзя просто открыть и прочитать), но при этом сам он для понимания является более простым. Объясню, почему я так в этом уверен.

Для полноценного использования HTTP вам нужно обязательно знать:

  1. Какими методами можно выполнить запрос (GET, POST, PATCH, PUT, ...).

  2. С какими статус кодами может вернуться ответ и что они означают (200, 403, 502, ...).

  3. Как сформировать путь к странице.

  4. Какие бывают протоколы и в чём их отличие (http, https).

  5. Заголовки запроса и ответа (какие бывают, что означают).

  6. Зачем Query параметры и для чего их используют.

А ещё структура запроса отличается от структуры ответа. А если говорить ещё и о REST, то он тоже добавляет требования поверх всего этого списка. Таким образом, порог входа в работу с HTTP и REST оказывается довольно высоким, но при этом это стандартный протокол: фреймворков для построения веб-приложений - тьма, обучающих материалов - море, Quick Start-ов по созданию собственного веб-сервиса - выше крыши.

В gRPC же всё сведено к простому: методов как таковых нет (по факту используется только POST), путей тоже нет (вместо них используются определённые в .proto файле rpc), понятия протокола тоже нет (достаточно указать - secure или insecure), query параметры тоже отсутствуют. Но при этом написать сервер на gRPC на Python в разы сложнее, чем реализовать REST API интерфейс. Просто посмотрите на минимально рабочий пример реализации REST API на Python на фреймворке FastAPI:

from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

Очень просто, не правда ли? А вот для работы с gRPC на Python нужно выполнить все шаги, которые я описал выше. И кстати, при изменении интерфейса в .proto файле путь надо будет пройти снова. Такой процесс делает работу с gRPC нереально сложной для новичков.

В рамках Битвы пет-проектов мы в Sapphire тоже нашли потребность в общении сервисов между собой. Изначально для этого у нас была Kafka, но она точно не подходит для получения информации от другого сервиса. Мы могли бы использовать REST (вряд ли у нас будет высокая нагрузка на внутреннюю сеть), но так как у меня есть опыт работы с gRPC на работе, то реализовать gRPC интерфейс показалось хорошей идеей. Но когда дело дошло до реализации, оказалось что реализовать сервис с нуля сложно и он будет выглядеть крайне громоздко, требуя написания большего количества кода, чем весь реализованный REST API.

Поэтому в процессе изучения я решил поискать готовые решения, которые бы упрощали реализацию gRPC сервиса на Python. Конечно же, я их нашёл:

  1. Fast-GRPC - классный асинхронный фреймворк. Мне он понравился, но было несколько проблем: использования старого pydantic (что я изменил в форке) и баги (например, в метод, который обрабатывает запрос, в self передаётся значение None). В принципе именно эти баги и заставили от него отказаться, так как ковыряться в запутанной структуре оказалось сложно и требовало много времени.

  2. grpcalchemy - классный синхронный фреймворк. Он тоже понравился, но его проблемой было то, что он только синхронный, а реализация асинхронного сервера уже давно висит в TODO. Я попытался его реализовать, но встроить его в существующую систему оказалось сложнее, чем я думал.

  3. Остальное из awesome-grpc - я просмотрел всё и из подходящих оставалась только библиотека grpclib, которая хоть и не обладала проблемами вышеперечисленных, но и реализацию не делала простой.

После двухдневных поисков и проб я потерял веру в то, что найду подходящий инструмент и решил, что пришло время реализовать свою имплементацию. Встречайте: Fast-gRPC (да, я не придумал ничего лучше). Установить довольно просто:

pip install py-fast-grpc

А минимальная имплементация сервера выглядит так:

from fast_grpc import FastGRPC, FastGRPCService, grpc_method
from pydantic import BaseModel


class HelloRequest(BaseModel):
    name: str


class HelloResponse(BaseModel):
    text: str


class Greeter(FastGRPCService):
    @grpc_method(request_model=HelloRequest, response_model=HelloResponse)
    async def say_hello(self, request: HelloRequest) -> HelloResponse:
        return HelloResponse(text=f"Hello, {request.name}!")


app = FastGRPC(Greeter())
app.run()

Как видите, здесь пропущены шаги с составлением proto файла, с генерацией pb2 и pb2_grpc модулей, с их подключением к проекту, всё это работает под капотом сервиса, программисту нужно только описать pydantic модели для запроса и ответа. Дополнительно можно указать имя сервиса, название каждого метода, порт сервера и включить рефлексию, но по умолчанию о них можно не задумываться. Все proto, pb2 и pb2_grpc файлы, которые нужны для работы сервера, генерируются в корне проекта, но это также можно изменить, указав нужные директории в аргументах сервиса.

К сожалению, в моделях запросов и ответов сейчас поддерживаются только стандартные Python типы данных (не включая коллекции, такие как list и dict) и uuid (то что сейчас требовалось в рамках Битвы пет-проектов), но в дальнейшем я предполагаю расширение возможностей. Даёшь простой gRPC на Python всем!

P.S. Кстати, я также веду некоторые свои соцсети, в которых рассказываю о Python, об IT и вообще о жизни - VK и Telegram.

Tags:
Hubs:
Total votes 14: ↑12 and ↓2+14
Comments30

Articles