【问题标题】:Need to ping 1000 urls every 2 minutes需要每 2 分钟 ping 1000 个 url
【发布时间】:2012-10-12 20:44:40
【问题描述】:

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

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

【问题讨论】:

  • 如果你这样做了,我可以看到许多这些提要阻止了你。
  • 制作一个 ping 一个网站的脚本,启动它 1000 次以创建单独的进程而不会阻塞。
  • @Dagon - 如果他在 2 分钟内达到 1 个饲料 1000 倍,是的。但是 OP 只说他有 1000 个(可能不同)的 URL,他需要每两分钟点击一次。他重复点击一个 URL 的频率是一个单独的问题。
  • @broofa 每 2 分钟 1 个特定的提要仍然过多。
  • @Dagon - 1. 我们没有得到关于 OP 预计多久点击一次特定提要的信息。 2.“过度”完全取决于我们谈论的是什么服务,以及预计获取的数据多久会改变一次。

标签: php javascript python perl curl


【解决方案1】:

由于您的问题是关于发送 http 请求,而不是真正的 ping,您可以使用 Grequests (Requests+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(coroutine-based ) 或nodejs。你可以在here阅读更多内容。

【讨论】:

    【解决方案2】:

    查看CPAN 上的AnyEvent::PingAnyEvent::FastPing 模块。

    以下是使用AnyEvent::Ping ping 10000 个网址的简单示例:

    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 如果使用 libevAnyEvent 将默认为纯 Perl 事件循环,如果没有找到(安装)其他内容。

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

    【讨论】:

      【解决方案3】:

      你用“python”标记了它,所以我假设在这里使用 Python 是一个选项。看看multiprocessing module。例如:

      #!/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 秒才能完成。

      旧 - Ping

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

      最后一位创建一个包含 5 个子进程的多处理池,然后对 addresses 中的每个值调用 ping()。由于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 返回。在您的脚本中,您可能希望使用 Requests 之类的东西来获取您的提要 URL(而不是我作为示例使用的文字 ping 命令),但基本结构可能几乎相同。

      【讨论】:

      • 是的,python 是一个选项。但我最终选择了 CURL 路线,因为我已经有了代码,而且很容易修改它。不过谢谢!我对你的答案投了赞成票。
      • 很高兴我们能帮上忙!祝你的项目好运。
      【解决方案4】:

      您可以在 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()
      

      【讨论】:

        【解决方案5】:

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

        【讨论】:

          【解决方案6】:

          [更新:使用 maxSockets = 100 重新测试,同时连接到 非常 良好的网络连接。脚本在

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

          test.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
          

          【讨论】:

            【解决方案7】:

            感谢 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 秒。代码可能可以稍微优化一下,但你会明白它的要点。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2019-07-21
              • 2021-12-08
              • 2019-04-02
              • 1970-01-01
              • 2016-01-05
              相关资源
              最近更新 更多