Привет, Хабр! Мы в Badoo активно работаем над производительностью PHP, поскольку у нас достаточно большая система на этом языке и вопрос производительности — это вопрос экономии денег. Более десяти лет назад мы создали для этого PHP-FPM, который сначала представлял собой набор патчей для PHP, а позже вошёл в официальную поставку.

За последние годы PHP сильно продвинулся вперёд: улучшился сборщик мусора, повысился уровень стабильности — сегодня на PHP можно без особых проблем писать демоны и долгоживущие скрипты. Это позволило Spiral Scout пойти дальше: RoadRunner, в отличие от PHP-FPM, не очищает память между запросами, что даёт дополнительный выигрыш в производительности (хотя этот подход и  усложняет процесс разработки). Мы сейчас экспериментируем с этим инструментом, но у нас пока нет результатов, которыми можно было бы поделиться. Чтобы ждать их было веселее, публикуем перевод анонса RoadRunner от Spiral Scout.

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

Enjoy!



В последние десять лет мы создавали приложения и для компаний из списка Fortune 500, и для бизнеса с аудиторией не более 500 пользователей. Всё это время наши инженеры разрабатывали бекенд преимущественно на PHP. Но два года назад кое-что сильно повлияло не только на производительность наших продуктов, но и на их масштабируемость — мы ввели Golang (Go) в наш стек технологий.

Почти сразу мы обнаружили, что Go позволяет нам создавать более крупные приложения с увеличением производительности до 40 раз. С помощью него мы смогли расширять существующие продукты, написанные на PHP, улучшая их благодаря комбинации преимуществ обоих языков.

Мы расскажем, как связка Go и PHP помогает решать реальные задачи разработки и как она превратилась для нас в инструмент, способный избавить от части проблем, связанных с моделью «умирания» PHP.

Ваша повседневная среда PHP-разработки


Прежде чем рассказывать, как с помощью Go можно оживить модель «умирания» PHP, давайте рассмотрим вашу стандартную среду PHP-разработки.

В большинстве случаев вы запускаете приложение с помощью комбинации веб-сервера nginx и сервера PHP-FPM. Первый обслуживает статичные файлы и перенаправляет в PHP-FPM специфические запросы, а сам PHP-FPM исполняет PHP-код. Возможно, вы используете менее популярную связку из Apache и mod_php. Но хотя она работает чуть иначе, принципы те же.

Рассмотрим, как PHP-FPM исполняет код приложения. Когда приходит запрос, PHP-FPM инициализирует дочерний PHP-процесс, а детали запроса передаёт как часть его состояния (_GET, _POST, _SERVER и т. д.).

Состояние не может меняться в ходе исполнения PHP-скрипта, поэтому получить новый набор входных данных можно только одним способом: очистив память процесса и инициализировав его заново.

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

Недостатки и неэффективность обычной PHP-среды


Если вы занимаетесь профессиональной разработкой на PHP, то знаете, с чего нужно начинать новый проект, — с выбора фреймворка. Он представляет собой библиотеки для внедрения зависимостей, ORM’ы, переводы и шаблоны. И, конечно же, все входные пользовательские данные можно удобно поместить в один объект (Symfony/HttpFoundation или PSR-7). Фреймворки — это клёво!

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

PHP-инженеры годами искали способы решения этой проблемы, использовали продуманные методики «ленивой» загрузки, микрофреймворки, оптимизированные библиотеки, кеш и т. д. Но в конечном итоге всё равно приходится сбрасывать всё приложение и начинать сначала, опять и опять. (Примечание переводчика: частично эта проблема будет решена с появлением preload в PHP 7.4)

Может ли PHP с помощью Go пережить больше одного запроса?


Можно написать PHP-скрипты, которые проживут дольше нескольких минут (вплоть до часов или дней): например, cron-задачи, CSV-парсеры, разборщики очередей. Все они работают по одному сценарию: извлекают задание, выполняют его, ждут следующее. Код постоянно находится в памяти, экономя драгоценные миллисекунды, поскольку для загрузки фреймворка и приложения требуется выполнять множество дополнительных действий.

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

Ситуация улучшилась с выходом PHP 7: появился надёжный сборщик мусора, стало легче обрабатывать ошибки, а расширения ядра теперь защищены от утечек. Правда, инженерам всё ещё нужно осторожно обращаться с памятью и помнить о проблемах состояния в коде (а существует ли язык, в котором можно не уделять внимание этим вещам?). И всё же в PHP 7 нас подстерегает меньше неожиданностей.

Можно ли взять модель работы с долгоживущими PHP-скриптами, адаптировать её под более тривиальные задачи вроде обработки HTTP-запросов и тем самым избавиться от необходимости загружать всё с нуля при каждом запросе?

Для решения этой задачи сначала нужно было реализовать серверное приложение, способное принимать HTTP-запросы и перенаправлять их один за другим PHP-воркеру, не убивая его каждый раз.

Мы знали, что сможем написать веб-сервер на чистом PHP (PHP-PM) или с использованием С-расширения (Swoole). И хотя у каждого способа есть свои достоинства, оба варианта нас не устраивали — хотелось чего-то большего. Нужен был не просто веб-сервер — мы рассчитывали получить решение, способное избавить нас от проблем, связанных с «тяжёлым стартом» в PHP, которое при этом можно легко адаптировать и расширять под конкретные приложения. То есть нам нужен был сервер приложений.

Может ли Go помочь в этом? Мы знали, что может, потому что этот язык компилирует приложения в одиночные бинарные файлы; он кроссплатформенный; использует собственную, очень элегантную, модель параллельной обработки (concurrency) и библиотеку для работы с HTTP; и, наконец, нам будут доступны тысячи open-source-библиотек и интеграций.

Трудности объединения двух языков программирования


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

Например, с помощью прекрасной библиотеки Алекса Палаэстраса можно было реализовать совместное использование памяти процессами PHP и Go (аналогично mod_php в Apache). Но эта библиотека обладает особенностями, ограничивающими её применение для решения нашей задачи.

Мы решили использовать другой, более распространённый, подход: построить взаимодействие между процессами через сокеты/конвейеры. Этот подход за последние десятилетия доказал свою надёжность и был хорошо оптимизирован на уровне операционной системы.

Для начала мы создали несложный бинарный протокол для обмена данными между процессами и обработки ошибок передачи. В своей простейшей форме протокол этого типа похож на netstring с заголовком пакета фиксированного размера (в нашем случае 17 байт), который содержит информацию о типе пакета, его размер и двоичную маску для проверки целостности данных.

На стороне PHP мы использовали функцию pack, а на стороне Go — библиотеку encoding/binary.

Одного протокола нам показалось мало — и мы добавили возможность вызывать Go-сервисы net/rpc прямо из PHP. Позднее нам это очень помогло в разработке, поскольку мы могли легко интегрировать Go-библиотеки в PHP-приложения. Результат этой работы можно увидеть, например, в другом нашем open-source-продукте Goridge.

Распределение задач по нескольким PHP-воркерам


После реализации механизма взаимодействия мы стали думать, как эффективнее всего передавать задачи PHP-процессам. Когда приходит задача, сервер приложений должен выбрать для её выполнения свободный воркер. Если воркер/процесс завершил работу с ошибкой или «умер», мы избавляемся от него и создаём новый взамен. А если воркер/процесс отработал успешно, мы возвращаем его в пул воркеров, доступных для выполнения задач.



Для хранения пула активных воркеров мы использовали буферизированный канал, для удаления из пула неожиданно «умерших» воркеров добавили механизм отслеживания ошибок и состояний воркеров.

В результате мы получили рабочий PHP-сервер, способный обрабатывать любые запросы, представленные в бинарном виде.

Чтобы наше приложение начало работать как веб-сервер, пришлось выбрать надёжный PHP-стандарт для представления любых входящих HTTP-запросов. В нашем случае мы просто преобразуем net/http-запрос из Go в формат PSR-7, чтобы он был совместим с большинством доступных сегодня PHP-фреймворков.

Поскольку PSR-7 считается неизменяемым (кто-то скажет, что технически это не так), разработчикам приходится писать приложения, которые в принципе не обращаются с запросом как с глобальной сущностью. Это прекрасно сочетается с концепцией долгоживущих PHP-процессов. Наша финальная реализация, которая ещё не получила названия, выглядела так:



Представляем RoadRunner — высокопроизводительный сервер PHP-приложений


Нашей первой тестовой задачей стал API-бекенд, на котором периодически непредсказуемо возникали всплески запросов (гораздо чаще обычного). Хотя в большинстве случаев возможностей nginx было достаточно, мы регулярно сталкивались с ошибкой 502, потому что не могли достаточно быстро балансировать систему под ожидаемое увеличение нагрузки.

Для замены этого решения в начале 2018 года мы развернули наш первый PHP/Go-сервер приложений. И сразу получили невероятный эффект! Мы не только полностью избавились от ошибки 502, но ещё и смогли на две трети уменьшить количество серверов, сэкономив кучу денег и таблеток от головной боли для инженеров и менеджеров продуктов.

К середине года мы усовершенствовали наше решение, опубликовали его на GitHub под лицензией MIT и назвали RoadRunner, подчеркнув тем самым его невероятную скорость и эффективность.

Как RoadRunner может улучшить ваш стек разработки


Применение RoadRunner позволило нам использовать Middleware net/http на стороне Go, чтобы проводить JWT-верификацию ещё до того, как запрос попадает в PHP, а также чтобы обрабатывать WebSockets и глобально агрегировать состояния в Prometheus.

Благодаря встроенному RPC можно открывать API любых Go-библиотек для PHP без написания экстеншенов-обёрток. Что ещё важнее, с помощью RoadRunner можно развёртывать новые серверы, отличающиеся от HTTP. В качестве примеров можно привести запуск в PHP обработчиков AWS Lambda, создание надёжных разборщиков очередей и даже добавление gRPC в наши приложения.

С помощью сообществ PHP и Go мы повысили стабильность решения, в некоторых тестах увеличили производительность приложений до 40 раз, усовершенствовали инструменты отладки, реализовали интеграцию с фреймворком Symfony и добавили поддержку HTTPS, HTTP/2, плагинов и PSR-17.

Заключение


Некоторые всё ещё находятся в плену устаревшего представления о PHP как о медленном громоздком языке, пригодном только для написания плагинов под WordPress. Эти люди даже могут сказать, что у PHP есть такое ограничение: когда приложение становится достаточно большим, приходится выбирать более «зрелый» язык и переписывать накопившуюся за много лет базу кода.

На всё это хочется ответить: подумайте ещё раз. Мы считаем, что только вы сами задаёте какие-то ограничения для PHP. Вы можете потратить всю жизнь на переходы с одного языка на другой, пытаясь найти идеальное сочетание с вашими потребностями, или можете начать воспринимать языки как инструменты. Мнимые недостатки языка вроде PHP на самом деле могут быть причинами его успеха. А если объединить его с другим языком вроде Go, то вы создадите гораздо более мощные продукты, чем если бы вы ограничились использованием какого-то одного языка.

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

UPD: приветствуем создателя RoadRunner и соавтора оригинальной статьи — Lachezis

Комментарии (52)


  1. singletoon
    25.12.2018 16:56
    +2

    Не плохо, не плохо)


  1. ZurgInq
    25.12.2018 17:21
    +1

    Не совсем понятно какие ограничения накладывает использование RoadRunner. На вскидку, не должны использоваться глобальные переменные и объекты, включая разные singleton экземпляры. Как ведут себя в такой среде коннекты до БД, tcp?


    1. Akuma
      25.12.2018 17:40
      +2

      Не совсем в тему, у меня есть несколько демонов на чистом PHP, в которых коннект к MySQL, Redis и Memcached. Даже таймауты отдельно не настраивал, все может работать в таком режиме неделями (иногда перезапускаю для обновления).
      На всякий случай сделал обработку поведения по Mysql gone away: просто переподключаемся и работаем дальше. На тестах работает, в живую вроде бы не пригождалось.

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


    1. DoctorX
      25.12.2018 18:13
      +3

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


      1. Rukis
        26.12.2018 11:13

        Поясните, пожалуйста, с одной стороны вы вроде говорите, что состояние получается изолированным для каждого отдельного запроса и иного подхода потребуют, вроде как, только вполне конкретные места, типа взаимодействие с БД, но с другой стороны говорите, что на мышление php программиста всё это плохо ложиться.

        Так всё же, в целом, насколько велики отличия классического php подхода к разработке и тем, что предлагает RoadRunner? Если представить некое абстрактное, типичное PHP приложение-сайт, то при переносе его на эту технологию, в каких узлах ожидать проблемы, что вероятно потребует изменений? Хочется понять уровень этих проблем и изменений, это уровень глобальный, архитектурный или всё таки локальный, вроде того что вы описали насчет работы с БД.


        1. DoctorX
          26.12.2018 11:47

          с одной стороны вы вроде говорите, что состояние получается изолированным для каждого отдельного запроса и иного подхода потребуют, вроде как, только вполне конкретные места, типа взаимодействие с БД

          Где? Вроде не говорил такого.

          Типичное PHP приложение-сайт будет легче переписать с нуля чем натянуть на эту технологию. Проблем стоит ожидать везде где речь идёт о глобальном состоянии (синглтоны, статические свойства классов и тд) — а современный php код (как ваш так и сторонних библиотек) пишется без оглядки на это состояние.

          Также следует ожидать утечечек памяти, но я так понимаю этот вопрос фиксится просто переодическим перезапуском воркеров.

          Если ваше приложение использует request — response абстракции (например построено на основе symfony) и написано в академично-интерпрайз манере то ваши шансы на переход конечно возрастают, однако с ходу всё равно не заведется — нужны будут правки.


    1. Sho0ter
      25.12.2018 19:28
      +1

      RoadRunner интегрируется посредством добавления цикла с получением PSR-7 запроса, поверх обычного запуска приложения, примерно вот так:

      Код воркера
      <?php
      
      use Spiral\Goridge\StreamRelay;
      use Spiral\RoadRunner\Worker;
      use Spiral\RoadRunner\PSR7Client;
      
      use Symfony\Bridge\PsrHttpMessage\Factory\DiactorosFactory;
      use Symfony\Bridge\PsrHttpMessage\Factory\HttpFoundationFactory;
      
      use Illuminate\Http\Request;
      
      require_once(__DIR__ . '/../vendor/autoload.php');
      
      $relay = new StreamRelay(STDIN, STDOUT);
      $psr7 = new PSR7Client(new Worker($relay));
      
      $requester = new HttpFoundationFactory();
      $responder = new DiactorosFactory();
      
      $app = require_once(__DIR__ . '/../bootstrap/app.php');
      $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);
      
      while ($psr7Request = $psr7->acceptRequest()) {
      
          try {
      
              $request = Request::createFromBase(
                  $requester->createRequest($psr7Request)
              );
      
              $response = $kernel->handle($request);
              $kernel->terminate($request, $response);
      
              $psr7Response = $responder->createResponse($response);
              $psr7->respond($psr7Response);
      		
          } catch (\Throwable $e) {
              $psr7->getWorker()->error((string) $e);
          }
      }


      1. DoctorX
        25.12.2018 22:42

        >В теории, если внести создание $app и $kernel внутрь цикла, то сборщик мусора при каждом новом запросе будет чистить абсолютно все.
        Может тогда уж просто вернёмся к умиранию?)


        1. Sho0ter
          26.12.2018 01:25

          Я думаю, что умирать он будет и так… еще с десяток лет, а может и не один :)
          А по существу, то даже так прирост производительности будет, ибо нет создания процесса, подключения файлов и компиляции кода/загрузка из опкэша)


          1. Sannis
            26.12.2018 13:40

            При использовании php-fpm уже давным-давно как нету создания процесса.


          1. t_kanstantsin
            26.12.2018 22:52
            +1

            Дополню соседний ответ:
            в 7.4 планируется подгрузка библиотек при первом запросе и подключение во всех последующих, что убирает ещё 1 пункт из вашего сообщения.


        1. Evir
          26.12.2018 05:33

          Имеется в виду, что если не использовать статичные и глобальные переменные – не будет сохраняться никакое «лишнее» состояние между запросами. Но зато всё равно будут оставаться в памяти уже загруженные классы, что всё равно даст прирост производительности.


    1. Lachezis
      26.12.2018 12:00
      +2

      Все коннекты гарантированно упадут в какой-то момент если их не закрывать, больше нюансов расписано вот тут: https://github.com/spiral/roadrunner/wiki/Production-Usage


  1. varanio
    25.12.2018 21:11
    -4

    Все же непонятно, зачем было так изголяться. Взяли бы java, kotlin или nodejs (с typescript), и не пришлось бы женить ежа с ужом


    1. VanquisherWinbringer
      25.12.2018 21:20

      Ну или C#. Старая история про забивание гвоздей не тем инструментом.


      1. JekaMas
        26.12.2018 00:34

        Угу, уволить давно подобранные команды и начать с нуля или начать переучивать хороших спецов php на совсем другой стек технологий.
        Цена? Смысл?


        1. Kirill_Dan
          26.12.2018 10:04

          А вы думаете, что изобретение своего велосипеда будет дешевле? В долгосрочной перспективе — это вообще может привести к коллапсу. Да и очень странно, что хороший специалист с опытом знает только один ЯП.


          1. JekaMas
            26.12.2018 10:46
            +2

            Мне очень странно от инженеров слышать что-то вроде "возьмем язых Х и это решит все наши задачи". Не решит. Решит какую-то часть лучше, какую-то хуже. Мы инженер, а не пустомели все же, и должны идти от задачи, а не нашей хотелки. Иначе мы просто играем в клевую разработку, но не создаем решение.


            Про велосипеды, можно примеры, лучше с коллапсом?


            Но заметьте, в статье ровно об этом речь: для части задач php плохо подходил, взяли go, решили задачу лучше.
            Я знаю примеры эволюции компании от php к go+php, затем к java, и сейчас к java+go. Но это не значит, что именно связка java+go работает идеально или что ее нужно было брать сразу. Каждый набор инструментов решал свои задачи в свое время, и имел определенную цену, которая или оправдана или нет.
            В поиске этих компромиссов, оценке разной стоимости и затем обосновании решения и состоит наше с вами дело. Но, согласитесь, странно заявлять "c#! kotlin! Golang! Java!"


            1. Kirill_Dan
              26.12.2018 11:17
              -1

              Очень странно — это взять не рунтайм язык и заниматься годами построением костылей, чтобы он стал рунтайм. Плюс взять ЯП, который не умеет создавать треды и обвешивать его другими сервисами и технологиями, чтобы сделать хоть какое-то подобие многопоточности. Да, у меня есть примеры крупных проектов и коллапсов. Если что, то отписал на пыхе, почти 15 лет.

              Работал на огромном портале недвижимости, весь написан на пыхе. Очень много работы с географией, кучей парсеров на миллионы записей и прочих прелестей. Сам портал увешан в итоге стал и редисом, и мемкешем, и риаком для хранения изображений, и кучей каких-то демонов, которые проверяют процессы распарсивания данных, пару разных очередей zeroMq и кролик. Плюс еще стоял эластик для поиска и еще куча всего. Времени исполнения некоторых скриптов не хватало, стали запускать в терминале с бесконечным таймингом. Потом стали писать под это свои веб обертки. В итоге пришли к тому, что в один поток работать — это лютый треш. Стали придумывать костыли. Прикрутили какую-то стороннюю сырую либу для пыхи, которая делала вид, что работает с тредами. Потом занимались херней, распараллеливая запросы прямо с веб морды, через сокеты (не нужно ждать ответа от сокета, что позволяет выполнять код в цикле, открывая новые сокеты с задачами). После всего этого зоопарка пришло понимание, что все это обслуживать, а тем более ловить баги — это очень суровая задача. Стали бить на микросервисы. Уперлись в монолитную БД, которую пришлось дробить и организовывать свой SOAP сервис, чтобы весь это зоопарк связать вместе (плюс еще 1С была подключена и своя CRM). Естественно, никто не писал никакой документации, комментариев в коде, так как всем все нужно на вчера. В итоге, через год команда на столько зае… алась со всем этим, что три четверти уволились в течении одной недели. Набрали новых, реально опытных людей. Они туда глянули, поколупались пару недель и тоже свалили. Так как возиться с этой кучей навоза на хрен никому не нужно ни за какие деньги.

              Отсюда мораль. Не стоит делать на неподходящей технологии сложные вещи только по тому, что ты хорошо знаешь эту технологию, но не знаешь другую, которая больше всего подходит для конкретной задачи. И вместо того, чтобы реализовывать бизнес логику и развивать БИЗНЕС, все дружно пытаются на… бать ЯП, чтобы он делал то, для чего не предназначен.

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


              1. JekaMas
                26.12.2018 11:28
                +2

                Секунду, как вы смогли-то в «монолитную» бд упереться-то?
                И да, мне не показалось, что вы предлагаете померяться? Можем, но тогда избавьте окружающих от этого зрелища и переходите в личку. Но если коротко, то проблемы в языке у вас не особо было, максимум, выделили бы эти самые критичные куски и вынесли в отдельные микросервисы, где сможете любой подходящий набор технологий использовать.
                И да, однопоточный php отлично себя чувствует в highload, если всеми любимая архитектура была под это поправлена. Многопоточность-то тут причем? Да и «пришло понимание, что» — это какая-то задача не решалась или просто нового захотелось?

                Про php, есть опыт работы в e-commerce на 6 стран. Была изначально php 5.4, и большие проблемы в рампродажи, когда нагрузка уходила за 20k rps на checkout. Стали рефакторить этот кусок, чтобы его выкусить в микросервис, затем тоже для импорта товаров, и затем для рассчета акций и скидок. Отрефпкторенный php переносили в гошные микросервисы, переключались на них с php, оставляя ту же БД и постепенно перекрывая доступы к отдельным ее табличкам со стороны php.
                В итоге решение заработало, php осталось, весь проект не переписывали, фичи бизнес получал non-stop, поскольку часть ресурсов оставалась на php и кодила дальше. Как подоспела 7я версия, то мигрировали на нее и нагрузка стала переноситься и еще с большим запасом.
                Вопрос, зачем бегать кругами и орать, что весь язык плохой? Тем более сгенерировать html php часто умеет вровень или шустрее java, python, go.
                Не решалась очень определенная задача «checkout не выдерживает в дни распродаж», ее и решили. Зачем все переписывать? Или зачем страдать об отсутствии (условно) многопоточности в php, когда пишешь генерацию html странички по закэшированным данным?


              1. AlexTest
                26.12.2018 13:19
                +3

                Очень странно — это взять не рунтайм язык и заниматься годами построением костылей, чтобы он стал рунтайм.
                Современный PHP — вполне может работать рантайм, я его напрмер использую для telegram бота как systemd сервис для обработки long polling запросов, вот пруф (картинку лучше открыть в отдельной вкладке): image
                Жалко этот сервер в начала декабря перегружал, до этого там несколько месяцев рантайма было.
                Плюс взять ЯП, который не умеет создавать треды и обвешивать его другими сервисами и технологиями, чтобы сделать хоть какое-то подобие многопоточности. Да, у меня есть примеры крупных проектов и коллапсов. Если что, то отписал на пыхе, почти 15 лет.
                Предполагаю, что вы проработали 15 лет и ушли до PHP 5.3 т.к. начиная с этой версии PHP вполне себе умеет потоки.


                1. JekaMas
                  26.12.2018 13:23
                  +3

                  и miltu_curl и еще было третье решение, но забыл…

                  pthread вполне рабочая штука, не требующая особой магии, полностью согласен.


                  1. AlexTest
                    26.12.2018 13:37

                    еще было третье решение, но забыл…
                    Наверно про это github.com/php-pm/php-pm


    1. chelovekkakvse
      25.12.2018 21:23
      +2

      Затем, что проект размера Badoo, большая часть которого написана на пхп, двумя пальцами на жаве не перепишешь. Это слишком дорогой. Кроме того, зачем ломать то, что работает? Ещё и штат новый набирать? А со старыми что делать?


      1. varanio
        26.12.2018 04:44

        В статье речь не про Badoo. А про студию веб дизайна


        1. chelovekkakvse
          26.12.2018 09:05
          +1

          Цитирую Badoo: Подход из статьи нам близок: при решении своих задач мы тоже чаще всего используем связку PHP и Go, получая преимущества от обоих языков и не отказываясь от одного в пользу другого.


        1. Lachezis
          26.12.2018 10:04
          +1

          Кроме дизайна мы ещё занимаемся серьезным энтерпрайзом, просто скрываемся :)


    1. Vi3heim6
      26.12.2018 11:14

      Java или Kotlin? Хорошая шутка, но нет.

      Node.js убог и не безопасен.

      Typescript вообще к чему?

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


      1. varanio
        26.12.2018 13:58
        +1

        Д'Артаньян, перелогиньтесь


  1. AlexPTS
    25.12.2018 22:33

    Можно подробнее разложить, чем отличается от PPM, кроме возможности дописывать обработчики на стороне go? В новых проектах сижу на ppm, присматриваюсь к roadrunner. Но пока рассматриваю чисто как менеджер процессов обе тулзы.


    1. Lachezis
      26.12.2018 12:02
      +3

      Быстрее (мы просто не смогли нагрузить PPM до упора из-за его модели работы), для HTTPS не нужен сервер сверху. В остальном все похоже.


  1. ideological
    26.12.2018 02:23

    К слову, тут на Хабре так часто лошили/лошат PHP и совершенно не обосновано (даже когда вышла 7я версия и по скорости и качеству язык стал ничем не хуже других скриптовых). А про JS обычно молчок. Странности.


    1. Pydeg
      26.12.2018 03:01

      Не ясно что вы имеете в виду. В JS описываемая в статье проблема в принципе отсутствует


    1. zhulan0v
      26.12.2018 08:43

      JS это хайп и мода, хаять его — значит плыть против течения. К этому наше сообщество не склонно. С PHP ситуация аналогичная, но со знаком минус. К сожалению, люди забывают, что говнокод создается в конечном итоге ими же, а не инструментом, и говнокода на том же JS тонны. PHP сейчас хорошо развивается и будет сохранять свою нишу еще долгое время. Вспомните, был хайп ruby/ror, но где это щас все? Потихоньку и эта волна спадет, а PHP впитает в себя лучшее и будет спокойно жить дальше.


      1. Groramar
        26.12.2018 11:15

        значит плыть против течения
        Плыть против кармы ) наличие нормально обсудить не даёт.


  1. tendium
    26.12.2018 10:45

    Интересно, а можно что-то подобное сделать без go-прослойки, с помощью контейнеров в, скажем, Kubernetes? Как мне представляется — запускаем N контейнеров-воркеров, запросы же на них будут перекидываться load-балансером самого Kubernetes. Или я что-то упускаю?


  1. pmurzakov Автор
    26.12.2018 11:12
    +1

    Приветствуем создателя RoadRunner и соавтора оригинальной статьи — Lachezis


  1. abratko
    26.12.2018 11:14

    Мы знали, что сможем написать веб-сервер на чистом PHP (PHP-PM) или с использованием С-расширения (Swoole). И хотя у каждого способа есть свои достоинства, оба варианта нас не устраивали — хотелось чего-то большего.


    Слабоватые аргументы. Чем не устраивали? Вы же наверно что-то смотрели, с чем-то сравнивали. Можно об этом подробнее?
    Например, есть такое решение github.com/amphp/http-server, есть тот же PPM.
    Что-то можете сказать об этом?


    1. Lachezis
      26.12.2018 12:27
      +1

      Завести Swoole в режиме "поставил и забыл", мы не смогли. Пробовали несколько раз в течении 4 последних лет.


      Нагружая PPM мы упирались в сам балансировщик. Насчет AMP не скажу, еще не гоняли его.


      Вообще изначально RR был нужен не для HTTP, а для AWS SWF воркеров. В итоге это не HTTP сервер, а app server (любой фронтенд). Например https://github.com/spiral/php-grpc


  1. ManInBlackHat
    26.12.2018 11:14

    Если вы всеравно используете rpc, то чем не устраивало использование:
    grpc & grpc-gateway?


  1. rusya_mahin_page
    26.12.2018 11:14
    +1

    Интересно будет опробовать
    Но мне лично Go не взошел
    Очень уж специфичным показался
    Но это только на мой взгляд!

    Если их действительно можно будет объединить во что-то общее… Думаю, должна получиться как минимум Ракета_)


  1. KEKSOV
    26.12.2018 14:18
    +3

    Вдруг, кому-то пригодится для быстрого старта.

    Очевидное отличие разработки под RoadRunner от классической схемы сохранил_скриптперезагрузи_страничку состоит в том, что теперь это выглядит сохранил_скриптперезагрузи_демонаперезагрузи_страничку. Руками это делать скучно, но умные люди уже всё придумали. Для автоматизации понадобится modd.

    Мой modd.conf выглядит вот так:

    **/*.php {
        daemon +sigterm: ~/bin/roadrunner/1.2.6/rr serve -d -v -c /home/foo/src/boo/rr.json
    }

    Кстати, вопрос к Lachezis: почему-то у меня не получилось использовать rr http:reset — php код сервера не обновляется. Команда rr http:reset -d -v -c /home/foo/src/boo/rr.json отрабатывает — в логе появляется сообщение new worker pool, но код не перегружается. Или это так и задумано и http:reset нужен для чего-то другого? Возможно, есть еще какой-то более правильный способ обновления кода на сервере, пожалуйста поделитесь.

    и rr.json
    {
      "http": {
        "address": "0.0.0.0:8880", <-- биндинг на ваше усмотрение
        "workers": {
          "command": "/usr/bin/php /home/foo/src/boo/main.php",
          "pool": {
            "numWorkers": 4 <-- число ядер CPU
          }
        }
      }
    }

    Заходим в /home/foo/src/boo и запускаем modd, теперь при изменении в php файлах, находящихся в директории /home/foo/src/boo, rr процесс автоматически перезагрузится и вы сможете увидеть сделанные изменения. Рекомендую использовать tmux, если дело происходит в терминале ssh


    1. Lachezis
      26.12.2018 14:22
      +3

      Еще можно maxJobs использоват для разработки. Должен перезагружаться (есть тесты), http:reset и задуман для безопасной перезагрузки пула. Если стабильно воспроизводится то кидайте нам багу на гитхаб (желательно со способом воспроизвести).


      1. KEKSOV
        26.12.2018 14:55

        #76 Пока писал баг-репорт, выяснил, что код перегружается успешно, но совсем перестает работать error_log. Поскольку я использовал его для отладки, то у меня и сложилось впечатление, что после http:reset код не обновляется, а оказалось, что проблема есть, но она в чём-то другом


      1. KEKSOV
        26.12.2018 16:06
        +1

        В 1.2.8 всё заработало!

        Теперь возник вопрос, а насколько дорого перезапускать весь сервер целиком по сравнению с оперцией http:reset?

        Поясню суть проблемы, если вызвать rr http:reset при выключенном сервере, то в ответ получаем Error: dial tcp 127.0.0.1:6001: connect: connection refused, что в принципе логично. Можно ли сделать так чтобы в этом случае rr всё-таки запускался? Иными словами, http:reset_or_start

        При использовании modd c директивой daemon +sigterm достаточно запустить только modd и он автоматически запустит новый rr и будет за ним следить, при этом rr будет выводить свои сообщения в этом же самом окне. Если же использовать конструкцию в modd.conf вида prep: rr http:reset, то сначала в одном окне нужно запустить rr serve, а в другом окне modd, мелочь, конечно…

        Или посоветуете использовать для разработки daemon +sigterm и «не придумывать себе», а http:reset оставить только для продакшена?


        1. Lachezis
          26.12.2018 16:13
          +2

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


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


  1. kpbsod
    26.12.2018 14:30
    -2

    Всё-таки PHP даже 7.3 ужасен для быстрой разработки в Enterprise среде. По синтаксическим плюшкам он крайне сильно отстаёт от современных языков (тому же Kotlin или C#).
    Хотя разработчики стараются, клепают версию за версией. Но догнать передовые языки, как мне кажется, уже не смогут.


    1. KEKSOV
      26.12.2018 15:01
      +1

      Расскажите об этом ветеранам энрерпрайза, которые С с решёткой на дух не переносят. Будь я боссом, я бы на свой бекэнд такую молодую технологию, как C# не пустил… Вот ещё десяток другой лет пройдет, тогда и посмотрим… Плюшки, извините, никому в продакшене не нужны, 80% жизни программы приходится на её сопровождение, которое может длится десятилетиями. Чем проще всё написано, тем лучше.


      1. zhulan0v
        27.12.2018 15:27

        Много ли энтерпрайза на PHP?)

        P.S. Не троллинга ради, исключительно любопытство


        1. tendium
          27.12.2018 18:58

          Фейсбук — это много или мало? :D
          P.S. Да, знаю, не всё там так просто, собстна даже HHVM из-за этого возник, но тем не менее ;)


  1. jegorenkov
    26.12.2018 15:39
    +2

    Спасибо за замечательный продукт, все завелось с пол пинка!
    Скажите можно ли использовать путь к конфигу отличительный от .rr.json?


    1. Lachezis
      26.12.2018 15:43
      +1

      Да, флаг ‘-c’.