Самый быстрый способ получить последний измененный заголовок 80K запросов - PullRequest
0 голосов
/ 07 февраля 2019

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

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

Я создал скрипт консоли php, который использует curl multi request , сделанный с этой библиотекой: ParallelCurl - github .

Мой код PHP:

function setComparatorData( $model, $filetime ) {
    global $comparator;

    if ( file_exists(DIR_IMAGE . "catalog/" . $model . ".jpg") ) {
        $localFileTime = filemtime(DIR_IMAGE . "catalog/" . $model . ".jpg");
        if ( $localFileTime > $filetime ) return;
    }

    $comparator[$model] = $filetime;
}

function onReceived($content, $url, $ch, $request) {
    $data = curl_getinfo($ch);
    setComparatorData($request['model'], $data['filetime']);
}

function request($limit = 100) {
    $products = array(); // This is array of products from database

    $curl_options = array(
        CURLOPT_SSL_VERIFYPEER  => FALSE,
        CURLOPT_SSL_VERIFYHOST  => FALSE,
        CURLOPT_USERAGENT       => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
        CURLOPT_NOBODY          => TRUE,
        CURLOPT_FOLLOWLOCATION  => TRUE,
        CURLOPT_HEADER          => TRUE,
        CURLOPT_FILETIME        => TRUE,
        CURLOPT_TIMEOUT         => 5,
        CURLOPT_FRESH_CONNECT   => TRUE // For test!
    );

    $parallel_curl = new ParallelCurl($limit, $curl_options);

    foreach ($products as $product) {
        $parallel_curl->startRequest("http://supplierImageUrlBasedOnProductVariable",'onReceived', array("model" => $product['model'], "source" => "remote"));
    }

    $parallel_curl->finishAllRequests();
}


$comparator = array();
request(100);
print_r($comparator);

Это разделяет мультизапрос на 100 параллельных запросов, и после завершения одной "группы" запускается следующая.Моя проблема в том, что это медленно, как ад.Для 600 запросов (изображений продуктов) это заняло 8 секунд, а для 5000 - полчаса (затем я остановил его).

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

Возможно, мое решение совершенно неверно, если у кого-то есть другая идея, запишите как.

Спасибо

Ответы [ 2 ]

0 голосов
/ 08 февраля 2019

изменяя код с https://stackoverflow.com/a/54353191/1067003 (который был разработан, чтобы быть очень быстрым с большими списками), я получаю

function last_modified_from_urls(array $urls, int $max_connections, int $timeout_ms = 10000) : array
{
    if ($max_connections < 1) {
        throw new InvalidArgumentException("max_connections MUST be >=1");
    }
    foreach ($urls as $key => $foo) {
        if (!is_string($foo)) {
            throw new \InvalidArgumentException("all urls must be strings!");
        }
        if (empty($foo)) {
            unset($urls[$key]); //?
        }
    }
    unset($foo);
    $urls = array_unique($urls); // remove duplicates.
    $ret = array();
    $mh = curl_multi_init();
    $workers = array();
    $headerfunction = function ($ch, string $header) use (&$ret, &$workers) {
        $lm = 'Last-Modified:';
        if (0 === stripos($header, $lm)) {
            $save = trim(substr($header, strlen($lm)));
            $ret[$workers[(int)$ch]] = $save;
        }
        return strlen($header);
    };
    $work = function () use (&$ret, &$workers, &$mh, &$return_fault_reason) {
        // > If an added handle fails very quickly, it may never be counted as a running_handle
        while (1) {
            curl_multi_exec($mh, $still_running);
            if ($still_running < count($workers)) {
                break;
            }
            $cms = curl_multi_select($mh, 10);
            //var_dump('sr: ' . $still_running . " c: " . count($workers)." cms: ".$cms);
        }
        while (false !== ($info = curl_multi_info_read($mh))) {
            //echo "NOT FALSE!";
            //var_dump($info);
            {
                if ($info['msg'] !== CURLMSG_DONE) {
                    continue;
                }
                if ($info['result'] !== CURLM_OK) {
                    $ret[$workers[(int)$info['handle']]] = array(false, $info['result'], "curl_exec error " . $info['result'] . ": " . curl_strerror($info['result']));
                } elseif (CURLE_OK !== ($err = curl_errno($info['handle']))) {
                    $ret[$workers[(int)$info['handle']]] = array(false, $err, "curl error " . $err . ": " . curl_strerror($err));
                } else {
                    $code = (string)curl_getinfo($info['handle'], CURLINFO_HTTP_CODE);
                    if ($code[0] === "2") {
                        if (!isset($ret[$workers[(int)$info['handle']]])) {
                            $ret[$workers[(int)$info['handle']]] = array(false, 0, "did not get a Last-Modified header!");
                        } else {
                            assert(
                                is_string($ret[$workers[(int)$info['handle']]]),
                                "last modified should be set by the headerfunction."
                            );
                        }
                    } else {
                        // all non-2xx and non-3xx are always considered errors (500 internal server error, 400 client error, 404 not found, etcetc)
                        $ret[$workers[(int)$info['handle']]] = array(false, -1, "got a http " . $code . " code, which is considered an error");
                    }
                }
                curl_multi_remove_handle($mh, $info['handle']);
                assert(isset($workers[(int)$info['handle']]));
                unset($workers[(int)$info['handle']]);
                curl_close($info['handle']);
            }
        }
        //echo "NO MORE INFO!";
    };
    foreach ($urls as $url) {
        while (count($workers) >= $max_connections) {
            //echo "TOO MANY WORKERS!\n";
            $work();
        }
        $neww = curl_init($url);
        if (!$neww) {
            trigger_error("curl_init() failed! probably means that max_connections is too high and you ran out of resources", E_USER_WARNING);
            $ret[$url] = array(false, -1, "curl_init() failed");
            continue;
        }
        $workers[(int)$neww] = $url;
        curl_setopt_array($neww, array(
            CURLOPT_NOBODY => 1,
            CURLOPT_HEADERFUNCTION => $headerfunction,
            CURLOPT_SSL_VERIFYHOST => 0,
            CURLOPT_SSL_VERIFYPEER => 0,
            CURLOPT_TIMEOUT_MS => $timeout_ms
        ));
        curl_multi_add_handle($mh, $neww);
        //curl_multi_exec($mh, $unused_here); LIKELY TO BE MUCH SLOWER IF DONE IN THIS LOOP: TOO MANY SYSCALLS

    }
    while (count($workers) > 0) {
        //echo "WAITING FOR WORKERS TO BECOME 0!";
        //var_dump(count($workers));
        $work();
    }
    curl_multi_close($mh);
    return $ret;
}

, который должен быть близок к скорости, с которой вы можете получить егоcurl_multi, использование:

$urls = array(
    'example.com',
    'example.org',
    'ratma.net'
);
var_dump(
    last_modified_from_urls(
        $urls,
        500
    )
);

возвращение:

array(3) {
  ["example.com"]=>
  string(29) "Fri, 09 Aug 2013 23:54:35 GMT"
  ["ratma.net"]=>
  string(29) "Thu, 09 Nov 2017 12:44:58 GMT"
  ["example.org"]=>
  string(29) "Fri, 09 Aug 2013 23:54:35 GMT"
}
0 голосов
/ 07 февраля 2019

Вы можете сохранить дату последнего обновления в одном файле.Прочитайте этот файл и обновите весь список файлов после последнего выполнения и обновите все новые файлы с сервера.

$ReminderFile = __DIR__ ."/check_hour.txt";
if(!file_exists($ReminderFile)) {
    $handle = fopen($ReminderFile, "w");
    $lastExecuteDate = date("Y-m-d h:i:sa");
    fwrite($handle, $lastExecuteDate);
} else {
    $handle = fopen($ReminderFile, "r");
    $lastExecuteDate = fread($handle,filesize($ReminderFile));
}


/**
  * @param Array  : array of files path and name
  * @param String : date selector 
  * @param String : optional, the passed date format default is m:d:Y ex, 09:30:2015 @link http://php.net/manual/en/function.date.php for more options
  * @return Array : array of filtered files path and name
  */
function fileFilter ($files, $date, $format = 'Y-m-d h:i:sa') {
    $selectedFiles = array ();

    foreach ($files as $file) {
        if (date ($format, filemtime ($file)) == $date) {
            $selectedFiles[] = $file; 
        }
    }
    return $selectedFiles;
}
// example :
var_dump(fileFilter (glob("C:/*.*"), $lastExecuteDate));

/** Update date in text file after execution **/
    $handle = fopen(__DIR__ ."/check_hour.txt", "w");
    $lastExecuteDate = date('Y-m-d H:i:sa');
    fwrite($handle, $lastExecuteDate);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...