Асинхронные контроллеры в Symfony

Асинхронное программирование в последние годы стало синонимом высокой производительности в веб-приложениях со стороны сервера. Во многом это связано с возрастающей популярностью изначально асинхронных JavaScript и Node.js.

Как и многие другие вещи, асинхронное программирование не является чем-то новым. Вы можете использовать этот стиль программирования во многих средах, начиная с Python и заканчивая .NET.

В браузере отдельные события, такие как клик мыши, помещаются в цикл обработки событий (см. What the heck is the event loop anyway?, Филип Робертс), а затем события обрабатываются асинхронно без определённой очерёдности: нельзя точно узнать, когда событие клика мыши выполнится.

Ключевое место в асинхронности занимает I/O. В браузерах данное понятие включено начиная с Internet Explorer 5.0 и популяризировано Gmail в 2004 году. Придуманный метод, AJAX, позволил браузерам выполнять запросы к серверу после первоначальной загрузки страницы.

На сервере неблокирующий асинхронный I/O позволяет, например, продолжать выполнять другие задачи, вместо того, чтобы ожидать выполнения долгих запросов к базе данных. Существует много механизмов для обработки потоков асинхронного кода, такие как фьючерсы/promises, генераторы и наблюдатели.

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

Асинхронный PHP

PHP традиционно соответствует стилю синхронного программирования. Компьютер выполняет операции в заданном порядке. Это делает его работу прямолинейной и предсказуемой для удобочитаемости кода и предвидения результатов.

Недостатком этого является то, что PHP может потенциально потратить значительную часть времени выполнения, ничего не делая, ожидая ответа с результатами от удалённого сервера или чего-то ещё. Это время можно было бы потратить эффективнее, к примеру, для продолжения других задач и возвращения к результатам БД, когда они будут готовы.

В последние годы наблюдается повышенная активность в области Асинхронного PHP с такими проектами как React, Amphp, Icicle и RxPHP. Также сейчас идёт работа над книгой по Асинхронному PHP. Кроме того, альтернативный PHP HHVM и производный от PHP язык Hack добавили асинхронные методы.

С 2015 наблюдаются положительные сдвиги и в самом PHP. Вышеупомянутые проекты присоединились к PHP-FIG (PHP Framework Interoperatibility Group) для совместной работы над созданием соответствующих реализаций, и была сформирована PHP Async Interop Group.

Как вы видите, PHP способен работать в асинхронной парадигме со многими реализациями цикла обработки событий, запускающимися с нативной реализацией PHP.

Асинхронные контроллеры в Symfony

Сам фреймворк Symfony синхронный, поскольку поток кода статичен, запускается и завершается во фронтальном контроллере в логичном порядке. Вы можете сделать асинхронное программирование путём задержки задач в очереди сообщений, которая будет обрабатываться другим процессом.

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

Типичным примером был бы запрос, отправленный к Системе Управления Контентом, которая должна сделать выборку множества элементов из базы данных для генерации отображаемой пользователю страницы. С Symfony вы можете использовать этот фрагмент подфреймворка и запустить задачи в параллельном режиме с помощью ESI или Hinclude.

В некоторых случаях можно оптимизировать производительность за счёт асинхронных вызовов. Например, рассмотрим этот контроллер Symfony, созданный с помощью Icicle Coroutines:


<?php 

namespace AppBundle\Controller; 

use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; 
use Symfony\Bundle\FrameworkBundle\Controller\Controller; 
use Symfony\Component\HttpFoundation\Request; 
use Icicle\Coroutine\Coroutine; 
use Icicle\Loop; 
use Icicle\Awaitable; 

class DefaultController extends Controller {
     /**
      * @Route("/", name="homepage")
      */
     public function indexAction(Request $request)
     {

        $messages = array();

        $routine1 = new Coroutine($this->sayHello('Baby'));
        $routine2 = new Coroutine($this->sayHello('Ginger'));
        $routine3 = new Coroutine($this->sayHello('Posh'));
        $routine4 = new Coroutine($this->sayHello('Scary'));
        $routine5 = new Coroutine($this->sayHello('Sporty'));

        $messages[] = $routine1->wait();
        $messages[] = $routine2->wait();
        $messages[] = $routine3->wait();
        $messages[] = $routine4->wait();
        $messages[] = $routine5->wait();

        Loop\Run();

        // replace this example code with whatever you need
        return $this->render('default/index.html.twig', [
            'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..'),
            'messages' => $messages
        ]);

    }

    public function sayHello($name){

        $delay = rand(1,5);
        $message = array("Hello! My name is " . $name, $delay);
        $promise = Awaitable\resolve($message);

        yield $promise->delay($delay);

    }
}

Контроллер просто возвращает количество сообщений в шаблон. Однако в данном случае это делается с задержкой 5 секунд. Если бы это было сделано синхронно, то все задержки в целом составили бы максимум 25 секунд. С использованием сопрограмм (coroutines) максимальное время выполнения будет около нескольких секунд.

Послесловие

Для получения асинхронного I/O ваш код должен быть неблокирующим. Многие распространённые утилиты, такие как библиотеки MySQL, останутся блокирующими, что делает асинхронные методы бесполезными. Есть обходные решения, такие как Amphp MySQL, но пройдёт некоторое время, прежде чем экосистема станет достаточно зрелой для поддержки асинхронности по умолчанию.

Смешивание синхронных и асинхронных потоков в приложении Symfony подходит как допустимый вариант использования. Явный ввод асинхронного цикла в случае необходимости, также сохраняет код довольно читаемым для разработчиков, привыкшим к стилю синхронного программирования.

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

Примечание

Это авторский перевод статьи «Going Async in Symfony Controllers» на русский язык.

Комментарии

  1. Сергей пишет:

    Я правильно понимаю, что Coroutine позволяет делить большие задания на части и выполнять их по очереди параллельно с другими заданиями, но при этом не может заставить такие атомарные операции как запросы к базе данных или получение данных по HTTP выполняться одновременно?

  2. Павел пишет:

    В php самом по себе нет честной многопоточности. Без нее параллелизация кода возможна в 2 типах:
    — Вы сбарасываете куски кода, которые должны выполняться параллельно в отдельные процессы, которые независимы от вашего мастер процесса. При этом в большинстве своем вы теряете возможность управления процессами, и у вас появляется возможность положить сервер при сложных запросах ( сам сервер, а не БД).
    — Вы в рамках вашего одного процесса делаете асинхронные вызовы (это реакт). Все управление у вас остается, но если один из кусков асинхронного кода будет тяжелым, то остальные не смогут работать ( процесс, то на самом деле один).

    Вы можете начать делать параллельные запросы к БД с использованием например Amphp MySQL. Но традиционно работа с БД была синхронной. В большинстве задач можно крайне сложно сделать асинхронный запрос к БД (например, что вы будете делать если в вашем параллельном коде регистрации юзера email отправится, а сама запись в БД не добавится, то есть пользователь не создастся на самом деле).

  3. Belkir пишет:

    А еще не стоит забывать про утечки памяти (ведь с php 4 мы уже отвыкли от прямого вызова unset да и тот же PDO течет весьма неплохо сам по себе), работу с файлами (ибо 90% конструкций I/O File запирающие), кривую реализацию потоков под виндой (а точнее, чуть более чем никакую), доп.настройки окружения (apache неплохо так), которые прекрасно дропают многопоточность. Наша любимая пыха уверенно ползет в сторону многопоточности, но увы — ползти ей еще долго.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *