NodeJS+request-promise同时请求太多

问题描述

我有一个包含 URL 的大数组(大约 9000 个元素)的 NodeJS 项目。将使用 request-promise 包请求这些 URL。但是,服务器或客户端都不喜欢从同一客户端对同一网站的 9000 个并发 GET 请求,因此我想随着时间的推移将它们分散开。我环顾四周,发现 Promise.map{concurrency: int} 选项 here 一起,听起来像我想要的那样。但我无法让它工作。我的代码如下所示:

const rp = require('request-promise');
var MongoClient = require('mongodb').MongoClient;
var URLarray = []; //This contains 9000 URLs

function getWebsite(url) {
  rp(url)
  .then(html => { /* Do some stuff */ })
  .catch(err => { console.log(err) });
}

MongoClient.connect('mongodb://localhost:27017/some-database',function (err,client) {
  Promise.map(URLArray,(url) => {
    db.collection("some-collection").findOne({URL: url},(err,data) => {
      if (err) throw err;
      
      getWebsite(url,(result) => {
        if(result != null) {
          console.log(result);
        }
      });
      
    },{concurrency: 1});
});

我想我可能误解了如何处理承诺。在这种情况下,我会认为,将并发选项设置为 1,数组中的每个 URL 将依次用于数据库搜索,然后作为参数传递给 getWebsite,其结果将显示在它的回调函数。然后将处理数组中的下一个元素。

实际发生的情况是一些(可能是 10 个)URL 被正确获取,然后服务器开始以 500 个内部服务器错误进行零星响应。几秒钟后,我的电脑死机,然后重新启动(我猜是因为某种恐慌?)。

我该如何解决这个问题?

解决方法

如果问题确实与并发有关,您可以将工作分成块并将这些块链接起来。

让我们从一个执行 mongo 查找和获取的函数开始......

// answer a promise that resolves to data from mongo and a get from the web
// for a given a url,return { mongoResult,webResult }
// (assuming this is what OP wants. the OP appears to discard the mongo result)
//
function lookupAndGet(url) {
  // use the promise-returning variant of findOne
  let result = {}
  return db.collection("some-collection").findOne({URL: url}).then(mongoData => {
    result.mongoData = mongoData
    return rp(url) 
  }).then(webData => {
    result.webData = webData
    return result
  })
}

lodashunderscore 都提供了一种块方法,可以将数组分解为更小的数组。编写您自己的或使用他们的。

const _ = require('lodash')
let chunks = _.chunk(URLArray,5)  // say 5 is a reasonable concurrency

这就是答案的重点,制作一个块链,这样你就只能同时执行较小的块...

let chain = chunks.reduce((acc,chunk) => {
  const chunkPromise = Promise.all(chunk.map(url => lookupAndGet(url)))
  return acc.then(chunkPromise)
},Promise.resolve())

现在执行链。块承诺将返回块大小的结果数组,因此您的缩减结果将是一个数组数组。幸运的是,lodash 和 underscore 都有一个方法来“展平”嵌套数组。

// turn [ url,url,...] into [ { mongoResult,webResult },{ mongoResult,...]
// running only 5 requests at a time
chain.then(result => {
  console.log(_.flatten(result))
})