Не Костыль: Клиент Grpc На Php В Производстве

Привет! Я хочу показать, что запуск gRPC в PHP — это обычное боевое решение, которое быстро пишется, легко развертывается и может оказаться для вас проще, чем сокеты.



НЕ костыль: клиент gRPC на PHP в производстве



Поначалу в REST всё работало и работало хорошо, но начался рост.

Skyeng постоянно взаимодействует со студентами: периодически нам нужно звонить — чтобы подтвердить запись на пробный урок или, например, уточнить, все ли в порядке, если человек пропустил урок.

На заре школы работа со звонками велась вручную, но бизнесу быстро захотелось как-то автоматизировать и анализировать работу операторов.

Voximplant помог сделать это.

Мы до сих пор пользуемся их технологией – это удобно.

Чтобы операторы не слушали гудки, не тратили время на набор недоступных номеров и так далее, у ребят есть PDS (система предиктивного набора) - система автоматического набора номера.

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

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



НЕ костыль: клиент gRPC на PHP в производстве

Долгое время все работало примерно по такой схеме.

Например, мы загружаем список PDS из 1000 номеров — и знаем, что у нас теперь 50 операторов.

Начинаем обзванивать первые 100.

  • Номер недоступен — обрабатываем скрипт на JS, отправляем номер и его статус на конечную точку, записываем факт неудачного звонка в базу данных.

  • Срабатывает автоответчик: все то же самое, но со статусом «автоответчик».

  • Совершается звонок клиенту – он берет трубку и говорит «Здравствуйте».

    В это время из пула операторов срочно ищется первый подходящий: мы знаем, что если держать человека «на линии» более двух секунд, он просто повесит трубку.

По мере того, как я рос, начали возникать трудности.

PDS вычисляет контактную информацию из базы данных и набирает еще несколько номеров.

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

Упор всегда делается на удобство клиента (те самые пару секунд), но время простоя оператора не должно затягиваться.

Это нормально, если это 30-40 секунд. На практике так сложилось, что загрузка времени операторов доходила почти до 30% — долго сидели в ожидании, это было критично.

Также мы стали сталкиваться со случаями двойного звонка.

Утром мы собрали большой список звонков на тысячи номеров и загрузили его в систему.

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

В PDS не было опции «добавить номера».

Поэтому мы остановили его, обновили таблицу и запустили снова.

Но на момент остановки часть номеров могла быть набрана, но их статусы еще не поступили в базу данных.

Они не были помечены как набранные и случалось, что мы разговаривали с человеком, а через две минуты ему набирал номер следующий оператор.

Мы не могли отметить, что номер дан для набора на нашей стороне: так как мы работали с тысячами количества одновременно, это вызвало большие задержки на нашей базе.

Тогда ребята из Voximplant предоставили нам прототип своего нового PDS — более совершенного решения под названием PDS2. И нам нужно было как-то к этому подключиться.



Почему стоит выбрать gRPC? И почему не работал клиент Go?

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

Вот типичный файл protobuf — очень похож на JSON, все достаточно просто:
  
  
   

syntax = "proto3" ; message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3 }

  • Есть плагин gRPC, который позволяет сгенерировать из протофайла весь необходимый код — сервис, типы, PDS-клиент, в нашем случае.

    Единственное, что он не генерирует — это серверную часть на PHP.

  • HTTP/2 как транспорт — можно прекратить выполнение запроса на сервере, а также повторно использовать один сокет для нескольких параллельных запросов.

  • Вместо ввода «domain.com/service/collection/resource/query? параметр=значение», как и в REST, здесь есть только сервис.

    Все остальное описывается через Protobuf с точки зрения нашей модели и ее событий.



Однако их выбрали не из-за особенностей: выбора просто не было — PDS2 общался только через gRPC.

Но мы попробовали это с Go. Прототип клиента от разработчиков из Voximplant некоторое время находился в производстве, но его было сложно поддерживать.

PHP — основной язык Skyeng; там почти все написано.

И мы поняли, что нужно перетащить много кода в клиент Go, а затем поддерживать его и в клиенте, и в PHP-части.

Например, у нас были проблемы с часовыми поясами — и решение было, но на PHP. И все это нужно было перенести в тот Go-клиент. Мы долго обсуждали это с тимлидом и в итоге решили, что проще будет всё сделать на PHP. После месяцев использования понимаю, что это было правильное решение.



Что делать, если PHP не был доставлен? Написать решение (почти) легко

Я выполнил рекомендации от gRPC.io для PHP. В принципе, там описано все, что вам нужно.

Был только один забавный нюанс.

Пару дней я искал решение, как генерировать код с пространствами имен в нашем протофайле.

Я все сгенерировал, все хорошо, но их мало.

В итоге я сел перечитывать всю документацию.

Оказалось, это называется пакеты.

Итак, если вы тоже задаетесь вопросом, то все довольно просто: пишем в файл

package foo.bar; message MyMessage {}

И это создаст такое пространство имен.



Foo\Bar\MyMessage

Подробности по связь .

Как это работает. При генерации задаем необходимые параметры для подключения к Voximplant, запускаем и получаем бесконечный цикл, который постоянно слушает наш поток.

Наш клиент, по сути, обычный демон.

Здесь пример от Воксимплант. Нашу связку показать вам не могу: она сильно разрослась из-за сложной и специфической для нас логики.



Каков результат?

Наш клиент совместно с супервайзером работает на продакшене с января, всё стабильно.

В сочетании с супервизором это почти демон - если что случится, супервайзер подхватит и зафиксирует падение себе.



НЕ костыль: клиент gRPC на PHP в производстве

Мы решили растущие проблемы.

Благодаря демону мы отправляем номера по запросу, динамически, небольшими порциями — по 50 за раз.

И теперь, если у нас есть какие-то «горячие» номера с сайта, он уже знает, что эти номера имеют высший приоритет — когда приходит новый запрос от Voximplamt, он их отправляет. Теперь у нас есть гибкость.

Также время ожидания операторов сократилось примерно до 20 секунд, но это исключительно за счет лучших алгоритмов самого PDS2, которые мы не писали.

Теги: #api #разработка сайтов #php #grpc #symfony

Вместе с данным постом часто просматривают: