斟酌用PHP實現以下場景: 有1個抓站的URL列表保存在隊列里,后臺程序讀取這個隊列,然后轉交給子進程去抓取HTML寄存到文件里。 為了提高效力,允許多任務并行履行,但為了不機器負載太高,限制了最大的并行任務數(為了測試方便,我們把這個數設為3),當隊列中取到 END標記時,程序結束運行。
這個場景用QPM的Supervisor::taskFactoryMode()實現,非常簡單。
QPM全名是 Quick Process Management Module for PHP. PHP 是強大的web開發語言,以致于大家常常忘記PHP 可以用來開發硬朗的命令行(CLI)程序以致于daemon程序。 而編寫daemon程序免不了與各種進程管理打交道。QPM正式為簡化進程管理而開發的類庫。QPM的項目地址是:https://github.com/Comos/qpm
為了,簡化測試環境,我們可以用1個文本文件來摹擬隊列的數據。完全的例子文件看這里:spider_task_factory_data.txt
http://news.sina.com.cn/
http://news.ifeng.com/
http://news.163.com/
http://news.sohu.com/
http://ent.sina.com.cn/
http://ent.ifeng.com/
...
END
使用QPM的taskFactoryMode之前,我們需要準備1個TaskFactory類。 我們將其命名為 SpiderTaskFactory,SpdierTaskFactory 的工廠方法fetchTask 正常返回 Runnable的子類的實例。當碰到END或文件結束,則throw StopSignal,這樣程序就會終止。
以下是組裝 Supervisor 并履行的代碼片斷。完全的例子見:spider_task_factory.php
//如果沒有從參數指定輸入,把spider_task_factory_data.txt作為數據源
$input = isset($argv[1]) ? $argv[1] : __DIR__.'/spider_task_factory_data.txt';
$spiderTaskFactory = new SpiderTaskFactory($input);
$config = [
//指定taskFactory對象和工廠方法
'factoryMethod'=>[$spiderTaskFactory, 'fetchTask'],
//指定最大并發數量為3
'quantity' => 3,
];
//啟動Supervisor
qpmsupervisorSupervisor::taskFactoryMode($config)->start();
SpiderTaskFactory 的實現以下:
/**
* 任務工廠,必須實現 fetchTask方法。
* 該方法正常返回
*
*/
class SpiderTaskFactory {
private $_fh;
public function __construct($input) {
$this->_input = $input;
$this->_fh = fopen($input, 'r');
if ($this->_fh === false) {
throw new Exception('fopen failed:'.$input);
}
}
public function fetchTask() {
while (true) {
if (feof($this->_fh)) {
throw new qpmsupervisorStopSignal();
}
$line = trim(fgets($this->_fh));
if ($line == 'END') {
throw new qpmsupervisorStopSignal();
}
if (empty($line)) {
continue;
}
break;
}
return new SpiderTask($line);
}
}
SpiderTask 的實現以下:
/**
* 在子進程中履行任務的類
* 必須實現 qpmprocessRunnable 接口
*/
class SpiderTask implements qpmprocessRunnable {
private $_target;
public function __construct($target) {
$this->_target = $target;
}
//在子進程中履行的部份
public function run() {
$r = @file_get_contents($this->_target);
if ($r===false) {
throw new Exception('fail to crawl url:'.$this->_target);
}
file_put_contents($this->getLocalFilename(), $r);
}
private function getLocalFilename() {
$filename = str_replace('/', '~', $this->_target);
$filename = str_replace(':', '_', $filename);
$filename = $filename.'-'.date('YmdHis');
return __DIR__.'/_spider/'.$filename.'.html';
}
}
真實的生產環境,用隊列替換文件輸入,便可實現持久運行的生產者/消費者模型的程序。