8

我在 MySQL 数据库表中有 1000 个提要 URL。我需要每 2 分钟对所有这些 url 进行一次 http 请求。我编写了一个 php 脚本来执行此操作,但该脚本需要 5 分钟 30 秒才能运行。

我希望能够在一分钟内完成所有 1000 个请求。有没有办法运行多个异步进程来更快地完成工作?任何帮助表示赞赏。提前致谢。

4

7 回答 7

7

由于您的问题是关于发送 http 请求,而不是真正的 ping,因此您可以使用GrequestsRequests + gevent轻松快速地完成此操作(根据我的经验,数百个 url 请求只需几秒钟)

import grequests

urls = [
    'http://www.python.org',
    'http://python-requests.org',
    'http://www.google.com',
]
rs = (grequests.get(u) for u in urls)
grequests.map(rs)    # [<Response [200]>, <Response [200]>, <Response [200]>]

您的 PHP 脚本需要 5 分钟才能运行,因为它是同步代码,这意味着对于您发送的每个请求,您必须等待响应到达,然后才能继续发送下一个请求。

这里的技巧不是等待(或阻止许多人会调用)响应,而是直接发出下一个请求,您可以使用gevent(基于协程的)或nodejs. 你可以在这里阅读更多。

于 2012-10-23T01:04:20.893 回答
4

看看CPANAnyEvent::Ping上的orAnyEvent::FastPing模块。

下面是使用AnyEvent::Pingping 10000 url 的简单示例:

use strict;
use warnings;
use AnyEvent;
use AnyEvent::Ping;

my $cv   = AnyEvent->condvar;
my $ping = AnyEvent::Ping->new;
my @results;

for my $url (get_ten_thousand_urls()) {
    $cv->begin;

    # ping each URL just once
    $ping->ping( $url, 1, sub {
        # [ url address, ping status ]
        push @results, [ $url, $_[0]->[0][0] ];
        $cv->end;
    });
}

$cv->recv;

# now do something with @results

以上使用 10,000 个随机URL 进行的一些快速测试在我的 Macbook Air 上运行只需 7 秒多一点。通过调整和/或使用更快的事件循环,这个时间将进一步下降(高于使用默认的纯 Perl 事件循环)。

注意。AnyEvent是一个抽象库,它允许您使用系统提供(或安装在)系统上的异步事件系统。如果您想使用特定的事件循环,请记住从 CPAN 安装相关的 Perl 模块,例如,EV如果使用libev. AnyEvent如果没有找到(安装)其他内容,将默认为纯 Perl 事件循环。

顺便说一句,如果您只需要检查 HTTP 请求(即不 ping),那么只需将AnyEvent::Ping部分替换为AnyEvent::HTTP.

于 2012-10-23T15:18:48.380 回答
3

你用“python”标记了这个,所以我假设在这里使用 Python 是一个选项。查看多处理模块。例如:

#!/usr/bin/env python

import multiprocessing
import os
import requests
import subprocess

addresses = ['1.2.3.4', '1.2.3.5', '4.2.2.1', '8.8.8.8']
null = open(os.devnull, 'w')

def fbstatus(count):
    """Returns the address, and True if the ping returned in under 5 seconds or
    else False"""
    return (count,
        requests.get('http://www.facebook.com/status.php').status_code)

def ping(address):
    """Returns the address, and True if the ping returned in under 5 seconds or
    else False"""
    return address, not subprocess.call(['ping', '-c1', '-W5', address],
        stdout=null)

pool = multiprocessing.Pool(15)
if False:
    print pool.map(ping, addresses)
else:
    pool.map(fbstatus, range(1000))

新 - 获取页面

fbstatus()函数从 Facebook 获取页面。通过 30 个并发进程,这几乎与池的大小呈线性关系。它在我的笔记本电脑上的平均总运行时间约为 80 秒。在 30 名工人中,总共需要大约 3.75 秒的挂钟时间才能完成。

老 - 平

这使用模块以5 秒超时和 1 计数subprocess调用命令。它使用返回值(0 表示成功,1 表示失败)并取反以获取失败和成功。该函数返回它被调用的地址加上该布尔结果。pingpingFalseTrueping()

最后一位创建一个包含 5 个子进程的多处理池,然后ping()调用addresses. 由于ping()返回其地址,因此很容易看到 ping 每个地址的结果。

运行它,我得到这个输出:

[('1.2.3.4', False), ('1.2.3.5', False), ('4.2.2.1', True), ('8.8.8.8', True)]

那次运行花费了 5.039 秒的挂钟时间和 0% 的 CPU。换句话说,它几乎 100% 的时间都在等待ping返回。在您的脚本中,您可能希望使用诸如请求之类的东西来获取您的提要 URL(而不是ping我作为示例使用的文字命令),但基本结构可能几乎相同。

于 2012-10-23T00:07:46.310 回答
2

您可以尝试在 python 上执行多线程 ping。这是一个很好的例子。

#!/usr/bin/env python2.5
from threading import Thread
import subprocess
from Queue import Queue

num_threads = 4
queue = Queue()
ips = ["10.0.1.1", "10.0.1.3", "10.0.1.11", "10.0.1.51"]
#wraps system ping command
def pinger(i, q):
    """Pings subnet"""
    while True:
        ip = q.get()
        print "Thread %s: Pinging %s" % (i, ip)
        ret = subprocess.call("ping -c 1 %s" % ip,
                        shell=True,
                        stdout=open('/dev/null', 'w'),
                        stderr=subprocess.STDOUT)
        if ret == 0:
            print "%s: is alive" % ip
        else:
            print "%s: did not respond" % ip
        q.task_done()
#Spawn thread pool
for i in range(num_threads):

    worker = Thread(target=pinger, args=(i, queue))
    worker.setDaemon(True)
    worker.start()
#Place work in queue
for ip in ips:
    queue.put(ip)
#Wait until worker threads are done to exit    
queue.join()
于 2012-10-23T00:01:21.017 回答
1

[更新:使用 maxSockets = 100 重新测试,同时连接到非常好的网络连接。脚本在 < 1 秒内完成,这意味着最大的因素可能是网络吞吐量/延迟,如前所述。你的结果几乎肯定会有所不同。;) ]

您可以为此使用 node.js,因为它用于执行 HTTP 的 API 功能强大、干净且简单。例如,以下脚本在我的 MacBook Pro 上不到 1秒的10 秒内获取约 1000 个请求:

测试.js

var http = require('http');

// # of simultaneouse requests allowed
http.globalAgent.maxSockets = 100;

var n = 0;
var start = Date.now();

function getOne(url) {
  var id =  n++;
  var req = http.get(url, function(res) {
    res.on('data', function(chunk){
      // do whatever with response data here
    });
    res.on('end', function(){
      console.log('Response #' + id + ' complete');
      n--;
      if (n == 0) {
        console.log('DONE in ' + (Date.now() - start)/1000 + ' secs');
      }
    });
  });
}

// Set # of simultaneous connections allowed
for (var i = 0; i < 1000; i++) {
  getOne('http://www.facebook.com/status.php');
}

输出...

$ node test.js  
Response #3 complete
Response #0 complete
Response #2 complete
...
Response #999 complete
DONE in 0.658 secs
于 2012-10-23T00:18:07.280 回答
1

我非常广泛地使用 Perl 的 POE Ping 组件模块来完成这项任务。

于 2012-10-23T22:31:31.120 回答
0

感谢 Alex Luunix 的建议。我查找了 curl_multi_* 并找到了在 curl 中执行此操作的解决方案,因此我不必对代码进行太多更改。但是感谢所有其他人的答案。这是我所做的:

<?php
require("class.php");
$obj=new module();

$det=$obj->get_url();

$batch_size = 40;

function curlTest2($urls) {
    clearstatcache();
    $batch_size = count($urls);

    $return = '';

    echo "<br/><br/>Batch:";
    foreach ($urls as &$url)
    {
        echo "<br/>".$url;
        if(substr($url,0,4)!="http") $url = "http://".$url;
        $url = "https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=-1&q=".$url;
    }

    $userAgent = 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; .NET CLR 1.1.4322)';

    $chs = array();

    for ($i = 0; $i < $batch_size; $i++)
    {
        $ch = curl_init();
        array_push($chs, $ch);
    }

    for ($i = 0; $i < $batch_size; $i++)
    {
        curl_setopt($chs[$i], CURLOPT_HEADER, 1);
        curl_setopt($chs[$i], CURLOPT_NOBODY, 1);
        curl_setopt($chs[$i], CURLOPT_USERAGENT, $userAgent);
        curl_setopt($chs[$i], CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($chs[$i], CURLOPT_CONNECTTIMEOUT, 15);
        curl_setopt($chs[$i], CURLOPT_FAILONERROR, 1);
        curl_setopt($chs[$i], CURLOPT_FRESH_CONNECT, 1);
        curl_setopt($chs[$i], CURLOPT_URL, $urls[$i]);
    }

    $mh = curl_multi_init();

    for ($i = 0; $i < $batch_size; $i++)
    {
        curl_multi_add_handle($mh, $chs[$i]);
    }

    $active = null;
    //execute the handles
    do {
        $mrc = curl_multi_exec($mh, $active);
    } while ($mrc == CURLM_CALL_MULTI_PERFORM);

    while ($active && $mrc == CURLM_OK) {
        if (curl_multi_select($mh) != -1) {
            do {
                $mrc = curl_multi_exec($mh, $active);
            } while ($mrc == CURLM_CALL_MULTI_PERFORM);
        }
    }

    //close the handles
    for ($i = 0; $i < $batch_size; $i++)
    {
        curl_multi_remove_handle($mh, $chs[$i]);
    }

    curl_multi_close($mh);
}

$startTime = time();
$urls = array();

foreach($det as $key=>$value){
    array_push($urls, $value['url']);

    if (count($urls) == $batch_size)
    {
        curlTest2($urls);
        $urls = array();
    }
}

echo "<br/><br/>Time: ".(time() - $startTime)."sec";
?>

这将我的处理时间从 332 秒减少到 18 秒。代码可能可以稍微优化一下,但你会明白它的要点。

于 2012-10-23T21:47:12.280 回答