LINUX.ORG.RU

Распараллеливание PHP

 ,


0

1

Есть websocket сервер на Ratchet. Работает отлично сутками, памяти не жрет, не падает - как часы работает, но есть недостаток. Так как в коде используются блокирующие функции, то очередь React блокируется и пока старое сообщение не будет отработано новые запросы от websocket клиентом не будут обработаны сервером.

Что-бы этого избежать я сначала скомпилил php с поддержкой pthread, но, как оказалось потом запуск трида тоже блокирует очередь.
Такой код блокирует очередь React

class Message extends \Thread{
    public function run(){
        db_init();
        app_init();
        //тут код, отрабатывающий сообщения от WS
        sleep(1000) //для простоты
    }
}

...
public function onMessage(){
   $work = new Message();
   $work->start();
}
...



Я был уверенным, что после $work->start() будет создан новый поток, с новым конектом БД и своим новым окружением. Впрочем так оно и есть, НО... очередь блокируется, пока не завершится поток(да, можно создать хоть 100 параллельных потоков, они будут работать, а очередь будет заблокирована). Почему так не понимаю, поток вроде как для того и создан, чтобы параллельно выполняться, но какого то хера он блокирует родительский скрипт. Нагуглил у многих эта проблема с Ratchet. Ну да ладно, перехожу на fork, proc_open

Сейчас думаю что использовать: fork или proc_open. С форками никогда не работал, хочу поинтересоваться как лучше сделать через fork или proc_open?

Сервер простой

use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use Ratchet\Http\HttpServer;

require "vendor/autoload.php";

class Websocket implements MessageComponentInterface {

    public function onOpen(ConnectionInterface $conn) {

    }

    public function onMessage(ConnectionInterface $conn, $msg) {
        
        $pid = pcntl_fork();
        if ($pid == -1) {
             die('could not fork');
        } else if ($pid) {
            
            pcntl_wait($status); //? что тут? return?
        } else {
             //тут код, обрабатывающий сообщение от WS...
             //обращение к БД, чтение файлов, загрузка по http, все долгие операции
        }
        
        
        //ИЛИ
        proc_open("php /message.php --mess={$msg}")     
        
    }

    public function onClose(ConnectionInterface $conn) {

    }

    public function onError(ConnectionInterface $conn, \Exception $e) {

    }


}    

$myWs = new Websocket();

$loop = React\EventLoop\Factory::create();
$socket = new React\Socket\Server($loop);
$server = new IoServer(new HttpServer(new WsServer($myWs)), $socket, $loop);
$socket->listen(7000, "127.0.0.1");
$loop->run();



Если форком, то что делать после отработки чилда? Если exit сделать, но весь скрипт падает, что делать в паренте? Или лучше proc_open?

Сообщения между процессами думаю делать на ZMQ... или просто через PIPE? Запускаемый процесс должен отработать сообщение и вернуть JSON основному скрипту, который разошлет ответ всем вебсокетам.

Простой пример того, чего хочу
Клиент(K), Сервер - WS
K: Соединение с WS
WS: OK
K: Mess Join(вход в чат)
WS: проверка сессии, пользователя, комнаты и прочие проверки долгие
K: Mess Chat(собственно, сообщение в чат)
WS: снова проверки выше, проверки доступа, подключение ботов, работа с http

★★★★

Ответ на: комментарий от umren

Чувак хочет принципиально на пхп, ибо сам сайт на нём же. Принципиальность, видимо, всё-таки разрешает компилить кастомный пхп и использовать ZMQ.

Deleted ()

Четвёртый сезон несмешного сериала про параллельные вселенные.

ThisNameWasFree ()
Ответ на: комментарий от Deleted

тк экспириенса в индустрии пхп таких штук чуть менее чем ноль, а ракет используют 3 1/2 инвалида в мире, то я бы на его месте задумался о проблемах которые появятся потом

umren ★★★★★ ()
Ответ на: комментарий от Deleted

А компилить его все равно пришлось бы с --enable-fd-setsize=20000, так что суть не в этом. А ZMQ это pecl, его компилить так и так надо.

gobot ★★★★ ()

Сообщения между процессами думаю делать на ZMQ... или просто через PIPE?

Отвечу вопросом на вопрос. Если придётся растащить приложение на пару серверов и организовывать общение между ними, сможешь сделать на PIPE?

как лучше сделать через fork или proc_open?

В немного другой области мы использовали pcntl_fork. Он работает. И когда разберёшься, то даже логично работает. За proc_open ничего не скажу.

очередь блокируется, пока не завершится поток
у многих эта проблема с Ratchet

Мне кажется, или тут есть некая связь? Попробуй убрать Ratchet и повторить проблему.

WARNING ★★★★ ()
Ответ на: комментарий от gobot

Ну и как мне нода поможет с блокируемыми sql-запросами?

Я не правильно понял, или у тебя работа с БД в синхронном режиме?

WARNING ★★★★ ()
Ответ на: комментарий от WARNING

сможешь сделать на PIPE

Понял, значит оставлю ZMQ

Попробуй убрать Ratchet и повторить проблему

Ratchet использует stream_select(), думаю если сделаю напрямую то будет тоже самое. Просто лень проверять

не правильно понял, или у тебя работа с БД в синхронном режиме?

Да

gobot ★★★★ ()
Ответ на: комментарий от gobot

Ratchet использует stream_select()

Посмотри stream_set_blocking(), не оно ли блокирует тебе очередь?

думаю если сделаю напрямую то будет тоже самое.

А ты не делай то же самое. У тебя на самом деле задача сейчас не сделать, а понять кто тупит: php, React, Rachet или ещё что-то. Поэтому надо написать минимальное тестовое приложение с минимумом используемых функций и минимальной функциональностью.

Да

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

WARNING ★★★★ ()
$pid = pcntl_fork();
if ($pid == -1) {
     die('could not fork');
} else if ($pid) {
    echo "parent: $pid\n";
    pcntl_wait($status);
} else {
    $pid = getmypid();
    echo "child: {$pid}\n";
}


parent: 22935
child: 22935



После делаю

pstree -p 27345
bash(27345)───php(22932)─┬─php(22935)
                         ├─{php}(22933)
                         └─{php}(22934)




27345 - это pid шела, в котором я запускал этот скрипт. Почему parent
22935, а не 22932? Что за {php}? Почему парент pid == чилд пид?

gobot ★★★★ ()
Ответ на: комментарий от WARNING

Посмотри stream_set_blocking(), не оно ли блокирует тебе очередь?

Не думаю. У меня помимо вебсокета, в очереди ещё ZMQ порт прослушивает и все нормально, а тут трид просто блокирует. Я запускал на чистом пхп эти триды и после их запуска скрипт не прекращал свою работу, даже если в конце поставить exit. Только crtl-c вырубал

Базу вообще не трогал, просто sleep(100) поставил в треде

gobot ★★★★ ()
Ответ на: комментарий от gobot

Почему parent 22935, а не 22932?
Почему парент pid == чилд пид?

Потому что pcntl_fork(), как я уже говорил, работает и работает логично, только если в нём разобраться. А тут до этого очень далеко.

pcntl_fork(): On success, the PID of the child process is returned in the parent's thread of execution.

А теперь посмотри на свой код и сам ответь на свой вопрос.

Что за {php}?

Понятия не имею. Как там оно называется меня никогда не интересовало, да и вывод pstree я никогда не смотрел.

WARNING ★★★★ ()
Ответ на: комментарий от WARNING

Ага )
Когда в чилде делаешь exit, то процесс остается зомби([php] <defunct>), а их будет много. Нужно родителем убивать дочерние?

gobot ★★★★ ()
Ответ на: комментарий от gobot

defunct-ов быть не должно. Это некрасиво, неправильно и нагружает систему. Ещё надо почитать про declare(ticks=1), pcntl_signal() и прочее.

WARNING ★★★★ ()
Ответ на: комментарий от WARNING

Установил сигналы и что дальше? Дочерние процессы также висят <defunct>, хотя я их завершаю(exit). Что сделать нужно, чтобы они не висели без дела?

<?php
declare(ticks = 1);

function sig_handler($signo)
{

    echo "{$signo}: ";
    switch ($signo) {
        case SIGTERM:
            // handle shutdown tasks
            echo "SIGTERM\n";
            exit;
            break;
        case SIGHUP:
            // handle restart tasks
            echo "SIGHUP\n";
            break;
        case SIGUSR1:
            echo "SIGUSR1\n";
            break;
        default:
            // handle all other signals
    }

}

pcntl_signal(SIGTERM, "sig_handler");
pcntl_signal(SIGHUP,  "sig_handler");
pcntl_signal(SIGUSR1, "sig_handler");

for($i =0; $i < 10; $i++){

    $pid = pcntl_fork();

    if($pid == -1){

    }elseif($pid){
        echo "Fork: {$i}\n";
        posix_kill($pid, SIGTERM);
    }else{
        exit;
    }

}


sleep(100);

gobot ★★★★ ()
Ответ на: комментарий от gobot

Дочерние процессы также висят <defunct>, хотя я их завершаю(exit). Что сделать нужно, чтобы они не висели без дела?

pcntl_wait, pcntl_waitpid

anonymous ()
Ответ на: комментарий от gobot

Про pcntl_waitpid уже написали. У тебя неправильно он используется. Раскуривай, не торопись. Спешка тут ни к чему хорошему не приведёт.

WARNING ★★★★ ()
Ответ на: комментарий от gobot

Теперь вместо 10 <defunct> стало 7

Когда ты вызвал pcntl_waitpid в родительском процессе, дочерний процесс мог ещё не завершиться.

anonymous ()
Ответ на: комментарий от anonymous

<?php
Разобрался, нужно родителем pcntl_waitpid обрабатывать каждый процесс

$childs = [];
for($i =0; $i < 10; $i++){

    $pid = pcntl_fork();

    if($pid == -1){

    }elseif($pid){
        echo "Fork: {$i}\n";
        $childs[] = $pid;
    }else{
        exit;
    }

}




while(count($childs) > 0) {
    foreach($childs as $key => $pid) {
        $res = pcntl_waitpid($pid, $status, WNOHANG);
        if($res == -1 || $res > 0) {
            unset($childs[$key]);
        }
    }
    sleep(1);
}

gobot ★★★★ ()
Ответ на: комментарий от gobot

чем оно лучше\хуже чем fork()

Вообще-то это тот же fork, вид сбоку, т.к. никаких popen и т.п. в ядре нет - это всё надстройки уровня библиотек над fork созданные для удобства.

no-such-file ★★★★★ ()
Ответ на: комментарий от no-such-file

fork копирует всю память процесса, т.е. уже будет инициализированный новый процесс, а в popen нужно заново все фалы считывать, компилировать скрипт

gobot ★★★★ ()

Очередные php проблемы. Мы же уже писали, что php не предназначен для этого всего. Хватит есть кактусы, пользуйся nodejs.

th3m3 ★★★★★ ()
Ответ на: комментарий от th3m3

Хватит есть кактусы, пользуйся nodejs

Ну как там треды уже завезли, или всё ещё костыляете? Каким же нужно быть оленем, чтобы вместо пыха советовать ноду, ладно бы руби какой-нибудь или сразу жабку.

no-such-file ★★★★★ ()
Ответ на: комментарий от gobot

а в popen нужно заново все фалы считывать, компилировать скрипт

А, ты в этом смысле. Ну вообще-то opcache решает, т.е. ничего особо компилировать заново не нужно, как и считывать - всё в памяти прокэшируется.

no-such-file ★★★★★ ()
Ответ на: комментарий от no-such-file

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

ritsufag ★★★★★ ()
Ответ на: комментарий от ritsufag

Потому что человек уперся в возможности похапе

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

переписать

Он не хочет переписать. Если переписать, то и на пыхе всё решается.

no-such-file ★★★★★ ()
Последнее исправление: no-such-file (всего исправлений: 2)

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

Kilte ★★★★★ ()
Ответ на: комментарий от ritsufag

Что, бомбит? Да, на пыхе всё решается, иди поплачь. Потом приходи, объясни, чем нода лучше и как она поможет ТСу.

no-such-file ★★★★★ ()
Ответ на: комментарий от no-such-file

Понятно, т.е. лучше popen? Минусы от форка это, что нужно конекты некоторые возобновлять в родительском процессе, а то чилд их рушит.

gobot ★★★★ ()
Ответ на: комментарий от ritsufag

Можно и на пхп sql запросы переписать асинхроном mysqli, но если я это сделаю, зачем мне нужен будет нод?

gobot ★★★★ ()
Ответ на: комментарий от gobot

Какая очередь, ты о чём вообще?

Я предлагаю тебе взять допустим редис, асинхронный клиент к нему. В onMessage ты пушишь таску с помощью этого клиента, а извлекаешь её уже в отдельном воркере. Под воркером я подразумеваю отдельное специально написанное для этого приложение, где тебе не нужно будет думать о том, что что-то там чего-то блокирует. Ты просто разгребаешь очередь и всё. Дополнительным плюсом (если это важно) будет то, что это ещё позволит допустим выкинуть ratchet и переписать его чём-то другом, жс, питон, на выбор, а воркер как был так и останется. Можно будет и воркер переписать не затрагивая всё остальное, если с PHP всё плохо. Вот, как-то так.

Kilte ★★★★★ ()
Ответ на: комментарий от ritsufag

Забавно читать это от бывшего похапедауна :3

Kilte ★★★★★ ()
Ответ на: комментарий от gobot

Можно и так, не знаю, как тебе угодно. А можно просто для начала попробовать пару тройку воркеров запустить и посмотреть, как оно будет. Если там ничего такого тяжёлого не планируется, мне кажется должно хватить. Вообще лучше конечно подыскать готовое решение. Я уверен, что на пыхе есть куча библиотек для таких задач.

Kilte ★★★★★ ()
Ответ на: комментарий от Kilte

Воркеры\библиотеки это прослойки над proc_open, это не принципиально. React\Ratcher менять не буду, 1) он проверен на деле 2) смысла нет менять шило на мыло

gobot ★★★★ ()

Хоспаде, в похапе и треды через жопу. Зачем же их поддерживают, если они не работают и никто этим не пользуется?

anonymous ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.