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

bsei735mf2w_kbxazrauf_db5xc.jpeg

В предыдущем посте я рассказал про то, как настроить и использовать 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 \danog\MadelineProto\API('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 \TelegramSwooleClient\Client();
        
        //Создаем callback с обработчиком запросов
        //На каждый запрос будет вызываться наш callback и в него будут передаваться объекты с запросом и ответом.
        $http_server->on('request', function(\Swoole\Http\Request $request,  \Swoole\Http\Response $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 сервер, как и любой демон, предъявляет повышенные требования к стабильности кода и зависимостей. Но при правильной архитектуре перезапускать нестабильные сервисы можно почти незаметно для пользователя.


© Habrahabr.ru