V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
morefreeze
V2EX  ›  Node.js

[求助]nodejs 中的 request 设置 timeout 问题

  •  
  •   morefreeze · 2015-09-29 20:10:26 +08:00 · 14060 次点击
    这是一个创建于 3349 天前的主题,其中的信息可能已经有所发展或是发生改变。

    request 文档

    我想用 request 做一个类似爬虫程序,需要读一个 urllist (用 linebyline 按行读取),然后请求这些 url ,其中有可能卡住,一直没有返回,所以我设置了 timeout 为 3 秒,并加了重试(我知道有 request-retry ,但它用的也是 request )。

    整个程序执行过程应该是这样
    1. 读取每行
    2. 并开始请求
    3. 读取下一行,跳到 1

    但我发现在读取行的时候,请求并没有发出去,而是占用了 timeout 的时间,证据就是,当 10 个请求时,很快就全处理完了,当 100 个请求时,有几个请求超时,当 500 个时,大部分都超时了,假如一个请求花 0.1 秒,那执行完 30 个请求后, request 就直接超时了。

    矛盾来了,我必须设置超时,如果小了,太多的超时,如果大了,浪费时间。请问这种应该怎么改呢?我只想安安静静地发个请求啊

    以下是一个 demo 代码

    var request = require('request');
    var readline = require('linebyline');
    rl = readline(process.stdin);
    var res_arr = [];
    function get_url(url){
        return new Promise(function(resolve, reject) {
            var l_options = {'url':url};
            l_options.timeout = 3 * 1000;
            var st_time = Date.now();
            console.error('start request');
            request(l_options, function(error, response, body) {
                console.error('cost '+(Date.now()-st_time)/1000);
                if (error) {
                    console.error(' ' +l_options.url + ' ' + error);
                    return ;
                }
                resolve(body);
            });
        });
    }
    rl.on('line', function(line, lineCount, byteCount){
        var k = lineCount - 1;
            get_url(line).then(function(str){
                console.log("body "+str.length);
            },
            function(str){
                console.error("failed "+str);
            });
    });
    

    像这样执行以上代码cat url.txt | node test.js,会发现先是输出了一坨"start request"(中间没有任何其他输出),然后开始打印请求的时间,而且可以看出时间越来越大(但不是单调递增的,这点很怪)

    17 条回复    2015-09-30 14:45:16 +08:00
    breeswish
        1
    breeswish  
       2015-09-29 20:27:54 +08:00   ❤️ 1
    使用 async 库:

    var queue = async.queue(function (url, callback) {
    request({url: url, timeout: 3000}, function (err, res, body) {
    callback(); // 告诉 async 任务完成
    });
    }, 30); // 并发 30

    rl.on('line', function (line) {
    queue.push(line);
    });
    breeswish
        2
    breeswish  
       2015-09-29 20:32:56 +08:00
    如果希望一次只请求一个,将 30 改成 1 即可

    你的预期是一次发起一个请求?

    1. 读取第一行
    2. 开始请求第一行
    3. 当请求完毕后,读取第二行
    4. 开始请求第二行
    ...

    然而实际情况是同时发起了 n 个请求:

    1. 读取第一行,开始请求第一行
    2. 读取第二行,开始请求第二行
    3. 读取第三行,开始请求第三行
    ...
    100. 读取第 100 行,开始请求第 100 行
    101. 第 x 行的请求返回
    102. 第 y 行的请求返回
    ....
    gzlock
        3
    gzlock  
       2015-09-29 21:40:30 +08:00
    @breeswish 现在有采集需求,当采集过程中发现新链接,要提交给任务队列
    我用 child_process 实现了任务队列和进程池,跟 async 相比,该用哪个呢?
    breeswish
        4
    breeswish  
       2015-09-29 22:18:26 +08:00
    @gzlock async 的 queue 是动态的
    gzlock
        5
    gzlock  
       2015-09-29 22:42:53 +08:00
    @breeswish 我做的任务队列也是动态的, child_process 可以通过 process.send({type:'newMission',url:'aaaa.com/?a=2'})发送新任务给主进程,由主进程添加到任务队列
    gzlock
        6
    gzlock  
       2015-09-29 22:48:55 +08:00
    @gzlock 看来还是要试试 async 才能下决定了
    breeswish
        7
    breeswish  
       2015-09-29 23:47:07 +08:00
    @gzlock 如果你已经造好轮子的话么你自己决定咯…反正 async 做流程控制是现成的库, async 可以充分发挥 Node.js 异步并发特性。你这么玩是传统的单线程思路,问题不大,没发挥 Node.js 优势而已
    gzlock
        8
    gzlock  
       2015-09-30 00:52:02 +08:00
    @breeswish 主要是担忧 nodejs 单进程的异步性能,是否可以发挥出 cpu 的多线程计算能力?
    gzlock
        9
    gzlock  
       2015-09-30 00:52:42 +08:00
    @breeswish 当然更主要造轮子前不知道有 async 这个库
    ysmood
        10
    ysmood  
       2015-09-30 03:05:53 +08:00
    我之前写爬虫都是用 Promise 控制流,比 async 要灵活多了,配合 ES7 的 async-await 语法直接甩 async 一条街。可以试试这个库 https://github.com/ysmood/yaku#asynclimit-list-saveresults-progress
    ysmood
        11
    ysmood  
       2015-09-30 03:10:15 +08:00
    https://github.com/ysmood/nokit/blob/master/examples/threadPool.coffee 这是我写的一个使用上面提到函数的示例,典型的 producer broker consumer 模型,会无穷无尽的爬下去。
    magicyu1986
        12
    magicyu1986  
       2015-09-30 09:23:21 +08:00
    最好用一个信号量来控制请求速度,不然瞬间发一大堆请求,失败率肯定会增高.
    morefreeze
        13
    morefreeze  
    OP
       2015-09-30 11:29:41 +08:00
    @breeswish 感谢提出的 async 的库
    我在用的时候,发现在 rl.on('line')时, push 到 queue 里,但我如果在最前面定义 queue.drain 却并没有出现完成的情况 这是为什么?
    breeswish
        14
    breeswish  
       2015-09-30 13:18:12 +08:00
    @morefreeze 当队列空且任务完成后才会触发 drain. 你看看是不是没有调用 callback()

    例如对于以下代码:

    https://gist.github.com/SummerWish/da6d5980737a411f4e3d

    应当在 4500ms 后输出 drain :
    500ms: 添加了两个任务(并发是 1 )
    2500ms: 第一个任务完成,开始第二个任务
    4500ms: 第二个任务完成, drain
    morefreeze
        15
    morefreeze  
    OP
       2015-09-30 14:03:46 +08:00
    @breeswish 是因为我传了 callback 加了参数。
    所以这个 callback 是有什么用呢,如果无法加参数的话
    breeswish
        16
    breeswish  
       2015-09-30 14:26:50 +08:00
    @morefreeze 第一个参数是 err ;对于 async 其他某些模型比如 waterfall 等还可以传第二个参数作为 data 。

    > worker(task, callback) - An asynchronous function for processing a queued task, which must call its callback(err) argument when finished, with an optional error as an argument. If you want to handle errors from an individual task, pass a callback to q.push().

    `push` 本身可以接受一个任务完成的回调

    不过 `err` 应该是不影响整体流程的..
    morefreeze
        17
    morefreeze  
    OP
       2015-09-30 14:45:16 +08:00
    @breeswish 我找到原因了,因为我有句判断如果出错就直接 return 没有调用 callback ,所以没触发 drain ,另外 callback 参数如你所说,是可以正常处理的
    多谢指导
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2812 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 1082ms · UTC 04:01 · PVG 12:01 · LAX 20:01 · JFK 23:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.