【问题标题】:Node.js : How to pause/resume a for loopNode.js:如何暂停/恢复 for 循环
【发布时间】:2015-12-04 11:22:26
【问题描述】:

我正在运行一个 for 循环来生成所有可能的 IPv4 地址,然后在 maxmind 的 mmdb 文件中查找它们。问题是,虽然 for 循环太快,但 maxmind 查找相对非常慢,因此进程变慢,最终我的系统在大约 400k 次迭代后冻结。如果我正在从某个流中读取数据,我会在读取每 10k 个 IP 地址后暂停该流,并且只有在从 mmdb 文件中查找完所有这些地址后才会恢复。但是我怎样才能对 for 循环进行这样的控制呢?

connectToMaxMindDatabases().then(function (done){
    for(var i=0; i<256; i++){
        for(var j=0; j<256; j++){
            for(var k=0; k<256; k++){
                for(var l=0; l<256; l++){
                    count++;
                    if(count % 1000 == 0){
                        console.log("count", count);
                    }
                    var newIP = getIPV4([i,j,k,l])
                    ispDB.getGeoDataAsync(newIP).then(function (result){
                        if(result){
                            console.log(count, newIP, result);
                            // process.exit();
                        }
                    });
                }
            }
        }
    }       
})


function getIPV4(bytes){
    return bytes.join(".")
}

【问题讨论】:

  • 请注意,Node.js 有一个非阻塞的理念,所以我认为停止循环可能不是一个好的选择。也许某种排队?
  • 您是否知道代码示例中的缺陷是因为getGeoDataAsync 是异步的,所以当它的结果返回时count 将增加一个不可预测的数量?不确定它是否与您的逻辑相关,但肯定是您忽略了。
  • @marekful 我明白你的意思。但是,这不会导致任何问题,因为我需要处理所有的 IP 地址。如果不是 for 循环,而是一个文件,这不会引起任何问题,因为我会在读取每 1k 条记录后暂停流,并且仅在 maxmind 处理该数量的记录时才恢复。我会为此保留一个单独的柜台。但我同意亚当的观点,暂停 for 循环将是灾难性的。我正在考虑先将所有排列写入文件。
  • 是的,我明白这个问题。它的根源肯定在底层 OS/Node 服务器的资源管理中。具有这种迭代次数的 for 循环将在该特定硬件上冻结,而在更强大的计算机上它可能只会稍后冻结或根本不冻结。解决方案是先将所有 IP 地址收集到一个数组中,然后以一次只处理一个段的方式处理它们,等到完成后再移动到下一个段。
  • 你应该使用mmdb-reader,它有一个相当快的同步接口。

标签: node.js maxmind


【解决方案1】:

我不确定,但 process.nextTick 可能会有所帮助 https://nodejs.org/api/process.html#process_process_nexttick_callback_arg

我真的不确定,但可能会使用以下代码:

connectToMaxMindDatabases().then(function (done){
    for(var i=0; i<256; i++){
        process.nextTick(function(){
            for(var j=0; j<256; j++){
                process.nextTick(function(){
                    for(var k=0; k<256; k++){
                        process.nextTick(function(){
                            for(var l=0; l<256; l++){
                                process.nextTick(function(){
                                    count++;
                                    if(count % 1000 == 0){
                                        console.log("count", count);
                                    }
                                    var newIP = getIPV4([i,j,k,l])
                                    ispDB.getGeoDataAsync(newIP).then(function (result){
                                        if(result){
                                            console.log(count, newIP, result);
                                            // process.exit();
                                        }
                                    });
                                }
                            }
                        })
                    }
                }
            }
        }
    }       
})

据我所知,情况如下: 所有的 for 循环都在同步运行,异步 ispDB.getGeoDataAsync(newIP) 调用正在排队,但它的回调(.then 函数)无法执行,因为事件循环被 4 个 for 循环阻塞。

【讨论】:

  • 感谢@manish。但是,现在我又想了想,我会经常使用这个列表,所以将它存储在一个文件中然后从那里读取可能不是一个糟糕的主意
  • @mandeep_m91 听起来您正在重新实现自己的 IP 查找系统?另外,我似乎记得 Maxmind EULA 禁止这些类型的衍生作品,因此请确保您所做的事情是合法的 :-)
【解决方案2】:

问题的根源肯定在于底层 OS/Node 服务器的资源管理。具有这种迭代次数的 for 循环将在该特定硬件上冻结,而在更强大的计算机上它可能只会稍后冻结或根本不冻结。解决方案是先将所有 IP 地址收集到一个数组中,然后以一次只处理一个段的方式处理它们,等到完成并移动到下一个段。

通过这种细分和 Bluebird 的Promse.each 可以实现一个可能的解决方案。

如果迭代器函数返回一个 promise 或 thenable,那么 等待承诺的结果,然后继续下一步 迭代。

var Promise = require('bluebird'),
    _ = require('lodash'),
    allIPs = [];

connectToMaxMindDatabases().then(function (done){
    for(var i=0; i<256; i++){
        for(var j=0; j<256; j++){
            for(var k=0; k<256; k++){
                for(var l=0; l<256; l++){
                    count++;
                    if(count % 1000 == 0){
                        console.log("count", count);
                    }
                    var newIP = getIPV4([i,j,k,l])
                    allIPs.push(newIP);
                }
            }
        }
    }       
    return allIPs;
})
.then(function(allIPs) {
    // Create an array of arrays of IPs, each inner array with 1000 IPs
    var from = 0, to = 1000, segments = [], promises;

    do {
        segments.push(allIPs.slice(from, to));
        from += 1000; to += 1000;
    } while(to < allIPs.lenght);

    // Process a segment of 1000 IPs and wait before moving the next
    // segment until this segment is fully resolved.
    Promise.each(segments, function(segmentArr) {
        promises = [];
        _.forOwn(segmentArr, function(IP) {
            promises.push(ispDB.getGeoDataAsync(IP)
              .then(function(result) {
                  // save the result
              })
            );
        });
        return Primse.all(promises);
    });
});

当然,如果您的情况下的资源受到限制,allIPs 数组甚至无法生成,因为它在此之前就开始冻结,这不会解决问题,但是资源很可能被所有这些人吃光了getGeoDataAsync 在这种情况下调用应该会有所帮助。

【讨论】:

  • “首先收集一个数组中的所有 IP 地址”听起来像是一个需要内存的想法
  • 确实如此。因此,IP 地址的提取也应移至分段阶段,因此不会尝试将所有 IP 地址一次收集到分段中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-29
  • 2020-04-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多