Запросы PHP Multi-cURL задерживаются до истечения времени ожидания

Резюме

У меня есть некоторый код PHP 5.4, который выбирает пакет фотографий Facebook/Instagram параллельно, используя multi curl. Этот код работал годами, и, насколько я могу судить, ничего не изменилось.

Я добавляю несколько запросов curl к запросу multi. Каждый запрос curl получает CURLOPT_TIMEOUT, Проблема, с которой я сталкиваюсь, заключается в том, что некоторые мои запросы неожиданно не завершаются, пока не истечет этот тайм-аут (независимо от того, какой тайм-аут я установил).

Код

Я делаю что-то вроде этого (упрощенно):

do {
    while (CURLM_CALL_MULTI_PERFORM === curl_multi_exec($mh, $running));

    // Wait for activity on any curl-connection (optional, reduces CPU)
    curl_multi_select($mh);

    // a request was just completed -- find out which one
    while($done = curl_multi_info_read($mh))
    {
        $completedCurlRequest = $done['handle'];

        //save the file
        do_some_work(completedCurlRequest);

        curl_multi_remove_handle($mh, $completedCurlRequest);
    }
} while ($running);

Я использую этот сценарий для запуска пакетов из примерно 40 параллельных запросов для получения некоторых изображений (из Facebook). Большинство из них занимает около 500 мс. Однако несколько запросов "зависают" (до CURLOPT_TIMEOUT), прежде чем они прибывают.

В основном curl_multi_select шаг занимает весь тайм-аут. Или, если я уберу это curl_multi_select линия, внешний цикл вращается (горящий процессор) до истечения времени ожидания.

Соображения

  • Неважно, что это за тайм-аут - если я установлю тайм-аут на 30 с, они прибудут через 30 секунд, если я установлю тайм-аут на 1 с, они прибудут через 1 с!

  • Это действительно внезапное изменение, которое не коррелирует ни с одним выпуском кода - все работало нормально до 30 января 2019 года, но 31-го внезапно перестало работать.

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

  • Это влияет как на изображения на Facebook, так и на Instagram, поэтому я думаю, что проблема должна быть связана с моим кодом или моим сервером (а не Facebook или Instagram), так как они оба не изменили бы что-то одновременно.

Вопросы

  1. Я делаю что-то не так в своем использовании multi-curl, что может вызвать это? (но если так, что изменилось?)
  2. Изменили ли Facebook и Instagram что-нибудь, что могло бы вызвать это?
  3. Может ли что-то на моем сервере измениться, чтобы вызвать это?
  4. Как я могу отладить это?

Обновление Вот что я получаю от медленного запроса, когда он наконец завершается:

ИНФОРМАЦИЯ

"content_type": "image/jpeg",
"http_code": 200,
"header_size": 377,
"request_size": 180,
"total_time": 15.001012,    //<----- Total time == CURLOPT_TIMEOUT
"namelookup_time": 0.007149,
"connect_time": 0.12018,
"pretransfer_time": 0.441911,
"size_download": 40714,
"speed_download": 2714,
"download_content_length": -1,   //<------Not set

HEADER

HTTP/2 200 
content-type: image/jpeg
x-haystack-needlechecksum: 3529661797
timing-allow-origin: *
access-control-allow-origin: *
cache-control: max-age=1209600, no-transform
date: Mon, 04 Feb 2019 14:04:17 GMT
access-control-expose-headers: X-FB-CEC-Video-Limit

Отсутствует content-length заголовок, но это всегда имеет место при первом получении файла. Только 1 или 2 из 50 параллельных запросов являются медленными, но у всех запросов отсутствуют заголовки длины содержимого.

Если я снова получу тот же файл, он будет намного быстрее, и я увижу, что на этот раз устанавливается длина контента

ИНФОРМАЦИЯ

"download_content_length": 52721,

HEADER

content-length: 52721           

1 ответ

Решение

Моя текущая теория заключается в том, что в файловом сервере Facebook есть ошибка, которая означает, что соединение иногда не закрывается, даже если данные отправлены, поэтому соединение остается открытым до истечения времени ожидания. В отсутствие (необязательного) заголовка длины содержимого, отправляемого файловым сервером Facebook, cURL не может знать, завершена ли полезная нагрузка, и поэтому зависает.

Мое текущее решение - "заправить" файловый сервер, сначала сделав запрос на изображение без тела, например:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_NOBODY, 1);
curl_exec($ch);

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

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

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

Другие вопросы по тегам