2017-04-01 32 views
0

我目前正在嘗試編寫一個函數,該函數接受用戶輸入,並根據訓練數據中出現該單詞的次數返回用戶評論可能屬於的最高subreddit 。我有一個數據庫,其中包含不同subreddits中的單詞頻率,我正在爲每個subreddit和用戶輸入中的每個單詞發出GET請求。請求承諾導致打開太多套接字

這增加了很多獲取請求,因爲我的數據庫中有7000多個子資源。我目前正在提出請求承諾(淺層)請求以獲取所有子級別的列表。然後,對於每個subreddit,我循環遍歷用戶輸入中的每個單詞,創建另一個請求承諾對象並將其添加到承諾數組中。

一旦添加了所有請求承諾對象,我就等到它們全都使用Promise.all解析,然後我嘗試打印給定的subreddit的詞頻數組,但是我得到一個'錯誤:連接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

不像你的標題提出,問題不是由'request-promise'造成的。這個問題是由於你的兩個嵌套循環試圖並行執行大量請求而不是讓你的代碼控制同時有多少個正在運行的。 – jfriend00

+0

一些相關的答案:[如何使從nodejs應用程序的數百萬並行http請求?](http://stackoverflow.com/questions/38268371/how-to-make-millions-of-parallel-http-requests-from- nodejs-app/38272107#38272107)和[在Node js中。我可以使用「請求」包發送多少個同步請求](http://stackoverflow.com/questions/36611890/in-node-js-how-many-simultaneous-requests-can-i-send-with-the -request-package/36612175#36612175)和[Making a million requests](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.

不,這是不正確的。您有兩個嵌套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個異步同時在飛行中的操作。由於您的代碼是基於承諾的,因此我會選擇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

謝謝!我認爲您提供的代碼會返回每個subreddit中每個頻率的列表,因爲它會將每個可能的URL添加到urls數組,然後使用Promise.map()將併發數一次限制爲最多20個。 是否有任何方法可以獲得每個subreddit的單詞頻率列表?我想爲每個subreddit計算一個分數,然後使用該分數來確定頂級子分幣。爲此,我會爲每個單獨的subreddit單獨做一個Promise.map()嗎? – cheese123211

+0

@ cheese123211 - 你的代碼沒有顯示你想要做什麼來處理結果,所以我不能包含這種類型的代碼,甚至不知道你想要做什麼結果。我向您展示瞭如何獲得所有結果以及如何控制一次有多少個請求正在進行中。您應該能夠處理您自己的結果處理。 – jfriend00

+0

@ cheese123211 - 是的,你可以累積每個子分享的結果。你的問題中的代碼沒有顯示你正在對結果做什麼,所以我沒有任何東西可以顯示與之相關的代碼。 – jfriend00

0

的問題來自兩個嵌套的循環和無節流的rp()電話,導致許多同時請求:那麼你可以,但是你想整理這組結果。

  • 連載通過構建then()鏈,例如通過降低的數組​​:

    節流通常通過實現。

  • 強制執行「併發」限制,例如使用Bluebird的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()); 
    }); 
} 

一個缺點是,你最終有一個深層嵌套的眼中釘,不借給自己壓扁。也就是說,如果不是所有其他方法,大部分都是相似的。

無論採用哪種方法,getBestSubreddit()現在都會包含對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); 
    }); 
}