[Хд] logo

Асинхронное выполнение PHP

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

Выбор асинхронного решения всегда нетривиален, особенно в случае выполнения PHP.

PHP-FPM fastcgi_finish_request()

Если в качестве веб-сервера используется Nginx, то к вашим услугам отличный менеджер процессов FastCGI (FPM). fastcgi_finish_request

К примеру, отправка почты в асинхронном режиме будет выглядеть так:

<?
$to = $_POST['email'];

if ( $to )
{
	echo 'Подтверждение отправлено на почту';
	fastcgi_finish_request();
	mail($to, 'Подтверждение', 'Это ваш ящик?');
}
else
{
	echo 'Введены не все данные';
}

# После вызова fastcgi_finish_request() посетитель сразу увидит ответ от сервера

Больше настроек и примеров в материале Асинхронность в PHP и FPM.

Неблокирующий режим потока

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

<?php 

function multiHTTP ($urlArr) { 
$sockets = Array(); // массив сокетов
$urlInfo = Array(); 
$retDone = Array(); 
$retData = Array(); 
$errno   = Array(); 
$errstr  = Array(); 
for ($x=0;$x<count($urlArr);$x++) { 
  $urlInfo[$x] = parse_url($urlArr[$x]); 
  $urlInfo[$x][port] = ($urlInfo[$x][port]) ? $urlInfo[$x][port] : 80; 
  $urlInfo[$x][path] = ($urlInfo[$x][path]) ? $urlInfo[$x][path] : "/"; 
  $sockets[$x] = fsockopen($urlInfo[$x][host], $urlInfo[$x][port], 
                           $errno[$x], $errstr[$x], 30); 
  socket_set_nonblock($sockets[$x]); 
  $query = ($urlInfo[$x][query]) ? "?" . $urlInfo[$x][query] : ""; 
  fputs($sockets[$x],"GET " . $urlInfo[$x][path] . "$query HTTP/1.0\r\nHost: " . 
        $urlInfo[$x][host] . "\r\n\r\n"); 
} 
// чтение данных
$done = false; 
while (!$done) { 
  for ($x=0; $x < count($urlArr);$x++) { 
   if (!feof($sockets[$x])) { 
    if ($retData[$x]) { 
     $retData[$x] .= fgets($sockets[$x],128); 
    } else { 
     $retData[$x] = fgets($sockets[$x],128); 
    } 
   } else { 
    $retDone[$x] = 1; 
   } 
  } 
  $done = (array_sum($retDone) == count($urlArr)); 
} 
return $retData; 
} 
?>

# Функция подключается к массиву URL-ов в асинхронном режиме и возвращает массив результатов

Также можно задействовать неблокирующий режим потока. Для этого используется функция stream_set_blocking . К примеру в неблокирующем режиме вызов fgets() будет сразу возвращаться, не дожидаясь доступности всех данных на потоке.

Небольшой пример использования:

function call_url($url) {
    
    $fp = fopen($url, 'r');
    stream_set_blocking($fp, 0);
    $data = fread($fp, 8192);
    fclose($fp);
     
    return strlen($data);
}

# Вызов url и моментальный возврат ответа, удобно для запуска других скриптов

Exec и cURL

Для запуска фоновых процессов curl можно использовать exec. Простой пример выполнения будет выглядеть так:

<?php
  $ch = curl_init();
 
curl_setopt($ch, CURLOPT_URL, 'http://somesite.com/long-script.php');
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
curl_setopt($ch, CURLOPT_TIMEOUT_MS, 50);
 
curl_exec($ch);
curl_close($ch);
?>

# Запрос выполняется почти моментально (50 мс), а в фоне запускается нужный длинный скрипт

Процессы curl также можно запустить из под командной оболочки:

curl -X POST -H 'Content-Type: application/json' \
  -d '{"batch":[{"userId":"some_user","event":"PHP Fork Event","timestamp":"2016-05-16T14:34:50-08:00","context":{"library":"analytics-php"},"action":"track"}]}' \
  'https://api.somesite.com/import' > /dev/null 2>&1 &

# Запуск и раздвоение скрипта

Расширение pthreads

Для PHP есть многопоточное расширение pthreads. php pthreads

Оно не входит в ядро PHP, так что его нужно установить:

pecl install pthreads

# Расширение доступно в репозитории PECL и совместимо с ZTS

Простейший пример для проверки работоспособности расширения будет выглядеть так:

<?php

class workerThread extends Thread {
public function __construct($i){
  $this->i=$i;
}

public function run(){
  while(true){
   echo "Worker {$this->i} ran" . PHP_EOL;
   sleep(1);
  }
}
}

for($i=0;$i<7;$i++){
$workers[$i]=new workerThread($i);
$workers[$i]->start();
}

?>

# Вывод номеров воркеров в 8 потоков

Использование сервера очередей

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

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

Самое главное

Выбор метода асинхронного выполнения полностью зависит от ваших задач. Но самыми производительными и надежными будут решения с использованием модуля PHP-FPM и сервера очередей.

  read in english
[Хд]

Подписывайтесь на отборные материалы по продвинутой разработке

Google Email

Esc, чтобы подписаться позже