Привет! Меня зовут Игорь Закутинский, я Head of Engineering GetProspect — украинского инструмента лидогенерации, позволяющего находить тысячи имейлов в LinkedIn за несколько кликов.
В этой статье я хочу поделиться собственным опытом миграции одного из сервисов проекта с HTTP API на GRPC, что позволило компании оптимизировать ресурсы и значительно увеличить ее производительность.
Главная задача нашего сервиса — поиск электронного адреса определенного профиля в LinkedIn. Это происходит благодаря алгоритму, анализирующему данные, которые указывает пользователь в соцсети. После того, как все имейлы найдены, благодаря GetProspect можно легко управлять своими контактами в нашей CRM-системе. Для того, чтобы база электронных почт была максимально полной, пользователь дополнительно может загружать в нее данные из следующих источников:
Поиск имейл-адреса, энричмент данных, экспорты и импорты — ресурсозатратные задачи. Поэтому эти процессы выполняются через очереди с определенными лимитами для равномерного распределения ресурсов сервиса между всеми пользователями.
Нагрузка динамическая и труднопрогнозируемая, поэтому основные требования к архитектуре сервиса — доступность и масштабируемость.
GetProspect реализован по классической микросервисной архитектуре, где каждый микросервис отвечает следующим критериям:
Большинство сервисов должны взаимодействовать как с внутренними, так и внешними ресурсами. Поэтому одна из главных составляющих, влияющей на производительность системы — микросервисная коммуникация.
Наши микросервисы используют HTTP RESTfull API в качестве базового протокола обмена данными.
REST (Representational State Transfer) — это архитектурный стиль взаимодействия распределенных компонентов в сети.
Основные преимущества REST API и почему мы его используем:
HTTP REST API удовлетворяет основные потребности обмена данными между микросервисами, но из-за конструктивных особенностей не может использоваться во всех случаях.
Одна из основных задач нашего инструмента лидогенерации — поиск имейла пользователя. Поиск электронной почты основан на SMTP-диалоге с email-сервером и может занять от нескольких секунд до 10 минут.
SMTP (Simple Mail Transfer Pro tocol) — это коммуникационный протокол для передачи электронной почты.
Процесс поиска и валидации email-адресов реализован на отдельном микросервисе — Mail verification service, а логика сохранения и управления данными — на API service.
На первом этапе реализация выглядела следующим образом:
Вышеприведенная схема достаточно упрощена, в ней не показаны многие элементы, такие как очереди, балансировка, кэш и т.д. Но его вполне достаточно для общего понимания процесса.
Как видно из схемы, API service инициирует поиск имейл-адреса на Mail verification service с помощью HTTP-запроса, и через некоторое время, когда будет завершен процесс поиска, Mail verification service возвращает результат также через HTTP-запрос на API Service.
То есть для того, чтобы найти email-адрес, выполнялось как минимум два HTTP-запроса (на самом деле их было немного больше 😉), что порождало несколько проблем:
В качестве оптимизации мы рассмотрели использование GRPC для коммуникации этих двух микросервисов, поскольку в нем архитектурно устранены недостатки, о которых я писал выше.
Что такое GRPC? Почему мы выбрали его для микросервисной коммуникации?
GRPC — это современный протокол RPC, реализуемый поверх HTTP/2.
HTTP/2 — это протокол уровня 7 (уровень приложений), работающий поверх TCP (уровень 4 — транспортный уровень), который работает поверх протокола IP (уровень 3 — сетевой уровень).
У GRPC есть множество преимуществ перед традиционным HTTP/REST/JSON, например:
Сообщения GRPC сериализируются с помощью Protobuf — эффективного двоичного формата сообщений. Protobuf быстро выполняет сериализацию на сервере и клиенте.
С помощью Protobuf–сериализации нам удалось значительно снизить объем полезных данных, а значит увеличить пропускную способность между API-сервисом и сервисом, который процессит поиск email-адресов.
Также значительно упростилась логика поиска. На стороне API service процесс поиска имейла выглядит как вызов асинхронной функции (удаленной процедуры в случае gRPC 😉).
Я решил провести тест, для которого использовался два AWS EC2 c5.2xLarge instance, на которых были запущены Node.js application — клиент и сервер.
В этом тесте клиент делает простой запрос на сервер или вызывает процедуру, а сервер просто возвращает значение без какой-либо сложной логики, поскольку цель — определить производительность протокола. В тесте определяется количество возможных запросов за единицу времени (1 секунду, а также время на выполнение 100к запросов или вызовов процедур).
gRPC server, который использовался для теста:
gRPC client, который использовался для теста:
Для теста HTTP Rest API реализована аналогичная логика на Fastify и Axios.
Результаты теста:
HTTP Post request | gRPC procedure call | |
100k async requests, sec | 32.342 | 4.370 |
Requests per second | 2147 | 26102 |
CPU usage – ms/req | 491 ms / per request | 211 ms / per request |
Как видно из графика, пропускная способность GRPC более чем в восемь раз выше HTTP API, что значительно повышает производительность на больших объемах запросов. Также следует отметить то, что CPU-время на 1 gRPC-процедуру более чем в два раза меньше, чем на аналогичный HTTP-запрос.
Поскольку общая нагрузка на сервис постоянно меняется, возникает необходимость масштабирования каждого отдельного микросервиса, и, соответственно, балансировка нагрузки между ними. Балансировка GRPC-сервисов несколько сложнее балансировки сервисов с HTTP-сервером.
Существует два основных подхода к GRPC-балансировке:
Преимущество клиентской балансировки — прямое соединение клиент-сервер, обеспечивающее минимальное время обмена сообщениями. Тем не менее при таком подходе есть много минусов, из-за которых мы отказались от такой реализации, а именно:
Эти проблемы значительно усложняются при росте числа клиентов.
В нашем случае балансировка реализована через Haproxy, что значительно упрощает реализацию и полностью удовлетворяет нашим требованиям по производительности.
GRPC поддерживает SSL/TLS encryption, что позволяет шифровать данные, которыми обменивается клиент и сервер. Также GRPC предоставляет простой Auth API, позволяющий предоставить авторизационные данные при создании канала или вызове функции.
Пример авторизации через SSL/TLS-сертификаты:
const cert = fs.readFileSync('path/to/cert'); const ssl = grpc.credentials.createSsl(cert); const stub = new service.Verification('grpc.service.com', ssl);
К сожалению, сегодня не существует полноценной браузерной поддержки GRPC, поэтому его не стоит использовать для служб, вызываемых непосредственно из браузерных приложений.
Запросы HTTP Rest API отправляются в текстовом виде (JSON), что позволяет разработчикам их легко читать и редактировать. gRPC использует двоичный протокол Protobuf, что значительно эффективнее, но не читаемо человеком, что затрудняет и замедляет процесс отладки и разработки.
На сегодня GRPC — перспективное решение для микросервисов, где необходима высокая пропускная способность. Его использование позволило нам оптимизировать bottleneck нашей системы, значительно сократить расходы на трафик и увеличить производительность.
Тем не менее из-за сложности в реализации, а также отсутствия полноценной браузерной поддержки мы не можем рассматривать GRPC как полноценного конкурента HTTP REST API.
Делитесь вашими мыслями, мигрировали ли вы с HTTP API на GRPC, и каковы результаты?
В благословенные офисные времена, когда не было большой войны и коронавируса, люди гораздо больше общались…
Вот две истории из собственного опыта, с тех пор, когда только начинал делать свою карьеру…
«Ты же программист». За свою жизнь я много раз слышал эту фразу. От всех. Кто…
Отличные новости! Если вы пропустили, GitHub Copilot — это уже не отдельный продукт, а набор…
Несколько месяцев назад мы с командой Promodo (агентство инвестировало в продукт более $100 000) запустили…
Пару дней назад прочитал сообщение о том, что хорошие курсы могут стать альтернативой классическому образованию.…