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

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

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

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

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

Вопрос в том, что может привести к плохой производительности мульти-керла.

Мой код multi curl как ниже:

$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 );

2

Решение

  • Вы должны спать около 2500 микросекунд, если curl_multi_select не удалось.
    На самом деле, это определенно терпит неудачу иногда для каждого исполнения.
    Без сна, ваши ресурсы процессора занимают много while (true) { } петли.
  • Если вы ничего не делаете после немного (не все) запросы завершены,
    Вы должны увеличить максимальное время ожидания.
  • Ваш код написан для старых libcurls. Начиная с версии 7.2 libcurl,
    штат 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 рассматривает это как недопустимое значение.)

Я протестировал, используя мою параллельную библиотеку cURL executor: mpyw / со

(Подготовка for неправильно, и это должно быть byизвините за мой плохой английский 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
]);

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

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

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

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

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

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

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

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

3-Б. По тем же направлениям

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

curl_multi_setopt($master, CURLMOPT_PIPELINING, 1);
2

Другие решения

Других решений пока нет …