2017-04-01 6 views
0

私は現在、ユーザー入力を受け取り、トレーニングデータに単語が何回発生したかに基づいて、ユーザーのコメントが属していた可能性があるトップサブトレンドを返します。私は、サブレッジごとに単語の頻度を含むデータベースを持っています。私はサブレッジごとに、またユーザの入力にあるすべての単語に対してGETリクエストを作成しています。要求の約束があまりにも多くのソケットを開く原因となる約束

データベースに7000件以上のサブディレクトリがあるため、これにより多くの取得要求が追加されます。私は現在、すべてのサブディレクトリのリストを取得するためのリクエスト(約束)要求を行っています。次に、各サブディレクトリに対して、ユーザーの入力内の各単語をループし、別の要求約束オブジェクトを作成して約束の配列に追加します。

すべてのリクエスト・プロミス・オブジェクトが追加されると、Promise.allを使用してすべて解決されるまで待ってから、指定したサブreditのワード頻度の配列を出力しようとしますが、エラー: EMFILE 'メッセージを接続します。

スタックオーバーフローの別の記事によると、これは私がソケットをあまりにも多く開いていることを意味しますが、どうして起こるのか混乱しています。私の理解では、一度に開く可能な接続はuser_words.lengthまでしかないでしょう。なぜなら、それらはPromise.allが解決するのを待っている間に行われているリクエストなのですから?私は接続が閉じていない方法を表示しません。

ありがとうございました!

function getBestSubreddit(messageText) { 
    var user_words = parse_message(messageText); 
    var top_subreddit = ""; 
    var top_score = Number.MIN_SAFE_INTEGER; 
    rp(dbUrl + '/.json?shallow=true').then(function(res) { 
    res = JSON.parse(res); 
    for (var subreddit in res) { 
     if (res.hasOwnProperty(subreddit)) { 
     var score = 0.0; 
     var promises = [] 
     for (var i = 0; i < user_words.length; i++) { 
      promises.push(rp(dbUrl + '/' + subreddit + '/word_freqs/' + user_words[i] + '.json')); 
     } 
     Promise.all(promises).then(function(values) { 
      console.log(values); 
     }, function(err) { 
      console.log(err); 
     }); 
     } 
    } 
    }).catch(function(err) { 
    console.log(err); 
    }) 
    return top_subreddit; 
} 
+0

あなたのタイトルの提案とは異なり、この問題は「リクエスト - 約束」に起因するものではありません。この問題は、同時に複数のリクエストを処理しようとする2つのネストされたループによって発生します。 – jfriend00

+0

いくつかの関連する回答:[nodejsアプリケーションから何百万もの並列httpリクエストを作成する方法?](http://stackoverflow.com/questions/38268371/how-to-make-millions-of-parallel-http-requests-from- nodejs-app/38272107#38272107)と[ノードjs。 "リクエスト"パッケージでいくつの同時リクエストを送信できますか?(http://stackoverflow.com/questions/36611890/in-node-js-how-many-simultaneous-requests-can-i-send-with-the -request-package/36612175#36612175)と[100万のリクエスト作成](http://stackoverflow.com/questions/34802539/node-js-socket-explanation/34802932#34802932)を参照してください。 – jfriend00

+0

個人的には、Bluebirdの 'Promise.map()'の並行処理オプションを使用して、同時に何回のリクエストが飛行中であったかを管理することができます。 – jfriend00

答えて

0

From my understanding, wouldn't it only ever have up to user_words.length possible connections open at a time, since those are the requests that are being done while the Promise.all waits to resolve? I don't see how the connections are not being closed.

いいえ、これは正しくありません。 2つのネストされたforループがあるので、user_words.length * how many subreddits there areを同時に開くことができます。 rp()Promise.all()はブロックされないので、いずれかの応答が処理される前に、ネストされたforループを実行してすべての単一接続を完了するようにしてください。

コードreturn top_subredditの行で何らかの形で同期的に結果を返すことが予想されるようです。あなたはそれをすることもできません。最終的に望ましい結果に結びつく約束を返すべきです。

From my understanding, wouldn't it only ever have up to user_words.length possible connections open at a time, since those are the requests that are being done while the Promise.all waits to resolve? I don't see how the connections are not being closed.

これはPromise.all()の正しい理解ではありません。 Promise.all()はブロックされません。コードが終了しないうちにすべての約束が解決されるまで「待機」しません。非同期に動作します。あなたのコードは、あなたのforループの他の繰り返しを実行し続け、Promise.all()は、あなたがそれを渡したすべての約束が終わったときにいつか、いつかハンドラを呼び出すでしょう。.then()あなたのforループの他の反復は引き続き実行され、より多くのソケットが積み重なります。

私は、これにアプローチする最も簡単な方法は、処理したいURLの配列を作成してから、既に最大N個の非同期を実行できるように組み込み関数を備えた非同期ライブラリの1つを使用することです同時に機内での操作を可能にします。あなたのコードは有望なので、私はBluebirdのPromise.map()を選択してURLのリストを処理します。

var Promise = require('bluebird'); 

function getBestSubreddit(messageText) { 
    var user_words = parse_message(messageText); 
    var top_subreddit = ""; 
    var top_score = Number.MIN_SAFE_INTEGER; 
    return rp(dbUrl + '/.json?shallow=true').then(function(res) { 
    res = JSON.parse(res); 
    // build a list of URLs to process 
    var urls = []; 
    for (var subreddit in res) { 
     if (res.hasOwnProperty(subreddit)) { 
     for (var i = 0; i < user_words.length; i++) { 
      urls.push(dbUrl + '/' + subreddit + '/word_freqs/' + user_words[i] + '.json'); 
     } 
     } 
    } 
    // 
    return Promise.map(urls, function(url) { 
     return rp(url); 
    }, {concurrency: 20}).then(function(allResults) { 
     // do any final processing of allResults here and return that value 
     // to become the resolved result of the returned promise 
    }); 
    } 
} 

getBestSubreddit(someText).then(function(result) { 
    // process result here 
}).catch(function(err) { 
    // handle error here 
}); 

この例では、同時リクエストの数を20に設定しました。それを高い数値または低い数値に変更することでスループットが向上するかどうかを試すことができます。理想的な数は、ローカル実行環境、要求しているデータの量、所持している帯域幅、要求元のターゲットホスト、同時要求を処理する方法など、いくつかの要素に依存します。あまりにも多くのリクエストをあまりにも早く行うと、ターゲットによるレート制限について心配する必要があるかもしれません。

いくつかの他の関連解答:

How to make millions of parallel http requests from nodejs app?

In Node js. How many simultaneous requests can I send with the "request" package

Making a million requests


それはまだあなたが取得しようとしている結果とまさにあなたの質問から私には明確ではありません可能なすべてのデータを収集するバージョンがあります。この形式のオブジェクトの配列で終わります。{result: result, subreddit: subreddit, word: word}ここで、resultは、与えられたサブレジットと与えられた単語のためのrp()の結果です。

var Promise = require('bluebird'); 

function getBestSubreddit(messageText) { 
    var user_words = parse_message(messageText); 
    var top_subreddit = ""; 
    var top_score = Number.MIN_SAFE_INTEGER; 
    return rp(dbUrl + '/.json?shallow=true').then(function(res) { 
    res = JSON.parse(res); 
    // build a list of URLs to process 
    var requestData = []; 
    for (var subreddit in res) { 
     if (res.hasOwnProperty(subreddit)) { 
     for (var i = 0; i < user_words.length; i++) { 
      requestData.push({url:dbUrl + '/' + subreddit + '/word_freqs/' + user_words[i] + '.json', subreddit: subreddit, word: user_words[i]}); 
     } 
     } 
    } 
    // 
    return Promise.map(requestData, function(url) { 
     return rp(requestData.url).then(function(result) { 
      return {result: result, subreddit: requestData.subreddit, word: requestData.word}; 
     }); 
    }, {concurrency: 20}).then(function(allResults) { 
     // now filter through all the data with appropriate subreddit 
     // allResults is an array of objects of this form {result: result, subreddit: subreddit, word: word} 
     // return whatever you want the final result to be after processing the allResults array 
    }); 
    } 
} 

getBestSubreddit(someText).then(function(result) { 
    // process result here 
}).catch(function(err) { 
    // handle error here 
}); 
+0

ありがとうございます!私はあなたが提供したコードは、urls配列にすべての可能なURLを追加してPromise.map()を使って一度に並行性を最大20に制限しているので、すべてのサブレイドのすべての頻度のリストを返すと思います。 subredditごとに単語の頻度のリストを取得する方法はありますか?私は、それぞれのサブディジットのスコアを計算し、そのスコアを使ってトップサブトレンドを決定したいと考えています。このため、個々のサブレディジごとに別々のPromise.map()を実行しますか? – cheese123211

+0

@ cheese123211 - あなたのコードは結果を処理するために何をしたいのかわからないので、そのタイプのコードを含めることはできませんし、結果を使って何をしようとしているのか知ることさえできません。すべての結果を得る方法と、一度に飛行中のリクエストの数を制御する方法を示しました。あなたは自分の結果の処理を処理できるはずです。 – jfriend00

+0

@ cheese123211 - はい、サブredditごとに結果を集めることができます。あなたの質問のコードは、あなたがその結果に何をしようとしているのかを示していないので、それに関連するコードを表示するために何もする必要はありません。 – jfriend00

0

問題は、多くの同時要求につながる、2つの入れ子ループと非スロットルrp()呼び出しから生じる:あなたが欲しいしかし、あなたは、結果のセットを照合することができます。アレイを低減することにより、例えばthen()チェーンを構築することにより

  • シリアル変換:

    スロットリングは、典型的にすることによって達成されます。

  • 例えばブルーバードのPromise.map()そのconcurrencyオプションとを使用して、「同時性」の制限を強制します。

私は、この特定の問題のために多数のアプローチが存在しなければならないと思いますが、基本的に:同時実行(jFriend00の答え)によってすべての要求やスロットル、

  • が一つのループが同期ままにし

    • プールそして
    • 巣シリアライゼーションでシリアライズ、
    • 巣同時実行中の同時実行、直列化または同時実行して他のを絞る
    • は、シリアライゼーションと同時実行の混合アプローチを採用しています。

    ここた、混合アプローチは次のとおり

    • 元アウターループは、元の内側ループが並行処理でブルーバードのマップで絞られるシリアライゼーション
    • によって絞られます。
    function getSubreddits(messageText) { 
        var user_words = parse_message(messageText); 
        return rp(dbUrl + '/.json?shallow=true').then(function(res) { 
         var subreddits = Object.keys(JSON.parse(res)); 
         return subreddits.reduce(function(p, subreddit) { 
          return p.then(function() { 
           return Promise.map(user_words, function(word) { 
            return rp(dbUrl + '/' + subreddit + '/word_freqs/' + word + '.json'); 
           }, {concurrency: 10}).then(function(freqs) { 
            // return an object that associates each subreddit with its results 
            return { 
             'subreddit': subreddit, // or maybe the object for which `subreddit` is the key? 
             'word_freqs': freqs 
            }; 
           }); 
          }); 
         }, Promise.resolve()); 
        }); 
    } 
    

    欠点は、あなたが平坦化に向いていない深くネストされた目障り、で終わるということです。他のアプローチのすべてではないにしてもほとんどが類似していると言われています。

    どちらのアプローチを採用しても、にはgetSubreddits()といくつかの結果の後処理が含まれています。

    function getBestSubreddit(messageText) { 
        return getSubreddits(messageText).then(function(results) { 
         // Here `results` is an array of `{'subreddit', 'word_freqs'}` objects. 
         // Loop through and calculate a score for each subreddit, 
         // then use that score to determine the top subreddit, 
         // and return it. 
        }).catch(function(error) { 
         console.log(error); 
        }); 
    } 
    
  • 関連する問題