golang

gRPC: проблемы и решения при переходе с REST

  • вторник, 17 сентября 2024 г. в 00:00:08
https://habr.com/ru/companies/garage8/articles/843724/

Вот уже 24 года мы используем сложный для понимания, завязанный на особенности HTTP REST-протокол. Не пора ли переходить к чему-то более современному?

Привет! Меня зовут Игорь Алексеев и я работаю бэкенд-разработчиком в компании Garage Eight. Некоторое время назад я внедрил gRPC для части своих сервисов. В этой статье поговорим о том, что такое gRPC, сравним его с возможными альтернативами, рассмотрим преимущества gRPC и пробежимся по проблемам, которые возникают почти в каждом проекте, где этот протокол приходится внедрять.

Уточню, я говорю только про gRPC в связке с Protobuf, поскольку GSON в отсутствие типизации, кодогенерации и других фич нивелирует большинство преимуществ.

Что такое gRPC? 

gRPC — это протокол вызова удаленных процедур, когда мы обращаемся к сетевому сервису как будто к локальному методу. 

Для передачи данных gRPC используется бинарный формат, что приносит как бонусы, так и ограничения. 

На транспортном уровне gRPC использует HTTP2 over TCP. Важным моментом является автоматическая, удобная генерация стабов для любых языков из описания контракта в protobuf файле, в кратком и удобном формате. 

Далее сравним gRPC с другими популярными протоколами – jsonRPC2 и GraphQL.

Сравнение gRPC с jsonRPC2 и GraphQL

jsonRPC2 — ещё один популярный выбор для RPC. Он легок, понятен, имеет маленькое описание и удобен в отладке, но jsonRPC2 расходует относительно много трафика, а это накладные расходы на Unmarshalling. 

GraphQL предназначен для данных в произвольной форме. Он текстовый, но объемный, и парсинг запроса может быть очень тяжелый. Так же, как jsonRPC2, GraphQL может работать по HTTP/1 и легко вписывается в любую инфраструктуру.

И наконец наш gRPC — это предопределенный формат запроса, сложности с интеграцией и бинарный формат, что ограничивает нас в дебаге и тестировании привычными средствами. 

Какие преимущества у gRPC и зачем его использовать? 

Первый плюс — это легкое распространение контрактов Protobuf. Контракты можно распространять любыми способами, о нескольких из этих способов я расскажу чуть дальше. Удобный формат шаблона. Если вас напрягает многословность OpenAPI и Swagger, то этот формат сам по себе может стать аргументом в пользу gRPC. 

Второй плюс — gRPC передает данные в бинарной форме, выигрыш по объему передаваемых данных 50-80% экономии трафика, что может быть существенно важно для средств с высокой нагрузкой.

Третий плюс — благодаря соединению по HTTP/2 и благодаря мультиплексированию и переиспользованию единственного сетевого соединения этот протокол позволяет сократить сетевые задержки в 2-10 раз, что тоже особенно важно для нагруженных систем. Это связано как с экономией трафика, так и с отсутствием накладных расходов на переподключение.

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

Проблемы при использовании gRPC в реальной инфраструктуре

Проблема 1. Распространение protobuf модели

Исторически, как только вы начинаете связывать два или более сервисов через этот протокол, происходит распространение protobuf модели. 

Варианты решений

Решение 1. Тут можно пойти традиционным путём и взять репозиторий GitHub модуля веб-артефакта, опубликовать его на Artifactory, Nexus, S3 и потом по мере необходимости стягивать. Это неплохой вариант, потому что он задействует существующую структуру.

Решение 2. Второй вариант для более продвинутой инфраструктуры — monorepo, когда все наши связанные сервисы находятся в одном Git репозитории. В отдельную директорию можно положить все контракты, упорядочить их, сложить рядом с документацией. 

Так делают в Google, Microsoft и во многих других крупных компаниях. Такое решение может быть удобным, а может принести огромные расходы и потребовать специализированного отдела для поддержки репозитория.

Решение 3. Перекладывать руками. Если вы в начале пути, связанных сервисов немного, а обновления происходит редко, то лучшее решение — скопировать руками.

Решение 4. Также есть специальные утилиты и даже выделенные репозитории. Например, существует сервис buf.build с разнообразными возможностями по тестированию, версионированию и даже автодокументированию изменений в описаниях ваших API. 

Если кто-то пользовался — отпишитесь в комментариях. Очень интересно узнать, как работает их модель тарификации по $0.5/месяц за один тип в сообщении.

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

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

Проблема 2. Интеграция через Ingress-nginx

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

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.

Проблема 3. gRPC-web: Интеграция бека и фронта

Как только мы настроили Nginx, может возникнуть мысль: почему бы не использовать gRPS-web и не связать фронт через тот же самый протокол?

И это третья проблема — интеграция бека и фронта. Идея не плохая, она используется, но по сути оказалось, что gRPC-web развит недостаточно. 

Я пошел сложным путем и взял gRPC-web для коммуникации с Web клиентом. Неожиданно оказалось, что добиться генерации пригодного для Web TypeScript от компилятора protoc не так уж и легко. Фактически возможность оказалась экспериментальной и найти рабочий вариант генерации вебстабов оказалось невозможно. Потребовалось несколько часов для написания мастер строки параметров. Эту строку вы можете найти в репозитории с примерами на моем гитхабе. Ссылка будет в конце статьи.

Проблема 4. Ручная верификация СSRF

Четвёртая проблема так же связана с использованием gRPC-Web. 

CSRF — это метод защиты пользователя в вебе от злоумышленников, когда они делают копию сайта и перехватывают данные, проксируя запросы к подлиннику. 

CSRF позволяет пользователю браузера быть уверенным, что на запрос через Интернет отвечает авторизованный сервер с того же URI.

Проблема заключается в том, что gRPC не позволяет использовать CSRF из коробки. Поддержка в принципе не реализована. Таким образом для использования в открытом вебе потребуется как минимум реализовать ответ OPTIONS Acces-Control-Allow-*, а как максимум — расширение с подписанием запросов токеном. 

Так что если вы ленивы, как я, оставьте gRPC для коммуникаций между фрагментами бека.

Советы по реализации gRPC

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

Совет 1. Метод Ping или healthcheck 

Проверка соединения становится абсолютно обязательной. Простой пинг или healthcheck на этапе старта даст отличную подсказку, если что-то идет не так.

Совет 2. Переключение между режимами работы сети 

Второй совет очевиден, но с поправкой на почти обязательность TLS. В каждом сервисе стоит реализовать переключение между режимами работы сети. TLS, сертификат, верификация, порт или хост позволят переключаться между локальными и удаленными сервисами, что не раз потребуется при разработке. 

Совет 3. Тестовый клиент

Поскольку протокол у нас бинарный, традиционными утилитами вроде CURL потестировать ничего не получится. 

Postman, Insomnia или родной grpc_cli имеют на самом деле неудобную и ограниченную поддержку. Например, в Postman все еще нельзя загрузить proto файл и получить предзаполненные поля. Так что специальный, написанный под конкретный случай тестовый клиент — абсолютный маст-хэв.

Заключение

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

Однако теперь у нас есть участок с gRPС, и он нам нравится. Мы получили значимую выгоду, которая перевешивает все минусы, и сумели преодолеть проблемы с интеграцией.

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

Пишите в телеграм, скачивайте репозиторий с примерами, оттуда можно свободно взять все перечисленные мной решения и использовать в своем проекте! 

Полезные ссылки: