gRPC: проблемы и решения при переходе с REST
- вторник, 17 сентября 2024 г. в 00:00:08
Вот уже 24 года мы используем сложный для понимания, завязанный на особенности HTTP REST-протокол. Не пора ли переходить к чему-то более современному?
Привет! Меня зовут Игорь Алексеев и я работаю бэкенд-разработчиком в компании Garage Eight. Некоторое время назад я внедрил gRPC для части своих сервисов. В этой статье поговорим о том, что такое gRPC, сравним его с возможными альтернативами, рассмотрим преимущества gRPC и пробежимся по проблемам, которые возникают почти в каждом проекте, где этот протокол приходится внедрять.
Уточню, я говорю только про gRPC в связке с Protobuf, поскольку GSON в отсутствие типизации, кодогенерации и других фич нивелирует большинство преимуществ.
gRPC — это протокол вызова удаленных процедур, когда мы обращаемся к сетевому сервису как будто к локальному методу.
Для передачи данных gRPC используется бинарный формат, что приносит как бонусы, так и ограничения.
На транспортном уровне gRPC использует HTTP2 over TCP. Важным моментом является автоматическая, удобная генерация стабов для любых языков из описания контракта в protobuf файле, в кратком и удобном формате.
Далее сравним gRPC с другими популярными протоколами – jsonRPC2 и GraphQL.
jsonRPC2 — ещё один популярный выбор для RPC. Он легок, понятен, имеет маленькое описание и удобен в отладке, но jsonRPC2 расходует относительно много трафика, а это накладные расходы на Unmarshalling.
GraphQL предназначен для данных в произвольной форме. Он текстовый, но объемный, и парсинг запроса может быть очень тяжелый. Так же, как jsonRPC2, GraphQL может работать по HTTP/1 и легко вписывается в любую инфраструктуру.
И наконец наш gRPC — это предопределенный формат запроса, сложности с интеграцией и бинарный формат, что ограничивает нас в дебаге и тестировании привычными средствами.
Первый плюс — это легкое распространение контрактов Protobuf. Контракты можно распространять любыми способами, о нескольких из этих способов я расскажу чуть дальше. Удобный формат шаблона. Если вас напрягает многословность OpenAPI и Swagger, то этот формат сам по себе может стать аргументом в пользу gRPC.
Второй плюс — gRPC передает данные в бинарной форме, выигрыш по объему передаваемых данных 50-80% экономии трафика, что может быть существенно важно для средств с высокой нагрузкой.
Третий плюс — благодаря соединению по HTTP/2 и благодаря мультиплексированию и переиспользованию единственного сетевого соединения этот протокол позволяет сократить сетевые задержки в 2-10 раз, что тоже особенно важно для нагруженных систем. Это связано как с экономией трафика, так и с отсутствием накладных расходов на переподключение.
Итак, мы разобрались, для чего нужен этот протокол и какие у него преимущества. Теперь давайте рассмотрим проблемы, которые возникают при использовании gRPC в реальной инфраструктуре.
Исторически, как только вы начинаете связывать два или более сервисов через этот протокол, происходит распространение protobuf модели.
Решение 1. Тут можно пойти традиционным путём и взять репозиторий GitHub модуля веб-артефакта, опубликовать его на Artifactory, Nexus, S3 и потом по мере необходимости стягивать. Это неплохой вариант, потому что он задействует существующую структуру.
Решение 2. Второй вариант для более продвинутой инфраструктуры — monorepo, когда все наши связанные сервисы находятся в одном Git репозитории. В отдельную директорию можно положить все контракты, упорядочить их, сложить рядом с документацией.
Так делают в Google, Microsoft и во многих других крупных компаниях. Такое решение может быть удобным, а может принести огромные расходы и потребовать специализированного отдела для поддержки репозитория.
Решение 3. Перекладывать руками. Если вы в начале пути, связанных сервисов немного, а обновления происходит редко, то лучшее решение — скопировать руками.
Решение 4. Также есть специальные утилиты и даже выделенные репозитории. Например, существует сервис buf.build с разнообразными возможностями по тестированию, версионированию и даже автодокументированию изменений в описаниях ваших API.
Если кто-то пользовался — отпишитесь в комментариях. Очень интересно узнать, как работает их модель тарификации по $0.5/месяц за один тип в сообщении.
Решение 5. Рефлексия протокола. Каждый gRPC сервер, если на нём включена рефлексия, может отдать список своих методов и список формата своих сообщений. По умолчанию эта функция отключена. Как распространять эту модель не очень понятно, т.к. она не предназначена для прода, но может пригодиться в исследовании незнакомого ресурса без доступа к исходникам.
Из всего вышеперечисленного я бы рекомендовал монорепо, но надо помнить о нюансах и не использовать его как единый склад для всех приложений компании.
После того, как сервис написан, начинается интеграция. Допустим, мы собрали наши сервисы, запустили их и теперь надо связать несколько сервисов между собой или открыть их в Интернет.
Traefik, Envoy, Istio. Всё пройдёт легко и удобно, если у вас современный проект с Traefik/Envoy/Istio — с ними все прекрасно, в них gRPC работает из коробки. Вот только они медленные и в проде их можно встретить в основном на хипстерских проектах.
Nginx. Если же сеть построена на Nginx, то есть две новости: gRPC настроить в нем можно, но потребуется определенная настойчивость.
Во-первых, Nginx не поддерживает HTTP/2 без TLS ввиду своей архитектуры, то есть вам придется озаботиться выпуском сертификатов, даже если оба сервиса сосуществуют на одной и той же машине.
Следующая проблема тоже связана с Nginx — это grpc_proxy_module. Он немного отличается от http_proxy_module. Немного, но достаточно, чтобы запутаться, где set_header, а где add_header.
Как только мы настроили Nginx, может возникнуть мысль: почему бы не использовать gRPS-web и не связать фронт через тот же самый протокол?
И это третья проблема — интеграция бека и фронта. Идея не плохая, она используется, но по сути оказалось, что gRPC-web развит недостаточно.
Я пошел сложным путем и взял gRPC-web для коммуникации с Web клиентом. Неожиданно оказалось, что добиться генерации пригодного для Web TypeScript от компилятора protoc не так уж и легко. Фактически возможность оказалась экспериментальной и найти рабочий вариант генерации вебстабов оказалось невозможно. Потребовалось несколько часов для написания мастер строки параметров. Эту строку вы можете найти в репозитории с примерами на моем гитхабе. Ссылка будет в конце статьи.
Четвёртая проблема так же связана с использованием gRPC-Web.
CSRF — это метод защиты пользователя в вебе от злоумышленников, когда они делают копию сайта и перехватывают данные, проксируя запросы к подлиннику.
CSRF позволяет пользователю браузера быть уверенным, что на запрос через Интернет отвечает авторизованный сервер с того же URI.
Проблема заключается в том, что gRPC не позволяет использовать CSRF из коробки. Поддержка в принципе не реализована. Таким образом для использования в открытом вебе потребуется как минимум реализовать ответ OPTIONS Acces-Control-Allow-*, а как максимум — расширение с подписанием запросов токеном.
Так что если вы ленивы, как я, оставьте gRPC для коммуникаций между фрагментами бека.
В процессе работы над этой группой сервисов оказалось, что когда сервисы находятся совсем не на одной ноде, а в разных кластерах, отладить связь между сервисами через пять последовательных Nginx-прокси становится проблемой. А ещё сложно убедиться, что наши сервисы работают после деплоя в момент старта. Что же может помочь успешной интеграции?
Проверка соединения становится абсолютно обязательной. Простой пинг или healthcheck на этапе старта даст отличную подсказку, если что-то идет не так.
Второй совет очевиден, но с поправкой на почти обязательность TLS. В каждом сервисе стоит реализовать переключение между режимами работы сети. TLS, сертификат, верификация, порт или хост позволят переключаться между локальными и удаленными сервисами, что не раз потребуется при разработке.
Поскольку протокол у нас бинарный, традиционными утилитами вроде CURL потестировать ничего не получится.
Postman, Insomnia или родной grpc_cli имеют на самом деле неудобную и ограниченную поддержку. Например, в Postman все еще нельзя загрузить proto файл и получить предзаполненные поля. Так что специальный, написанный под конкретный случай тестовый клиент — абсолютный маст-хэв.
Ну и в заключение, здесь, в Garage 8, мы почти везде в проде используем jsonRPC2. Это понятный формат с традиционной интеграцией. Все перечисленные выше проблемы, о которых надо помнить и которые придётся решать, в jsonRPC2 отсутствуют. При упомянутых в сравнении недостатках это вполне хороший вариант.
Однако теперь у нас есть участок с gRPС, и он нам нравится. Мы получили значимую выгоду, которая перевешивает все минусы, и сумели преодолеть проблемы с интеграцией.
В этой статьте мы рассмотрели преимущества gRPC, сравнили его с другими протоколами и, надеюсь, нашли, как преодолеть сложности. Ниже несколько ссылок, самая важная из них — официальная документация.
Пишите в телеграм, скачивайте репозиторий с примерами, оттуда можно свободно взять все перечисленные мной решения и использовать в своем проекте!
Полезные ссылки:
https://stackoverflow.com/questions/55922886/how-to-share- protobuf-definitions-for-grpc