MySQL データベース テーブルに 1000 個のフィード URL があります。これらすべての URL に対して 2 分ごとに http 要求を行う必要があります。これを行うための php スクリプトを作成しましたが、スクリプトの実行には 5 分 30 秒かかります。
1000 件のリクエストすべてを 1 分以内に完了できるようにしたいと考えています。複数の非同期プロセスを実行して、ジョブをより高速に処理する方法はありますか? どんな助けでも大歓迎です。前もって感謝します。
MySQL データベース テーブルに 1000 個のフィード URL があります。これらすべての URL に対して 2 分ごとに http 要求を行う必要があります。これを行うための php スクリプトを作成しましたが、スクリプトの実行には 5 分 30 秒かかります。
1000 件のリクエストすべてを 1 分以内に完了できるようにしたいと考えています。複数の非同期プロセスを実行して、ジョブをより高速に処理する方法はありますか? どんな助けでも大歓迎です。前もって感謝します。
あなたの質問は、実際にはpingではなく、httpリクエストの送信に関するものであるため、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
(コルーチンベース) またはnodejs
. 詳細については、こちらをご覧ください。
CPANAnyEvent::Ping
のまたはAnyEvent::FastPing
モジュールを見てください。
以下は、AnyEvent::Ping
10000 個の 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 モジュールをインストールすることを忘れないでくださいlibev
。 AnyEvent
他に何も見つからない (インストールされている) 場合は、デフォルトで純粋な Perl イベント ループになります。
ところで、HTTP リクエストをチェックするだけ (つまり、ping ではない) する必要がある場合は、単純にAnyEvent::Ping
一部を に置き換えAnyEvent::HTTP
ます。
これに「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 秒かかりました。
これは、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
コマンドではありません)、基本的な構造はほぼ同じである可能性があります。
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()
私はこのタスクにPerlの POEPingコンポーネントモジュールをかなり広範囲に使用しました。
[更新: 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
提案してくれた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 秒に短縮されました。コードはおそらく少し最適化できますが、要点は理解できます。