web-dev-qa-db-ja.com

Node.jsリクエストモジュールがETIMEDOUTおよびESOCKETTIMEDOUTを取得

request モジュールと async モジュールの組み合わせと並行して、多くのリンクをクロールしています。
リンクに到達可能で、クロムを使用してかなり迅速に応答しますが、多くのETIMEDOUTおよびESOCKETTIMEDOUTエラーに気づいています。

要求オプションで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の場合は常に接続オブジェクトがtrue/falseであるため、接続タイムアウトである場合とそうでない場合があります(リクエストドキュメントによる)

UPDATE:maxSocketsInfinityにブーストすることで、使用可能なソケットがないために接続がハングアップしないようにしました。

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);
            }
        });
    }
};

これをまとめると:-maxSocketsInfinityにブーストして、ソケット接続のタイムアウトエラーを克服しようとしました-失敗したリクエストとrequestLoopおよびそのようなリクエストのmaxAttempsを制御する実装されたretryDelayメソッド-渡された数によって設定される同時リクエストの最大数もありますasync.filterLimit

エラーのないクロールを取得するために、ここですべての設定を試しましたが、これまでの試行も失敗しました。

この問題の解決に関するヘルプを引き続き探しています。

UPDATE2:async.filterLimitを削除して、独自の制限メカニズムを作成することにしました。これを達成するのに役立つ変数が3つだけあります。
pendingRequests-すべての要求を保持する要求配列(後で説明します)activeRequests-アクティブな要求の数maxConcurrentRequests-許可される同時要求の最大数

pendingRequests配列に、i 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()の呼び出しに注意してください。この関数ジョブは、最大activeRequestsmaxConcurrentRequestsの制限以下に保ちながら、可能な場合に要求を管理し、要求を起動します。

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();
});

これにより、タイムアウトエラーが解決するはずです。私のテストでは、これをテストした特定のWebサイトでいくつかのタイムアウトにまだ気付いています。私はこれについて100%確信することはできませんが、httpチェックをサポートするウェブサイトの性質が、IPチェックを行うことでユーザーリクエストを最大に制限し、その結果HTTP 400メッセージを返すためだと考えていますサーバーで起こり得る「攻撃」を防ぐため。

40
Jorayen

編集: https://stackoverflow.com/a/37946324/744276 の複製

デフォルトでは、Nodeには DNSクエリを解決する4つのワーカー があります。 DNSクエリに時間がかかる場合、要求はDNSフェーズでブロックされ、症状はESOCKETTIMEDOUTまたはETIMEDOUTになります。

UVスレッドプールサイズを増やしてみてください。

export UV_THREADPOOL_SIZE=128
node ...

またはindex.js(またはエントリポイントがある場所):

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

function main() {
   ...
}

編集: これはブログの投稿です それについて。

31

非同期リクエストが多すぎる場合、LinuxでESOCKETTIMEDOUT例外が発生することがわかりました。私が見つけた回避策はこれをしている:

このオプションをrequest()に設定します:agent: false, pool: {maxSockets: 100}その後、タイムアウトが横たわっている可能性があるため、それを増やす必要があるかもしれません。

1
cancerbero