Определите, какое обещание не удалось, и динамически изменяйте очередь обещаний в Guzzle 6.

Мне нужно загрузить большое количество больших файлов, хранящихся на нескольких одинаковых серверах. Файл, такой как «5.doc», который хранится на сервере 3, также хранится на сервере 55.

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

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

Если сервер не работает, я установил тайм-аут в 300 секунд, и когда он будет достигнут, Guzzle перехватит исключение ConnectionException.

Как определить, какие обещания (загрузки) не удалось выполнить, чтобы отменить их? Могу ли я получить информацию о том, какой файл/сервер вышел из строя?

Ниже приведен упрощенный пример кода, который я использую для иллюстрации. Спасибо за помощь!

$filesToDownload = [['5.doc', '8.doc', '10.doc'], ['1.doc', '9.doc']]; //The file names that we need to download
$availableServers = [3, 55, 88]; //Server id's that are available

foreach ($filesToDownload as $index => $fileBatchToDownload) {
    $promises = [];

    foreach ($availableServers as $key => $availableServer) {
        array_push(
            $promises, $client->requestAsync('GET', 'http://domain.com/' . $fileBatchToDownload[$index][$key],  [
                'timeout' => 300,
                'sink' => '/assets/' . $fileBatchToDownload[$index][$key]
            ])
        );

        $database->updateRecord($fileBatchToDownload[$index][$key], ['is_cached' => 1]);
    }

    try {
        $results = Promise\unwrap($promises);
        $results = Promise\settle($promises)->wait();
    } catch (\GuzzleHttp\Exception\ConnectException $e) {
        //When can't connect to the server or didn't download within timeout
        foreach ($e->failed() as $failedPromise) {
            //Re-set record in database to is_cached = 0
            //Delete file from server
            //Remove this server from the $availableServers list as it may be down or too slow
            //Re-add this file to the next batch to download $filesToDownload
        }
    }
}

person Toby Mellor    schedule 03.07.2016    source источник
comment
В настоящее время я устанавливаю идентификатор отказавшего сервера в качестве заголовка, который затем можно получить с помощью $e->getRequest()->getHeaders()[‹header›][0]. (временное решение)   -  person Toby Mellor    schedule 03.07.2016


Ответы (1)


Я не уверен, как вы выполняете асинхронную загрузку одного файла с нескольких серверов с помощью Guzzle, но получить индекс массива неудачных запросов можно с помощью метода обещания then():

array_push(
    $promises,
    $client->requestAsync('GET', "http://localhost/file/{$id}", [
            'timeout' => 10,
            'sink' => "/assets/{$id}"
        ])->then(function() {
            echo 'Success';
        },
        function() use ($id) {
            echo "Failed: $id";
        }
    )
);

then() принимает два обратных вызова. Первый срабатывает при успехе, а второй при неудаче. Источник называет их $onFullfilled и $onRejected. Другие варианты использования задокументированы в документации. Таким образом, вы можете начать загрузку файла сразу после его сбоя.

Могу ли я получить информацию о том, какой файл/сервер вышел из строя?

Когда обещание не выполнено, это означает, что запрос остался невыполненным. В этом случае вы можете получить хост и запрошенный путь, передав экземпляр класса RequestException второму обратному вызову then():

use GuzzleHttp\Exception\RequestException;
.
.
.
array_push(
    $promises,
    $client->requestAsync('GET', "http://localhost/file/{$id}", [
            'timeout' => 10,
            'sink' => "/assets/{$id}"
        ])->then(function() {
            echo 'Success';
        },
        function(RequestException $e)  {
            echo "Host: ".$e->getRequest()->getUri()->getHost(), "\n";
            echo "Path: ".$e->getRequest()->getRequestTarget(), "\n";
        }
    )
);

Таким образом, у вас будет полная информация о неисправном хосте и имени файла. Если вам может понадобиться доступ к дополнительной информации, вы должны знать, что $e->getRequest() возвращает экземпляр класса GuzzleHttp\Psr7\Request и все методы на этот класс можно использовать здесь. (Guzzle и PSR-7)

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

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

person revo    schedule 10.07.2016
comment
Спасибо за помощь в первой части вопроса. Когда элемент успешно загружен, можем ли мы сразу же начать загрузку нового файла на теперь свободном сервере, пока другие файлы загружаются? Чтобы уточнить, я не уверен, как вы выполняете асинхронную загрузку одного файла с нескольких серверов, каждый файл доступен на всех серверах, но файл загружается только с одного сервера, в то время как другие серверы сосредоточены на остальных. Когда он запустится, сервер 3 загрузит «5.doc», 55 «8.doc» и 88 «10.doc». Когда кто-то заканчивает загрузку, он должен немедленно начать с «1.doc». - person Toby Mellor; 10.07.2016
comment
Я добавил больше информации в отношении вашего комментария. @ТобиМеллор - person revo; 10.07.2016
comment
Спасибо, это было чрезвычайно полезно. Я наградил тебя наградой. - person Toby Mellor; 10.07.2016