В предыдущем посте я рассказал про то, как настроить и использовать php телеграм клиент madelineProto для парсинга постов. Но при использовании библиотеки я столкнулся с несколькими недостатками:
Долгая обработка запросов из-за авторизации телеграм клиента;
Неудобная настройка;
Проблемы с отдачей изображений из постов.
Поэтому решил создать два микросервиса на php для парсинга телеграм каналов, используя асинхронное расширение swoole. Теперь эти пакеты упрощают и ускоряют работу с telegram api (не путать с bot api) в нескольких моих проектах. Хочется поделится ими и услышать мнение других разработчиков.
Под катом расскажу об архитектуре, использовании разных областей видимости в swoole server и устранении последствий ошибок в сторонних библиотеках и внешних api. Ссылки на репозитории с исходным кодом и на тестовый сервер — в конце поста.
Общая архитектура
Изначально планировался один пакет, который выступал бы в качестве парсера telegram и генератора RSS потоков. Но в процессе разработки код становился все более и более неподдерживаемым. Стало ясно, что нужно строже следовать одному из базовых канонов разработки: метод или библиотека должны решать только одну задачу.
В результате декомпозиции появились два микросервиса: TelegramSwooleClient и TelegramRSS.
Общая схема сервиса по генерации RSS потоков из telegram каналов
TelegramRSS
тот микросервис отвечает за коммуникацию с пользователями и генерацию RSS потоков. Упрощенная схема его работы:
Получаем запрос от клиента;
Определяем, что запросили: главную страницу, favicon, rss, json или media файл;
Если пользователь сделал некорректный запрос, или слишком часто обращается к api — добавляем ip в blacklist;
Если пользователь в blacklist — выдаем ошибку;
Запрашиваем сообщения из телеграм канала или конкретный медиафайл из поста через http запрос к TelegramSwooleClient;
Если запросили media файл: даем команду TelegramSwooleClient скачать файл во временную папку и вернуть путь до этого файла. Отдаем файл и удаляем его;
Если запросили RSS: парсим ответ TelegramSwooleClient и генерируем RSS;
В TelegramRSS использование swoole сервера было не обязательным, но дало небольшие преимущества:
Не нужен кеш для хранения черного списка ip адресов (или других данных). Все необходимое хранится в памяти, в экземпляре класса, доступном во всех запросах.
Проще конфигурация nginx: проксируем запросы на ip микросервиса и не беспокоимся о безопасности файлов: .env или *.session.madeline и любых других.
TelegramSwooleClient
Эта библиотека / микросервис отвечает за коммуникацию с telegram api. По сути — это обертка над madelineProto, задача которой держать телеграм клиент в памяти и при получении http запроса вызывать соответствующий метод madelineProto.
Допустим, нам нужно получить 10 последних постов из канала. Пример кода из документации madelineProto:
Данный код будет выполняться минимум 1-2 секунды при горячем запуске или около 10 секунд при холодном. Большую часть этого времени занимает проверка или генерация временных ключей, соединение с серверами телеграм и авторизация на них.
Однако, если запустить swoole server, инициализировать madelineProto при запуске раз и в обработчике запросов использовать инициализированный экземпляр madelineProto, то мы получим сервис, который будет обрабатывать запросы за 100-200 мс. Пример простого обращения к такому микросервису, дающего аналогичный результат:
//Для примера используется file_get_contents, но через него сложно обрабатывает ошибки.
//В реальных задачах лучше использовать curl
$response = file_get_contents('http://127.0.0.1:9503/api/messages.getHistory/?data[peer]=breakingmash&data[limit]=10&data[offset_id]=0&data[offset_date]=0&data[add_offset]=0&data[max_id]=0&data[min_id]=0&data[hash]=0');
if ($response){
$response = json_decode($response, true);
}
$messages_Messages = $response['response'];
Основные преимущества микросервисного подхода:
Снижение времени обработки запросов с 1-10 секунд до 50-300 мс. за счет авторизации при запуске сервиса, а не при каждом запросе.
Упрощение кода
Снижение размера проектов. Нет необходимости включать madelineProto в зависимости, достаточно просто обращаться к нужному адресу из любого проекта (можно даже настроить прием запросов из внешних источников)
Можно запустить неограниченное число клиентов с разными аккаунтами на разных портах
Swoole: использование
Swoole сервер дает огромные преимущества, но сложно ли его запустить? Совсем нет.
Разберем на примере:
//Ради примера код скомпонован в "спагетти" и немного упрощен.
//В проекте он находится в нескольких разных методах
//Создаем сервер, который будет слушать запросы
$http_server = new swoole_http_server(
'127.0.0.1',
9503,
SWOOLE_BASE
);
//Указываем что обрабатываем все запросы в один поток и используем http сжатие данных
$http_server->set([
'worker_num' => 1,
'http_compression' => true,
]);
//Инициализируем класс, в котором будем хранить черный список ip адресов, время бана и тд...
$ban = new Ban();
//Инициализируем класс через который будем общаться madelineProto
//Инициализация займет 1-10 секунд, но будет произведена только 1 раз при старте сервера
$client = new TelegramSwooleClientClient();
//Создаем callback с обработчиком запросов
//На каждый запрос будет вызываться наш callback и в него будут передаваться объекты с запросом и ответом.
$http_server->on('request', function(SwooleHttpRequest $request, SwooleHttpResponse $response) use($client, $ban)
{
//На каждый запрос создаем новый экземпляр класса Controller.
//переменные $client и $ban - в данном случае глобальные. Они содержат классы, неизменные для всех запросов. Данные внутри этих классов хранятся пока работает сервер.
//Их так же можно использовать, как простой кеш.
new Controller($request, $response, $client, $ban);
});
//Запускаем сервер
//Этот метод будет выполняться все время работы сервера.
$http_server->start();
Это лишь часть кода из библиотеки TelegramSwooleClient, но она дает представление о том, как запустить http swoole сервер, и как использовать разные области видимости.
Swoole: установка
Надеюсь, вы уже хотите начать использовать swoole в своих проектах, поэтому распишу чуть подробнее про установку.
Для установки необходим, как минимум, виртуальный сервер с KVM, для возможности установки расширений php. На ubuntu 18.04, с php 7.3 сделал следующее:
# устанавливаем pecl
apt-get install php-dev
# для поддержки http2 в swoole нужно это расширение
apt install libnghttp2-dev
# иногда в системе нет g++, он нужен для компиляции расширения из исходников
apt-get install g++
# устанавливаем актуальную версию swoole
pecl install swoole
Далее последуют вопросы касательно включения разных модулей. В моем случае я сделал такой выбор:
На macOs вместо apt-get можно использовать brew. Но нужно помнить, что swoole не дружит с дебагерами (Xdebug), поэтому при запуске swoole сервера для локальной разработки надо предварительно отключить это расширение (удалять не обязательно).
Особенности работы микросервисов
В связи с тем, что swoole сервер — это по сути демон, который должен работать непрерывно, нужно предусмотреть автоматическое восстановление работы после падений. Для перезапуска при падении / выходе я использую supervisor.
Логи в supervisor отключены, так как логирование реализовано внутри пакета. Главные параметры — это `autostart=true` (запускает микросервис при запуске системы) и `autorestart=true` (безусловный перезапуск, даже если работа была завершена без ошибок).
В последних версиях madelineProto есть неприятная особенность: при получении запроса после интервала более 10-15 минут без запросов библиотека выдает неустранимую ошибку. После этого требуется ее перезапускать. Все запросы, которые придут во время перезапуска вернут пользователям ошибку.
Для решения этой проблемы я сначала использовал рестарт по крону:
Но условия возникновения фатальной ошибки и интервал точно установить не удалось. Вероятно, в часы пик телеграм агрессивнее закрывает неактивные соединения, и такой сценарий не предусмотрен в библиотеке. Но из-за во время рестарта сервис гарантированно был недоступен, что было не приемлемо.
Сейчас реализован более элегантный костыль: TelegramSwooleSever завершает работу при определенном виде ошибки в madelineProto и происходит перезапуск через supervisor. TelegramRSS ждет, когда сервер восстановит работу и дублирует ему запросы. Таким образом, нет ненужных рестартов. А если при обработке запроса TelegramSwooleSever упал, то запрос все равно будет обработан корректно, хоть и с задержкой в несколько секунд.
Используя swoole версий 2 и 4 в течении последнего года, могу сказать: на умеренных нагрузках он стабилен, не вызывает утечек памяти и вполне подходит для продакшена. Но нужно тщательно тестировать все зависимости и свой код, что бы не было никаких не перехваченных Throwable.
ab тесты вроде
ab -c 100 -n 1000 https://tg.i-c-a.su/
Выдерживает без проблем.
Среднее использование памяти (параметр RSS из утилиты ps): до 30 МБ для TelegramRSS и до 60 МБ для TelegramSwooleClient.
TL;DR
Swoole позволяет использовать микросервисную архитектуру в php проектах
Микросервисы позволяют: ускорить запросы, инкапсулировать логику, упростить код и избавится от дублирования зависимостей.
Желательно настроить supervisor для бесперебойной работы микросервисов
Swoole сервер, как и любой демон, предъявляет повышенные требования к стабильности кода и зависимостей. Но при правильной архитектуре перезапускать нестабильные сервисы можно почти незаметно для пользователя.