php プログラマーは、php で書かれたプログラムがすべて同期であることを知っています。php で非同期プログラムを書くにはどうすればよいでしょうか? 答えは Swoole です。ここでは、Web コンテンツのクロールを例として、Swoole を使用して非同期プログラムを作成する方法を示します。
PHP同期プログラム
非同期プログラムを作成する前に、心配しないで、まずPHPを使用して同期プログラムを実装してください。
<?php /** * Class Crawler * Path: /Sync/Crawler.php */ class Crawler { private $url; private $toVisit = []; public function __construct($url) { $this->url = $url; } public function visitOneDegree() { $this->loadPageUrls(); $this->visitAll(); } private function loadPageUrls() { $content = $this->visit($this->url); $pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i'; preg_match_all($pattern, $content, $matched); foreach ($matched[0] as $url) { if (in_array($url, $this->toVisit)) { continue; } $this->toVisit[] = $url; } } private function visitAll() { foreach ($this->toVisit as $url) { $this->visit($url); } } private function visit($url) { return @file_get_contents($url); } }
Swooleの非同期クローラ実装に関する予備調査
まずは公式の非同期クローラページをご覧ください。
使用例
<?php /** * crawler.php */ require_once 'Sync/Crawler.php'; $start = microtime(true); $url = 'http://www.swoole.com/'; $ins = new Crawler($url); $ins->visitOneDegree(); $timeUsed = microtime(true) - $start; echo "time used: " . $timeUsed; /* output: time used: 6.2610177993774 */
同期のfile_get_contentsのコードを少し変更するだけで非同期実装が簡単に成功するようです。
それで、次のコードが得られました:
Swoole\Async::dnsLookup("www.baidu.com", function ($domainName, $ip) { $cli = new swoole_http_client($ip, 80); $cli->setHeaders([ 'Host' => $domainName, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->get('/index.html', function ($cli) { echo "Length: " . strlen($cli->body) . "\n"; echo $cli->body; }); });
<?php /** * Class Crawler * Path: /Async/CrawlerV1.php */ class Crawler { private $url; private $toVisit = []; private $loaded = false; public function __construct($url) { $this->url = $url; } public function visitOneDegree() { $this->visit($this->url, true); $retryCount = 3; do { sleep(1); $retryCount--; } while ($retryCount > 0 && $this->loaded == false); $this->visitAll(); } private function loadPage($content) { $pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i'; preg_match_all($pattern, $content, $matched); foreach ($matched[0] as $url) { if (in_array($url, $this->toVisit)) { continue; } $this->toVisit[] = $url; } } private function visitAll() { foreach ($this->toVisit as $url) { $this->visit($url); } } private function visit($url, $root = false) { $urlInfo = parse_url($url); Swoole\Async::dnsLookup($urlInfo['host'], function ($domainName, $ip) use($urlInfo, $root) { $cli = new swoole_http_client($ip, 80); $cli->setHeaders([ 'Host' => $domainName, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->get($urlInfo['path'], function ($cli) use ($root) { if ($root) { $this->loadPage($cli->body); $this->loaded = true; } }); }); } }
実行には 3 秒かかりました。私の実装に注目してください。ホームページをクロールするリクエストを開始した後、結果を毎秒ポーリングし、3 回ポーリングすると終了します。ここでの 3 秒は、3 回ポーリングを行っても結果が得られなかったことによる終了と思われます。
私が焦りすぎて、十分な準備時間を与えられなかったようです。さて、投票数を 10 に変更して結果を見てみましょう。
この時の私の気持ちがわかります。
それはスウールのパフォーマンスの問題ですか? 10秒経っても効果が出ないのは姿勢が悪いからでしょうか?老人マルクスは、「実践こそが真実を試す唯一の基準である」と言いました。原因を調べるにはデバッグする必要があるようです。
そこで、
<?php /** * crawler.php */ require_once 'Async/CrawlerV1.php'; $start = microtime(true); $url = 'http://www.swoole.com/'; $ins = new Crawler($url); $ins->visitOneDegree(); $timeUsed = microtime(true) - $start; echo "time used: " . $timeUsed; /* output: time used: 3.011773109436 */
と
time used: 10.034232854843
にブレークポイントを追加しました。最後に、visitAll() が常に最初に実行され、次にloadPage() が実行されることがわかりました。
少し考えてみたら、おそらくその理由が分かりました。舞台裏の理由は何ですか?
私が期待した非同期動的モデルは次のようになります:
しかし、実際のシーンはこのようなものではありません。デバッグを通じて、実際のモデルは次のようになるはずであることがおおよそ理解できます:
つまり、リトライ回数をいくら増やしても、データは決して準備できず、データが準備されるだけです現在の関数の準備が完了すると、実行が開始されます。ここでの非同期は、接続の準備にかかる時間を短縮するだけです。
そこで問題は、データを準備した後、プログラムに期待する機能を実行させるにはどうすればよいかということです。
まず、非同期タスクを実行するための Swoole の公式コードがどのように書かれているかを見てみましょう
$this->visitAll();
公式が関数匿名関数を通じて後続の実行ロジックを渡していることがわかります。このように見ると、物事ははるかに単純になります。
$this->loadPage($cli->body);
このコードを読んで、私は既視感を感じました。nodejs の開発では、随所に見られるコールバックにはそれぞれの理由があります。ここで、非同期の問題を解決するためにコールバックが存在することが突然わかりました。
プログラムを実行すると、わずか 0.0007 秒しかかからず、開始する前に終了しました。本当に非同期の効率がそこまで向上するのでしょうか?答えはもちろん「いいえ」です。コードに何か問題があります。
非同期を使用しているため、タスクが完全に完了するのを待たずに終了時刻を計算するロジックが実行されます。再びコールバックを使用する時期が来たようです。
$serv = new swoole_server("127.0.0.1", 9501); //设置异步任务的工作进程数量 $serv->set(array('task_worker_num' => 4)); $serv->on('receive', function($serv, $fd, $from_id, $data) { //投递异步任务 $task_id = $serv->task($data); echo "Dispath AsyncTask: id=$task_id\n"; }); //处理异步任务 $serv->on('task', function ($serv, $task_id, $from_id, $data) { echo "New AsyncTask[id=$task_id]".PHP_EOL; //返回任务执行的结果 $serv->finish("$data -> OK"); }); //处理异步任务的结果 $serv->on('finish', function ($serv, $task_id, $data) { echo "AsyncTask[$task_id] Finish: $data".PHP_EOL; }); $serv->start();
今見ると、結果はより信頼性が高くなります。
同期と非同期の差を比較してみましょう。同期には 6.26 秒かかり、非同期には 0.068 秒かかります。これは完全に 6.192 秒の差です。いや、正確に言うと10倍近く悪いはずです!
もちろん、効率の点では、非同期コードは同期コードよりもはるかに優れていますが、論理的に言えば、非同期ロジックは同期コードよりも複雑で、コードは大量のコールバックをもたらすため、理解するのは簡単ではありません。
Swoole 公式には、非同期と同期の選択に関する説明があり、非常に適切なので、それを共有したいと思います:
url = $url; } public function visitOneDegree() { $this->visit($this->url, function ($content) { $this->loadPage($content); $this->visitAll(); }); } private function loadPage($content) { $pattern = '#((http|ftp)://(\S*?\.\S*?))([\s)\[\]{},;"\':<]|\.\s|$)#i'; preg_match_all($pattern, $content, $matched); foreach ($matched[0] as $url) { if (in_array($url, $this->toVisit)) { continue; } $this->toVisit[] = $url; } } private function visitAll() { foreach ($this->toVisit as $url) { $this->visit($url); } } private function visit($url, $callBack = null) { $urlInfo = parse_url($url); Swoole\Async::dnsLookup($urlInfo['host'], function ($domainName, $ip) use($urlInfo, $callBack) { if (!$ip) { return; } $cli = new swoole_http_client($ip, 80); $cli->setHeaders([ 'Host' => $domainName, "User-Agent" => 'Chrome/49.0.2587.3', 'Accept' => 'text/html,application/xhtml+xml,application/xml', 'Accept-Encoding' => 'gzip', ]); $cli->get($urlInfo['path'], function ($cli) use ($callBack) { if ($callBack) { call_user_func($callBack, $cli->body); } $cli->close(); }); }); } }
関連書籍:
上記がこの記事のすべての内容です。学生が質問がある場合は、コメントエリアで議論できます。以下〜
以上がSwoole を使用して Web ページを非同期にクロールする実践的な共有の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。