Получение данных из API, рост памяти

Я работаю над проектом, где я извлекаю данные (JSON) из API. У меня проблема в том, что память медленно растет, пока я не получу страшную фатальную ошибку:

Неустранимая ошибка: допустимый объем памяти * байтов исчерпан (пробовал
выделить * байтов) в C: … on line *

Я не думаю, что должен быть какой-либо рост памяти. Я пытался сбросить все в конце цикла, но без разницы. Итак, мой вопрос: я делаю что-то не так? Это нормально? Что я могу сделать, чтобы решить эту проблему?

<?php

$start = microtime(true);

$time = microtime(true) - $start;
echo "Start: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br/>";

include ('start.php');
include ('connect.php');

set_time_limit(0);

$api_key = 'API-KEY';
$tier = 'Platinum';
$threads = 10; //number of urls called simultaneously

function multiRequest($urls, $start) {

$time = microtime(true) - $start;
echo "&nbsp;&nbsp;&nbsp;start function: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br>";

$nbrURLS = count($urls); // number of urls in array $urls
$ch = array(); // array of curl handles
$result = array(); // data to be returned

$mh = curl_multi_init(); // create a multi handle

$time = microtime(true) - $start;
echo "&nbsp;&nbsp;&nbsp;Creation multi handle: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br>";

// set URL and other appropriate options
for($i = 0; $i < $nbrURLS; $i++) {
$ch[$i]=curl_init();

curl_setopt($ch[$i], CURLOPT_URL, $urls[$i]);
curl_setopt($ch[$i], CURLOPT_RETURNTRANSFER, 1); // return data as string
curl_setopt($ch[$i], CURLOPT_SSL_VERIFYPEER, 0); // Doesn't verifies certificate

curl_multi_add_handle ($mh, $ch[$i]); // Add a normal cURL handle to a cURL multi handle
}

$time = microtime(true) - $start;
echo "&nbsp;&nbsp;&nbsp;For loop options: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br>";

// execute the handles
do {
$mrc = curl_multi_exec($mh, $active);
curl_multi_select($mh, 0.1); // without this, we will busy-loop here and use 100% CPU
} while ($active);

$time = microtime(true) - $start;
echo "&nbsp;&nbsp;&nbsp;Execution: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br>";

echo '&nbsp;&nbsp;&nbsp;For loop2<br>';

// get content and remove handles
for($i = 0; $i < $nbrURLS; $i++) {

$error = curl_getinfo($ch[$i], CURLINFO_HTTP_CODE); // Last received HTTP code

echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;error: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br>";

//error handling if not 200 ok code
if($error != 200){

if($error == 429 || $error == 500 || $error == 503 || $error == 504){
echo "Again error: $error<br>";
$result['again'][] = $urls[$i];

} else {
echo "Error error: $error<br>";
$result['errors'][] = array("Url" => $urls[$i], "errornbr" => $error);
}

} else {
$result['json'][] = curl_multi_getcontent($ch[$i]);

echo "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Content: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br>";
}

curl_multi_remove_handle($mh, $ch[$i]);
curl_close($ch[$i]);
}

$time = microtime(true) - $start;
echo "&nbsp;&nbsp;&nbsp; after loop2: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br>";

curl_multi_close($mh);

return $result;
}


$gamesId = mysqli_query($connect, "SELECT gameId FROM `games` WHERE `region` = 'EUW1' AND `tier` = '$tier ' LIMIT 20 ");
$urls = array();

while($result = mysqli_fetch_array($gamesId))
{
$urls[] = 'https://euw.api.pvp.net/api/lol/euw/v2.2/match/' . $result['gameId'] . '?includeTimeline=true&api_key=' . $api_key;
}

$time = microtime(true) - $start;
echo "After URL array: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br/>";

$x = 1; //number of loops

while($urls){

$chunk = array_splice($urls, 0, $threads); // take the first chunk ($threads) of all urls

$time = microtime(true) - $start;
echo "<br>After chunk: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br/>";

$result = multiRequest($chunk, $start); // Get json

unset($chunk);

$nbrComplete = count($result['json']); //number of retruned json strings

echo 'For loop: <br/>';

for($y = 0; $y < $nbrComplete; $y++){
// parse the json
$decoded = json_decode($result['json'][$y], true);

$time = microtime(true) - $start;
echo "&nbsp;&nbsp;&nbsp;Decode: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "<br/>";


}

unset($nbrComplete);
unset($decoded);

$time = microtime(true) - $start;
echo $x . ": ". memory_get_peak_usage(true) . " | " . $time . "<br>";

// reuse urls
if(isset($result['again'])){
$urls = array_merge($urls, $result['again']);
unset($result['again']);
}

unset($result);
unset($time);

sleep(15); // limit the request rate

$x++;
}

include ('end.php');

?>

Версия PHP 5.3.9 — 100 циклов:

loop: memory | time (sec)
1: 5505024 | 0.98330211639404
3: 6291456 | 33.190237045288
65: 6553600 | 1032.1401019096
73: 6815744 | 1160.4345710278
75: 7077888 | 1192.6274609566
100: 7077888 | 1595.2397520542

РЕДАКТИРОВАТЬ:
После попытки с PHP 5.6.14 xampp на окнах:

loop: memory | time (sec)
1: 5505024 | 1.0365679264069
3: 6291456 | 33.604479074478
60: 6553600 | 945.90159296989
62: 6815744 | 977.82566595078
93: 7077888 | 1474.5941500664
94: 7340032 | 1490.6698410511
100: 7340032 | 1587.2434458733

EDIT2: я вижу увеличение памяти только после json_decode

Start: 262144 | 135448
After URL array: 262144 | 151984
After chunk: 262144 | 152272
start function: 262144 | 152464
Creation multi handle: 262144 | 152816
For loop options: 262144 | 161424
Execution: 3145728 | 1943472
For loop2
error: 3145728 | 1943520
Content: 3145728 | 2095056
error: 3145728 | 1938952
Content: 3145728 | 2131992
error: 3145728 | 1938072
Content: 3145728 | 2135424
error: 3145728 | 1933288
Content: 3145728 | 2062312
error: 3145728 | 1928504
Content: 3145728 | 2124360
error: 3145728 | 1923720
Content: 3145728 | 2089768
error: 3145728 | 1918936
Content: 3145728 | 2100768
error: 3145728 | 1914152
Content: 3145728 | 2089272
error: 3145728 | 1909368
Content: 3145728 | 2067184
error: 3145728 | 1904616
Content: 3145728 | 2102976
after loop2: 3145728 | 1899824
For loop:
Decode: 3670016 | 2962208
Decode: 4980736 | 3241232
Decode: 5242880 | 3273808
Decode: 5242880 | 2802024
Decode: 5242880 | 3258152
Decode: 5242880 | 3057816
Decode: 5242880 | 3169160
Decode: 5242880 | 3122360
Decode: 5242880 | 3004216
Decode: 5242880 | 3277304

11

Решение

Я проверил ваш скрипт на 10 URL. Я удалил все ваши комментарии, кроме одного комментария в конце скрипта и одного в проблемном цикле при использовании json_decode. Также я открыл одну страницу, которую вы кодируете из API, и выглядел очень большим массивом, и я думаю, что вы правы, у вас есть проблема в json_decode.

Результаты и исправления.

Результат без изменений:

Код:

for($y = 0; $y < $nbrComplete; $y++){
$decoded = json_decode($result['json'][$y], true);
$time = microtime(true) - $start;
echo "Decode: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "\n";
}

Результат:

Decode: 3407872 | 2947584
Decode: 3932160 | 2183872
Decode: 3932160 | 2491440
Decode: 4980736 | 3291288
Decode: 6291456 | 3835848
Decode: 6291456 | 2676760
Decode: 6291456 | 4249376
Decode: 6291456 | 2832080
Decode: 6291456 | 4081888
Decode: 6291456 | 3214112
Decode: 6291456 | 244400

Результат с unset($decode):

Код:

for($y = 0; $y < $nbrComplete; $y++){
$decoded = json_decode($result['json'][$y], true);
unset($decoded);
$time = microtime(true) - $start;
echo "Decode: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "\n";
}

Результат:

Decode: 3407872 | 1573296
Decode: 3407872 | 1573296
Decode: 3407872 | 1573296
Decode: 3932160 | 1573296
Decode: 4456448 | 1573296
Decode: 4456448 | 1573296
Decode: 4980736 | 1573296
Decode: 4980736 | 1573296
Decode: 4980736 | 1573296
Decode: 4980736 | 1573296
Decode: 4980736 | 244448

Также вы можете добавить gc_collect_cycles:

Код:

for($y = 0; $y < $nbrComplete; $y++){
$decoded = json_decode($result['json'][$y], true);
unset($decoded);
gc_collect_cycles();
$time = microtime(true) - $start;
echo "Decode: ". memory_get_peak_usage(true) . " | " . memory_get_usage() . "\n";
}

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

Вы можете попробовать перезапустить скрипт с unset, а также unset+gc и напишите назад, если у вас будет та же проблема после изменений.

Также я не вижу, где вы используете $decoded переменная, если это ошибка в коде, вы можете удалить json_decode 🙂

1

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

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

Вы можете подумать о том, чтобы реорганизовать этот код в более мелкие методы, чтобы воспользоваться этим преимуществом, а также всеми другими полезными вещами, связанными с меньшими методами, однако в то же время вы можете попробовать gc_collect_cycles(); в самом конце вашего цикла, чтобы увидеть, можете ли вы освободить память:

if(isset($result['again'])){
$urls = array_merge($urls, $result['again']);
unset($result['again']);
}

unset($result);
unset($time);

gc_collect_cycles();//add this line here
sleep(15); // limit the request rate

Изменить: сегмент, который я обновил, на самом деле не принадлежит большой функции, однако я подозреваю, что размер может быть $result может перевернуть вещи, и он не будет очищен, пока цикл не прекратится, возможно. Это стоит попробовать, однако.

4

Итак, мой вопрос: я делаю что-то не так? Это нормально? Что можешь
Я делаю, чтобы исправить эту проблему?

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

Что еще можно сделать?

  1. Не запускайте несколько http-соединений одновременно. Очередь $threads до 1, чтобы проверить это. Если есть утечка памяти в вызове расширения C gc_collect_cycles() не освобождает память, это влияет только на память, выделенную в Zend Engine, которая больше недоступна.
  2. Сохраните результаты в папке и обработайте их в другом скрипте. Вы можете переместить обработанные файлы в подкаталог, чтобы пометить, когда вы успешно обработали файл json.
  3. Исследуйте разветвление или очередь сообщений, чтобы несколько процессов работали над частью проблемы одновременно — либо несколько процессов PHP прослушивали сегмент очереди, либо разветвляли дочерние процессы родительского процесса со своей собственной памятью процесса.
3

Итак, мой вопрос: я делаю что-то не так? Это нормально? Что я могу сделать, чтобы решить эту проблему?

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

Конечно, решение вашей проблемы может быть простым:

ini_set('memory_limit', -1);

Что позволяет использовать всю необходимую память.


Когда я использую фиктивный контент, использование памяти остается неизменным между запросами.

Это использует PHP 5.5.19 в XAMPP на Windows.

Там был ошибка, связанная с утечкой памяти cURL который был исправлен в версии 5.5.4

1