在快速流读取流中平衡慢速I / O.

时间:2013-09-08 18:30:22

标签: node.js asynchronous stream

在node.js中,我有一个读取流,我希望重新格式化并写入数据库。由于读取流速度很快且写入速度很慢,因此当写入队列建立起来时,node.js队列可能会被淹没(假设流是gb的数据)。如何强制读取等待代码的写入部分,这样不会在没有阻塞的情况下发生?

var request = http.get({
      host: 'api.geonames.org',
      port: 80,
      path: '/children?' + qs.stringify({
      geonameId: geonameId,
      username: "demo"
   })
}).on('response', function(response) {
   response.setEncoding('utf8');
   var xml = new XmlStream(response, 'utf8');

   xml.on('endElement: geoname ', function(input) {  
      console.log('geoname');
      var output = new Object();
      output.Name = input.name;
      output.lat = input.lat;
      output.lng = input.lng;
      output._key = input.geonameId;
      data.db.document.create(output, data.doc, function(callback){    
         //this is really slow.
      }
      // i do not want to return from here and receive more data until the 'create' above has completed
   });  
});

2 个答案:

答案 0 :(得分:3)

我昨晚遇到了这个问题,在我的黑客马拉松诱导睡眠不足状态下,我就是这样解决的:

每当我发出一个要处理的作业时,我会递增一个计数器,并在操作完成时递减计数器。为了防止出站流量压倒其他服务,我会在有一定数量的待处理出站请求时暂停流。代码与以下内容非常相似。

var instream = fs.createReadStream('./combined.csv');
var outstream = new stream;
var inProcess = 0;
var paused = false;
var rl = readline.createInterface(instream, outstream);
rl.on('line', function(line) {
    inProcess++;
    if(inProcess > 100) {
        console.log('pausing input to clear queue');
        rl.pause();
        paused = true;
    }

    someService.doSomethingSlow(line, function() {
        inProcess--;
        if(paused && inProcess < 10) {
            console.log('resuming stream');
            paused = false;
            rl.resume();
        }

        if (err) throw err;
    });
});

rl.on('end', function() {
    rl.close();
});

不是最优雅的解决方案,但它起作用并允许我处理数百万行而不会耗尽内存或限制其他服务。

答案 1 :(得分:0)

我的解决方案只是扩展了一个空的stream.Writable,与@Timothy的基本相同,但是使用了事件和 不依赖于Streams1 .pause().resume()(这似乎对我的数据管道没有任何影响, 反正)。

var stream = require("stream");

var liveRequests = 0;
var maxLiveRequests = 100;
var streamPaused = false;

var requestClient = new stream.Writable();

function requestCompleted(){
    liveRequests--;
    if(streamPaused && liveRequests < maxLiveRequests){
        streamPaused = false;
        requestClient.emit("resumeStream");
    }
}

requestClient._write = function (data, enc, next){
    makeRequest(data, requestCompleted);
    liveRequests++;

    if(liveRequests >= maxLiveRequests){
        streamPaused = true;
        requestClient.once("resumeStream", function resume(){
            next();
        });
    }
    else {
        next();
    }
};

计数器liveRequests跟踪并发请求的数量,并随时递增 makeRequest()在完成时调用并递减(即,requestCompleted())。如果请求有 刚刚制作完成且liveRequests超过maxLiveRequests,我们会使用streamPaused暂停该流。如果有请求 完成后,流暂停,liveRequests现在小于maxLiveRequests,我们可以恢复流。以来 _write()调用其next()回调时,后续数据项会被读取,我们可以简单地推迟后者 我们的自定义"resumeStream"事件的事件监听器,它模仿暂停/恢复。 现在,只需readStream.pipe(requestClient)


编辑:我在package中抽象了这个解决方案以及输入数据的自动批处理。