Производительность PHP multi cURL хуже, чем последовательная file_get_contents

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

Я реализовал интерфейс двумя способами:

  1. используя последовательные file_get_contents.
  2. используя multi завиток.

Я сравнивал 2 версии с jmeter. Результат показывает, что многократный завиток намного лучше, чем последовательный file_get_contents, когда в jmeter есть только 1 поток, но намного хуже, когда 100 потоков.

Вопрос в том, что может привести к плохой работе с несколькими завитками?

Мой мультиколонный код выглядит следующим образом:

$curl_handle_arr = array (); $master = curl_multi_init(); foreach ( $call_url_arr as $key => $url ) { $curl_handle = curl_init( $url ); $curl_handle_arr [$key] = $curl_handle; curl_setopt( $curl_handle , CURLOPT_RETURNTRANSFER , true ); curl_setopt( $curl_handle , CURLOPT_POST , true ); curl_setopt( $curl_handle , CURLOPT_POSTFIELDS , http_build_query( $params_arr [$key] ) ); curl_multi_add_handle( $master , $curl_handle ); } $running = null; $mrc = null; do { $mrc = curl_multi_exec( $master , $running ); } while ( $mrc == CURLM_CALL_MULTI_PERFORM ); while ( $running && $mrc == CURLM_OK ) { if (curl_multi_select( $master ) != - 1) { do { $mrc = curl_multi_exec( $master , $running ); } while ( $mrc == CURLM_CALL_MULTI_PERFORM ); } } foreach ( $call_url_arr as $key => $url ) { $curl_handle = $curl_handle_arr [$key]; if (curl_error( $curl_handle ) == '') { $result_str_arr [$key] = curl_multi_getcontent( $curl_handle ); } curl_multi_remove_handle( $master , $curl_handle ); } curl_multi_close( $master ); 

1. Простая оптимизация

  • Вы должны спать около 2500 микросекунд, если curl_multi_select не удалось.
    Фактически, он defintely терпит неудачу иногда для каждого исполнения.
    Без сна ваши ресурсы процессора заняты большим количеством while (true) { } петель .
  • Если вы ничего не сделаете после завершения некоторых (не всех) запросов,
    вы должны позволить максимальному времени ожидания больше.
  • Ваш код написан для старых libcurls. Начиная с версии libcurl 7.2,
    состояние CURLM_CALL_MULTI_PERFORM больше не появляется.

Итак, следующий код

 $running = null; $mrc = null; do { $mrc = curl_multi_exec( $master , $running ); } while ( $mrc == CURLM_CALL_MULTI_PERFORM ); while ( $running && $mrc == CURLM_OK ) { if (curl_multi_select( $master ) != - 1) { do { $mrc = curl_multi_exec( $master , $running ); } while ( $mrc == CURLM_CALL_MULTI_PERFORM ); } } 

должно быть

 curl_multi_exec($master, $running); do { if (curl_multi_select($master, 99) === -1) { usleep(2500); continue; } curl_multi_exec($master, $running); } while ($running); 

Заметка

Значение таймаута curl_multi_select должно быть настроено только в том случае, если вы хотите сделать что-то вроде …

 curl_multi_exec($master, $running); do { if (curl_multi_select($master, $TIMEOUT) === -1) { usleep(2500); continue; } curl_multi_exec($master, $running); while ($info = curl_multi_info_read($master)) { /* Do something with $info */ } } while ($running); 

В противном случае значение должно быть слишком большим.
(Однако PHP_INT_MAX слишком велик, libcurl рассматривает его как недопустимое значение.)

2. Простой эксперимент в одном PHP-процессе

Я проверил использование моей параллельной библиотеки исполнителей cURL: mpyw / co

(Подготовка не подходит, и это должно быть, извините за мой плохой английский xD)

 <?php require 'vendor/autoload.php'; use mpyw\Co\Co; function four_sequencial_requests_for_one_hundread_people() { for ($i = 0; $i < 100; ++$i) { $tasks[] = function () use ($i) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'example.com', CURLOPT_FORBID_REUSE => true, CURLOPT_RETURNTRANSFER => true, ]); for ($j = 0; $j < 4; ++$j) { yield $ch; } }; } $start = microtime(true); yield $tasks; $end = microtime(true); printf("Time of %s: %.2f sec\n", __FUNCTION__, $end - $start); } function requests_for_four_hundreds_people() { for ($i = 0; $i < 400; ++$i) { $tasks[] = function () use ($i) { $ch = curl_init(); curl_setopt_array($ch, [ CURLOPT_URL => 'example.com', CURLOPT_FORBID_REUSE => true, CURLOPT_RETURNTRANSFER => true, ]); yield $ch; }; } $start = microtime(true); yield $tasks; $end = microtime(true); printf("Time of %s: %.2f sec\n", __FUNCTION__, $end - $start); } Co::wait(four_sequencial_requests_for_one_hundread_people(), [ 'concurrency' => 0, // Zero means unlimited ]); Co::wait(requests_for_four_hundreds_people(), [ 'concurrency' => 0, // Zero means unlimited ]); 

Я пробовал пять раз, чтобы получить следующие результаты:

введите описание изображения здесь

Я также попытался в обратном порядке (третий запрос был пнут xD):

введите описание изображения здесь

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

3. Расширенная оптимизация

3-А. Для разных направлений

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

  1. Разделите количество запрашивающих пользователей, используя apcu_add / apcu_fetch / apcu_delete .
  2. Переключайте методы (секвенциальные или параллельные) по текущему значению.

3-Б. Для тех же пунктов назначения

CURLMOPT_PIPELINING поможет вам. Этот параметр объединяет все соединения HTTP / 1.1 для одного и того же адресата в одно TCP-соединение.

 curl_multi_setopt($master, CURLMOPT_PIPELINING, 1);