我正在尝试与脚本并行获取大约7个网址:第一个在下面,使用HTTP :: Async,第二个是on pastebin,使用Net :: Async :: HTTP。
问题是我得到了相同的计时结果 - 所有网址列表大约8..14秒。与从shell开始的curl + xargs相比,这是不可接受的缓慢,使用10-20“线程”可以在不到3秒的时间内完成所有操作。
例如,第一个脚本中的Devel :: Timer显示最大队列长度甚至小于6($queue->in_progress_count
<= 5,$queue->to_send_count
= 0总是如此)。因此,它看起来像foreach with $ queue-&gt; add执行速度太慢,我不知道为什么。
我使用Net :: Async :: HTTP(在pastebin上的第二个脚本)获得了相同的情况,这比第一个更慢。
所以,请问,有人知道,我做错了什么?与从shell开始的curl + xargs相比,我怎样才能获得并发下载速度?
#!/usr/bin/perl -w
use utf8;
use strict;
use POSIX qw(ceil);
use XML::Simple;
use Data::Dumper;
use HTTP::Request;
use HTTP::Async;
use Time::HiRes qw(usleep time);
use Devel::Timer;
#settings
use constant passwd => 'ultramegahypapassword';
use constant agent => 'supa agent dev.alpha';
use constant timeout => 10;
use constant slots => 10;
use constant debug => 1;
my @qids;
my @xmlz;
my $queue = HTTP::Async->new(slots => slots,max_request_time => 10, timeout => timeout, poll_interval => 0.0001);
my %responses;
my @urlz = (
'http://testpodarki.afghanet/api/products/4577',
'http://testpodarki.afghanet/api/products/4653',
'http://testpodarki.afghanet/api/products/4652',
'http://testpodarki.afghanet/api/products/4571',
'http://testpodarki.afghanet/api/products/4572',
'http://testpodarki.afghanet/api/products/4666',
'http://testpodarki.afghanet/api/products/4576',
'http://testpodarki.afghanet/api/products/4574',
'http://testpodarki.afghanet/api/products/4651',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[3294]',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[3294]',
'http://testpodarki.afghanet/api/combinations/?display=full&filter[id_product]=[4577]',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[4577]',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[4577]',
'http://testpodarki.afghanet/api/product_option_values/188',
'http://testpodarki.afghanet/api/product_option_values/191',
'http://testpodarki.afghanet/api/product_option_values/187',
'http://testpodarki.afghanet/api/product_option_values/190',
'http://testpodarki.afghanet/api/product_option_values/189',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[4653]',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[4653]',
'http://testpodarki.afghanet/api/images/products/4577/12176',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[4652]',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[4652]',
'http://testpodarki.afghanet/api/images/products/4653/12390',
'http://testpodarki.afghanet/api/combinations/?display=full&filter[id_product]=[4571]',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[4571]',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[4571]',
'http://testpodarki.afghanet/api/images/products/4652/12388',
'http://testpodarki.afghanet/api/product_option_values/175',
'http://testpodarki.afghanet/api/product_option_values/178',
'http://testpodarki.afghanet/api/product_option_values/179',
'http://testpodarki.afghanet/api/product_option_values/180',
'http://testpodarki.afghanet/api/product_option_values/181',
'http://testpodarki.afghanet/api/images/products/3294/8965',
'http://testpodarki.afghanet/api/product_option_values/176',
'http://testpodarki.afghanet/api/product_option_values/177',
'http://testpodarki.afghanet/api/combinations/?display=full&filter[id_product]=[4572]',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[4572]',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[4572]',
'http://testpodarki.afghanet/api/product_option_values/176',
'http://testpodarki.afghanet/api/product_option_values/181',
'http://testpodarki.afghanet/api/product_option_values/180',
'http://testpodarki.afghanet/api/images/products/4571/12159',
'http://testpodarki.afghanet/api/product_option_values/177',
'http://testpodarki.afghanet/api/product_option_values/179',
'http://testpodarki.afghanet/api/product_option_values/175',
'http://testpodarki.afghanet/api/product_option_values/178',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[4666]',
'http://testpodarki.afghanet/api/combinations/?display=full&filter[id_product]=[4576]',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[4666]',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[4576]',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[4576]',
'http://testpodarki.afghanet/api/images/products/4572/12168',
'http://testpodarki.afghanet/api/product_option_values/185',
'http://testpodarki.afghanet/api/product_option_values/182',
'http://testpodarki.afghanet/api/product_option_values/184',
'http://testpodarki.afghanet/api/product_option_values/183',
'http://testpodarki.afghanet/api/product_option_values/186',
'http://testpodarki.afghanet/api/images/products/4666/12413',
'http://testpodarki.afghanet/api/combinations/?display=full&filter[id_product]=[4574]',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[4574]',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[4574]',
'http://testpodarki.afghanet/api/product_option_values/177',
'http://testpodarki.afghanet/api/product_option_values/181',
'http://testpodarki.afghanet/api/images/products/4576/12174',
'http://testpodarki.afghanet/api/product_option_values/176',
'http://testpodarki.afghanet/api/product_option_values/180',
'http://testpodarki.afghanet/api/product_option_values/179',
'http://testpodarki.afghanet/api/product_option_values/175',
'http://testpodarki.afghanet/api/product_option_values/178',
'http://testpodarki.afghanet/api/specific_prices/?display=full&filter[id_product]=[4651]',
'http://testpodarki.afghanet/api/images/products/4574/12171',
'http://testpodarki.afghanet/api/stock_availables/?display=full&filter[id_product]=[4651]',
'http://testpodarki.afghanet/api/images/products/4651/12387'
);
my $timer = Devel::Timer->new();
foreach my $el (@urlz) {
my $request = HTTP::Request->new(GET => $el);
$request->header(User_Agent => agent);
$request->authorization_basic(passwd,'');
push @qids,$queue->add($request);
$timer->mark("pushed [$el], to_send=".$queue->to_send_count().", to_return=".$queue->to_return_count().", in_progress=".$queue->in_progress_count());
}
$timer->mark('requestz pushed');
while ($queue->in_progress_count) {
usleep(2000);
$queue->poke();
}
$timer->mark('requestz complited');
process_responses();
$timer->mark('responzez processed');
foreach my $q (@xmlz) {
# print ">>>>>>".Dumper($q)."<<<<<<<<\n";
}
$timer->report();
print "\n\n";
答案 0 :(得分:6)
我HTTP::Async的最佳结果是超过4秒,最多超过5秒。据我所知,这种方法不是必需的,这是一个简单的分叉示例,需要2点多一点,最多3秒钟。
它使用Parallel::ForkManager和LWP::UserAgent进行下载。
use warnings;
use strict;
use Path::Tiny;
use LWP::UserAgent;
use Parallel::ForkManager;
my @urls = @{ get_urls('https://pastebin.com/raw/VyhMEB3w') };
my $pm = new Parallel::ForkManager(60); # max of 60 processes at a time
my $ua = LWP::UserAgent->new;
print "Downloading ", scalar @urls, " files.\n";
my $dir = 'downloaded_files/';
mkdir $dir if not -d $dir;
my $cnt = 0;
foreach my $link (@urls)
{
my $file = "$dir/file_" . ++$cnt . '.txt';
$pm->start and next; # child process
# add code needed for actual pages (authorization etc)
my $response = $ua->get($link);
if ($response->is_success) {
path($file)->spew_utf8($response->decoded_content);
}
else { warn $response->status_line }
$pm->finish; # child exit
}
$pm->wait_all_children;
sub get_urls {
my $resp = LWP::UserAgent->new->get($_[0]);
return [ grep /^http:/, split /\s*'?,?\s*\n\s*'?/, $resp->decoded_content ];
};
使用Path::Tiny编写文件。其path
构建一个对象,spew
例程编写该文件。
作为参考,顺序下载大约需要26秒。
当最大进程数设置为30时,这需要4秒,而60则超过2秒,大约与(最多)90相同。此测试中有70个URL。
在具有良好网络连接的4核笔记本电脑上进行测试。 (这里的CPU并不是那么重要。)测试反复运行,多次,多天。
与问题
中的方法进行比较最佳HTTP::Async
结果比上述慢约两倍。他们有30-40个“插槽”,因为数字越多,时间越长,谜题(我)。该模块使用select
通过Net::HTTP::NB(Net::HTTP的非阻止版本)进行多路复用。虽然select
“不能很好地扩展”,但这涉及数百个套接字,我希望能够在这个网络绑定问题上使用超过40个。简单的分叉方法。
此外,select
被认为是一种监视套接字的慢速方法,而fork甚至不需要,因为每个进程都有自己的url。 (当有许多连接时,这可能导致模块的开销?)Fork的固有开销是固定的,并且由于网络访问而相形见绌。如果我们在(很多)数百次下载后,系统可能会因进程而变得紧张,但select
也不会很好。
最后,基于select
的方法一次严格下载一个文件,
并且当请求被add
时打印看到效果 - 我们可以看延迟。分叉下载并行(在这种情况下,所有70个同时没有问题)。然后会出现网络或磁盘瓶颈,但与增益相比,这个瓶颈很小。
更新:我将此推送到站点和进程数量的两倍,没有出现OS / CPU压力的迹象,并保持平均速度。
所以我想说,如果你需要剃掉每一秒使用的叉子。但是,如果这不是至关重要的,HTTP::Async
(或其他)有其他好处,那么请满足(稍微)一点点下载。
表现良好的HTTP::Async
代码最终只是
foreach my $link ( @urls ) {
$async->add( HTTP::Request->new(GET => $link) );
}
while ( my $response = $async->wait_for_next_response ) {
# write file (or process otherwise)
}
我也尝试调整标题和时间。 (这包括按照keep-alive
的建议放弃$request->header(Connection => 'close')
,不起作用。)
答案 1 :(得分:4)
解释我的评论。我很好奇,因为以前从未使用Net::Async::HTTP
,想在本地尝试你的脚本。因此,创建了这个极简主义的Plack app.psgi
:
use 5.014;
use warnings;
use Plack::Builder;
use Plack::Request;
use Plack::Response;
use Time::HiRes qw(usleep);
my $app = sub {
my $env = shift;
my $req = Plack::Request->new($env);
my($ms,$id) = $req->path_info =~ /(\d+)/g;
$ms //= 0;
$id //= "NOID";
#fake some processing slowness
usleep($ms);
my $res = Plack::Response->new(200);
$res->content_type('text/plain');
$res->body( "req# $id served by PID $$ fakewait: $ms\n");
return $res->finalize;
};
builder {
# enable "Auth::Basic", authenticator => \&auth;
$app;
};
#sub auth { return $_[0] eq 'me' && $_[1] eq 'me' }
服务器以GET /sleep_time/reqID
格式了解网址,其中
usleep
的休眠时间以微秒为单位 - 服务器在响应之前休眠给定时间。例如。它假装了一些“处理时间”。 E.g。请求GET /1000000/1
,服务器将在响应之前休眠1秒。作为回应,包括响应过程的PID
。
在一个终端窗口中,使用Starman
preforkimg服务器和默认20
工作人员运行上述内容。
plackup -s Starman
在另一个窗口中,结果使用xargs
:
time seq 20 | xargs -n1 -P10 -I% curl http://localhost:5000/1000000/%
所以,发送20个请求,每个响应花费1s
个处理时间。
req# 1 served by PID 28163 fakewait: 1000000
req# 2 served by PID 28161 fakewait: 1000000
req# 3 served by PID 28162 fakewait: 1000000
req# 4 served by PID 28160 fakewait: 1000000
req# 5 served by PID 28159 fakewait: 1000000
...
real 0m4,032s
user 0m0,092s
sys 0m0,074s
所以,20个请求= 4秒。可见,响应PID
是不同的 - 例如,repsonse由不同的工作人员发送。
现在使用您的脚本async.pl
(稍微缩短/修改):
#!/usr/bin/perl
use 5.014;
use warnings;
use HTTP::Request;
use IO::Async::Loop;
use Net::Async::HTTP;
use Future::Utils qw(fmap_void);
my $sleep = $ARGV[0] // 0;
my $numreq = $ARGV[1] // 20;
my $loop = IO::Async::Loop->new();
my $http = Net::Async::HTTP->new( timeout => 10, max_connections_per_host => 0, pipeline => 0, ip_tos => 0x10 );
$loop->add( $http );
my $future = fmap_void {
(my $url ) = @_;
my $request = HTTP::Request->new(GET => $url);
#$request->authorization_basic('me','me');
$http->do_request( request => $request )
->on_done( sub {
my $response = shift;
my($body) = $response->content =~ s/\n.*//sr;
print "$url [", $response->code, "] --> $body\n";
} )
->on_fail( sub {
my $failure = shift;
print "$url failed: $failure\n";
} );
} foreach => [map { "http://localhost:5000/$sleep/$_" } (1 .. $numreq)];
$loop->await( $future );
命令
time perl async.pl 1000000 20
结果
http://localhost:5000/1000000/1 [200] --> req# 1 served by PID 28160 fakewait: 1000000
http://localhost:5000/1000000/2 [200] --> req# 2 served by PID 28160 fakewait: 1000000
http://localhost:5000/1000000/3 [200] --> req# 3 served by PID 28160 fakewait: 1000000
http://localhost:5000/1000000/4 [200] --> req# 4 served by PID 28160 fakewait: 1000000
http://localhost:5000/1000000/5 [200] --> req# 5 served by PID 28160 fakewait: 1000000
http://localhost:5000/1000000/6 [200] --> req# 6 served by PID 28160 fakewait: 1000000
...
real 0m20,309s
user 0m0,183s
sys 0m0,053s
相同的20个请求= 20秒,每个请求由相同的PID
提供。像纯顺序处理一样。 :(
这个可能是,因为请求重用了连接(例如,保持活动状态)。
最后 - 不幸的是,正如我所说 - 我对模块没有任何经验,所以不知道如何强制模块做 - 不重用已打开的连接。
答案 2 :(得分:4)
所以,最后得到了工作样本(full script)。它使用Furl
中的fork_call
和AnyEvent::Util
。这个例子在~3秒内返回,这还不错。如果您需要使用基本的HTTP身份验证,只需使用带有以下信头的URI:username:password@hostdomain.org/path?param1=val1¶m2=val2
。您最好在使用use EV;
之前添加AnyEvent
,因为EV是最快的。
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
use Devel::Timer;
use Furl;
use EV;
use AnyEvent;
use AnyEvent::Util 'fork_call';
#get full script
my @urls = (...);
sub fetch {
my $url = shift;
my $furl = Furl::HTTP->new(agent => 'Furl/0.31',timeout => 3);
print "start $url\n";
my ($ver, $code, $msg, $headers, $body) = $furl->get($url);
my $size = length $body;
print "finished $url, $size bytes\n";
return ($code, $msg, $headers, $body);
}
my %resps;
my $timer = Devel::Timer->new();
$timer->mark('foreach');
$AnyEvent::Util::MAX_FORKS = 20;
my $cv = AE::cv;
foreach my $url (@urls) {
$timer->mark('next foreach');
$cv->begin;
fork_call {
print "getting $url... ";
my ($code, $msg, $headers, $body)=fetch($url);
print "[$code]\n";
return ($url, $code, $msg, $headers, $body);
}
sub {
print "adding 2 %resps\n";
my ($url, $code, $msg, $headers, $body)=@_;
$resps{$url}->{'code'}=$code;
$resps{$url}->{'msg'}=$msg;
$resps{$url}->{'headers'}=$headers;
$resps{$url}->{'body'}=$body;
$cv->end;
};
}
$cv->recv;
$timer->mark('end');
print "\nall data is ready, press <ENTER>:";
<STDIN>;
print Dumper(%resps);
print "\n<PRESS ENTER>to print timer report\n";
<STDIN>;
$timer->report();
sleep(3);
答案 3 :(得分:2)
异步将比并行下载慢:异步代码只会在等待响应时产生其他调用,但是在单个进程中按顺序进行下载,而curl + xargs将100%工作(好,几乎100%) ,并且只要你没有使核心饱和)并行,就像使用分叉工人时一样。
请pogle,请注意“并发不是并行”