[求助]nodejs 中的 request 设置 timeout 问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
morefreeze
V2EX    Node.js

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

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

    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     Solana     1015 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 22:54 PVG 06:54 LAX 15:54 JFK 18:54
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86