Подтвердить что ты не робот

Node.js модуль запроса, получающий ETIMEDOUT и ESOCKETTIMEDOUT

Я сканирую множество ссылок с модулем запроса параллельно с комбинацией модуля async.
Я замечаю множество ошибок ETIMEDOUT и ESOCKETTIMEDOUT, хотя ссылки доступны и довольно быстро реагируют с помощью Chrome.

Я ограничил maxSockets до 2 и timeout до 10000 в опциях запроса. Я использую async.filterLimit() с пределом 2, чтобы даже сократить параллелизм до 2 запросов каждый раз. Таким образом, у меня есть 2 сокета, 2 запроса и тайм-аут в 10 секунд для ожидания ответа заголовков с сервера, но я получаю эти ошибки.

Здесь я использую конфигурацию запроса:

{
    ...
    pool: {
        maxSockets: 2
    },
    timeout: 10000
    ,
    time: true
    ...
}

Вот фрагмент кода, который я использую для поиска ссылок:

var self = this;
async.filterLimit(resources, 2, function(resource, callback) {
    request({
        uri: resource.uri
    }, function (error, response, body) {
        if (!error && response.statusCode === 200) {
            ...
        } else {
            self.emit('error', resource, error);
        }
        callback(...);
    })
}, function(result) {
    callback(null, result);
});

Я прослушал событие ошибки и вижу, что всякий раз, когда код ошибки ETIMEDOUT, объект подключения является либо истинным, либо ложным, поэтому иногда это время ожидания подключения, а иногда - нет (в соответствии с документацией запроса)

UPDATE: Я решил увеличить maxSockets до Infinity, чтобы соединение не зависало из-за отсутствия доступных сокетов:

pool: {
    maxSockets: Infinity
}

Для контроля полосы пропускания я реализовал метод requestLoop, который обрабатывает запрос с параметрами maxAttemps и retryDelay для управления запросами:

async.filterLimit(resources, 10, function(resource, callback) {
    self.requestLoop({
        uri: resource.uri
    }, 100, 5000, function (error, response, body) {
            var fetched = false;
            if (!error) {
                ...
            } else {
                ....
            }
            callback(...);
        });
}, function(result) {
    callback(null, result);
});

Реализация requestLoop:

requestLoop = function(options, attemptsLeft, retryDelay, callback, lastError) {
    var self = this;
    if (attemptsLeft <= 0) {
        callback((lastError != null ? lastError : new Error('...')));
    } else {
        request(options, function (error, response, body) {
            var recoverableErrors = ['ESOCKETTIMEDOUT', 'ETIMEDOUT', 'ECONNRESET', 'ECONNREFUSED'];
            var e;
            if ((error && _.contains(recoverableErrors, error.code)) || (response && (500 <= response.statusCode && response.statusCode < 600))) {
                e = error ? new Error('...');
                e.code = error ? error.code : response.statusCode;
                setTimeout((function () {
                    self.requestLoop(options, --attemptsLeft, retryDelay, callback, e);
                }), retryDelay);
            } else if (!error && (200 <= response.statusCode && response.statusCode < 300)) {
                callback(null, response, body);
            } else if (error) {
                e = new Error('...');
                e.code = error.code;
                callback(e);
            } else {
                e = new Error('...');
                e.code = response.statusCode;
                callback(e);
            }
        });
    }
};

Итак, чтобы подвести итог: - Увеличен maxSockets до Infinity, чтобы попытаться преодолеть ошибку тайм-аута соединения сокетов - Реализован метод requestLoop для управления неудачным запросом и maxAttemps, а также retryDelay таких запросов - Также есть максимальное количество одновременных запросов, установленных числом, переданным async.filterLimit

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

Все еще ищу помощь в решении этой проблемы.

UPDATE2: Я решил отказаться от async.filterLimit и создать свой собственный механизм ограничения. У меня просто есть 3 переменные, которые помогут мне достичь этого:
pendingRequests - массив запросов, который будет содержать все запросы (объясню позже) activeRequests - количество активных запросов maxConcurrentRequests - количество максимально разрешенных одновременных запросов

в массив pendingRequests я помещаю сложный объект, содержащий ссылку на функцию requestLoop, а также массив arguments, содержащий аргументы для передачи в функцию цикла:

self.pendingRequests.push({
    "arguments": [{
        uri: resource.uri.toString()
    }, self.maxAttempts, function (error, response, body) {
        if (!error) {
            if (self.policyChecker.isMimeTypeAllowed((response.headers['content-type'] || '').split(';')[0]) &&
                self.policyChecker.isFileSizeAllowed(body)) {
                self.totalBytesFetched += body.length;
                resource.content = self.decodeBuffer(body, response.headers["content-type"] || '', resource);
                callback(null, resource);
            } else {
                self.fetchedUris.splice(self.fetchedUris.indexOf(resource.uri.toString()), 1);
                callback(new Error('Fetch failed because a mime-type is not allowed or file size is bigger than permited'));
            }
        } else {
            self.fetchedUris.splice(self.fetchedUris.indexOf(resource.uri.toString()), 1);
            callback(error);
        }
        self.activeRequests--;
        self.runRequest();
    }],
    "function": self.requestLoop
});
self.runRequest();

Вы замечаете звонок в runRequest() в конце. Эта функциональная задача состоит в том, чтобы управлять запросами и запускать запросы, когда это возможно, сохраняя максимальное значение activeRequests ниже предела maxConcurrentRequests:

var self = this;
process.nextTick(function() {
    var next;
    if (!self.pendingRequests.length || self.activeRequests >= self.maxConcurrentRequests) {
        return;
    }
    self.activeRequests++;
    next = self.pendingRequests.shift();
    next["function"].apply(self, next["arguments"]);
    self.runRequest();
});

Это должно решить любые ошибки тайм-аутов, хотя в ходе моего тестирования я все же заметил некоторые тайм-ауты на определенных веб-сайтах, на которых я это тестировал. Я не могу быть на 100% уверен в этом, но я думаю, что это связано с тем, что веб-сайт поддерживает http-сервер, ограничивая пользовательские запросы до максимума, выполняя ip-проверку и в результате возвращая некоторые сообщения HTTP 400 предотвратить возможную "атаку" на сервер.

4b9b3361

Ответ 1

Изменение: дубликат fooobar.com/questions/252059/...

По умолчанию в Node есть 4 рабочих для разрешения DNS-запросов. Если ваш DNS-запрос занимает много времени, запросы будут блокироваться на фазе DNS, и симптом будет точно ESOCKETTIMEDOUT или ETIMEDOUT.

Попробуйте увеличить размер пула ультрафиолетовых потоков:

export UV_THREADPOOL_SIZE=128
node ...

или в index.js (или где бы вы ни находились):

#!/usr/bin/env node
process.env.UV_THREADPOOL_SIZE = 128;

function main() {
   ...
}

Изменение: Я также написал сообщение в блоге об этом.

Ответ 2

Я обнаружил, что слишком много асинхронных запросов, тогда исключение ESOCKETTIMEDOUT происходит в Linux. Обходной путь, который я нашел, делает следующее:

установка этих параметров для запроса(): agent: false, pool: {maxSockets: 100} Обратите внимание, что после этого тайм-аут может лежать, поэтому вам может потребоваться его увеличить.