Микросервисы на php и swoole для конвертации телеграм каналов в RSS

в 12:37, , рубрики: madelineproto, php, rss, swoole, telegram, многопоточность, параллельное программирование, парсинг, Системы обмена сообщениями

Микросервисы на php и swoole для конвертации телеграм каналов в RSS - 1

В предыдущем посте я рассказал про то, как настроить и использовать php телеграм клиент madelineProto для парсинга постов. Но при использовании библиотеки я столкнулся с несколькими недостатками:

  • Долгая обработка запросов из-за авторизации телеграм клиента;
  • Неудобная настройка;
  • Проблемы с отдачей изображений из постов.

Поэтому решил создать два микросервиса на php для парсинга телеграм каналов, используя асинхронное расширение swoole. Теперь эти пакеты упрощают и ускоряют работу с telegram api (не путать с bot api) в нескольких моих проектах. Хочется поделится ими и услышать мнение других разработчиков.

Под катом расскажу об архитектуре, использовании разных областей видимости в swoole server и устранении последствий ошибок в сторонних библиотеках и внешних api. Ссылки на репозитории с исходным кодом и на тестовый сервер — в конце поста.

Общая архитектура

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

В результате декомпозиции появились два микросервиса: TelegramSwooleClient и TelegramRSS.

схема
Общая схема сервиса по генерации RSS потоков из telegram каналов

TelegramRSS
тот микросервис отвечает за коммуникацию с пользователями и генерацию RSS потоков. Упрощенная схема его работы:

  1. Получаем запрос от клиента;
  2. Определяем, что запросили: главную страницу, favicon, rss, json или media файл;
  3. Если пользователь сделал некорректный запрос, или слишком часто обращается к api — добавляем ip в blacklist;
  4. Если пользователь в blacklist — выдаем ошибку;
  5. Запрашиваем сообщения из телеграм канала или конкретный медиафайл из поста через http запрос к TelegramSwooleClient;
  6. Если запросили media файл: даем команду TelegramSwooleClient скачать файл во временную папку и вернуть путь до этого файла. Отдаем файл и удаляем его;
  7. Если запросили RSS: парсим ответ TelegramSwooleClient и генерируем RSS;

В TelegramRSS использование swoole сервера было не обязательным, но дало небольшие преимущества:

  • Не нужен кеш для хранения черного списка ip адресов (или других данных). Все необходимое хранится в памяти, в экземпляре класса, доступном во всех запросах.
  • Проще конфигурация nginx: проксируем запросы на ip микросервиса и не беспокоимся о безопасности файлов: .env или *.session.madeline и любых других.

TelegramSwooleClient

Эта библиотека / микросервис отвечает за коммуникацию с telegram api. По сути — это обертка над madelineProto, задача которой держать телеграм клиент в памяти и при получении http запроса вызывать соответствующий метод madelineProto.

Допустим, нам нужно получить 10 последних постов из канала. Пример кода из документации madelineProto:

if (!file_exists('madeline.php')) {
    copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
}
include 'madeline.php';

$MadelineProto = new danogMadelineProtoAPI('session.madeline');
$MadelineProto->start();

$messages_Messages = $MadelineProto->messages->getHistory([
    'peer' =>'breakingmash', 
    'offset_id' => 0, 
    'offset_date' => 0, 
    'add_offset' => 0, 
    'limit' => 10, 
    'max_id' => 0, 
    'min_id' => 0, 
    'hash' => 0, 
]);

Данный код будет выполняться минимум 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

Далее последуют вопросы касательно включения разных модулей. В моем случае я сделал такой выбор:

enable sockets supports? [no] : yes
enable openssl support? [no] : no
enable http2 support? [no] : yes
enable mysqlnd support? [no] : yes
enable postgresql coroutine client support? [no] : no

На macOs вместо apt-get можно использовать brew. Но нужно помнить, что swoole не дружит с дебагерами (Xdebug), поэтому при запуске swoole сервера для локальной разработки надо предварительно отключить это расширение (удалять не обязательно).

Особенности работы микросервисов

В связи с тем, что swoole сервер — это по сути демон, который должен работать непрерывно, нужно предусмотреть автоматическое восстановление работы после падений. Для перезапуска при падении / выходе я использую supervisor.

Содержимое .conf файла для TelegramSwooleClient:

[program:telegram_client]
command=/usr/bin/php /home/admin/web/tg.i-c-a.su/TelegramSwooleClient/server.php
numprocs=1
directory=/home/admin/web/tg.i-c-a.su/TelegramSwooleClient/
autostart=true
autorestart=true
stdout_logfile=none
redirect_stderr=true

Для TelegramRSS конфигурация аналогична.

Логи в supervisor отключены, так как логирование реализовано внутри пакета. Главные параметры — это `autostart=true` (запускает микросервис при запуске системы) и `autorestart=true` (безусловный перезапуск, даже если работа была завершена без ошибок).

В последних версиях madelineProto есть неприятная особенность: при получении запроса после интервала более 10-15 минут без запросов библиотека выдает неустранимую ошибку. После этого требуется ее перезапускать. Все запросы, которые придут во время перезапуска вернут пользователям ошибку.

Для решения этой проблемы я сначала использовал рестарт по крону:

*/15 * * * * supervisorctl restart telegram_client

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

Сейчас реализован более элегантный костыль: 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 проектах
  • Микросервисы позволяют: ускорить запросы, инкапсулировать логику, упростить код и избавится от дублирования зависимостей.
  • Для установки swoole требуется VPS c KVM или выделенный сервер.
  • Желательно настроить supervisor для бесперебойной работы микросервисов
  • Swoole сервер, как и любой демон, предъявляет повышенные требования к стабильности кода и зависимостей. Но при правильной архитектуре перезапускать нестабильные сервисы можно почти незаметно для пользователя.

Исходный код и ссылки

github.com/xtrime-ru/TelegramSwooleClient
github.com/xtrime-ru/TelegramRSS
Сервер для тестов, конвертирующий Telegram в RSS: tg.i-c-a.su
Документация swoole: www.swoole.co.uk
Документация madelineProto: docs.madelineproto.xyz
Документация supervisor: supervisord.org/configuration.html#program-x-section-settings
Подбирал градиент на схеме с архитектурой в: www.draw.io :)

Автор: xtrime

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js