8

MySQL データベース テーブルに 1000 個のフィード URL があります。これらすべての URL に対して 2 分ごとに http 要求を行う必要があります。これを行うための php スクリプトを作成しましたが、スクリプトの実行には 5 分 30 秒かかります。

1000 件のリクエストすべてを 1 分以内に完了できるようにしたいと考えています。複数の非同期プロセスを実行して、ジョブをより高速に処理する方法はありますか? どんな助けでも大歓迎です。前もって感謝します。

4

7 に答える 7

7

あなたの質問は、実際にはpingではなく、httpリクエストの送信に関するものであるため、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のまたはAnyEvent::FastPingモジュールを見てください。

以下は、AnyEvent::Ping10000 個の URL を pingするために使用する簡単な例です。

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は、システムによって提供される (またはシステムにインストールされている) 非同期イベント システムを使用できるようにする抽象化ライブラリです。EV特定のイベント ループを使用する場合は、CPAN から関連する Perl モジュールをインストールすることを忘れないでくださいlibevAnyEvent他に何も見つからない (インストールされている) 場合は、デフォルトで純粋な Perl イベント ループになります。

ところで、HTTP リクエストをチェックするだけ (つまり、ping ではない) する必要がある場合は、単純にAnyEvent::Ping一部を に置き換えAnyEvent::HTTPます。

于 2012-10-23T15:18:48.380 に答える
3

これに「python」のタグを付けたので、ここでは Python の使用がオプションであると仮定します。multiprocessing モジュールを見てください。例えば:

#!/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()関数は、呼び出されたアドレスとブール値の結果を返します。

ping()最後のビットは、5 つの子プロセスを持つマルチプロセッシング プールを作成し、の各値を呼び出します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を返還を待っていました。スクリプトでは、Requestsのようなものを使用してフィード 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

私はこのタスクにPerlの POEPingコンポーネントモジュールをかなり広範囲に使用しました。

于 2012-10-23T22:31:31.120 に答える
1

[更新: maxSockets = 100 で、非常に良好なネットワーク接続に接続している間にこれを再テストしました。スクリプトは 1 秒未満で終了しました。これは、前述のように、最大​​の要因はおそらくネットワーク スループット/レイテンシであることを意味します。あなたの結果はほぼ確実に異なります。;) ]

これには node.js を使用できます。これは、HTTP を実行するための API であり、強力でクリーンでシンプルなためです。たとえば、次のスクリプトは、MacBook Pro で 1 秒未満の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
于 2012-10-23T00:18:07.280 に答える
0

提案してくれたAlex Lunixに感謝します。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 に答える