Запросы 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), так как они оба не изменили бы что-то одновременно.
Вопросы
- Я делаю что-то не так в своем использовании multi-curl, что может вызвать это? (но если так, что изменилось?)
- Изменили ли Facebook и Instagram что-нибудь, что могло бы вызвать это?
- Может ли что-то на моем сервере измениться, чтобы вызвать это?
- Как я могу отладить это?
Обновление Вот что я получаю от медленного запроса, когда он наконец завершается:
ИНФОРМАЦИЯ
"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, я не уверен, что еще делать.