1. PHP异步多任务处理

方案1 利用浏览器的ajax技术异步发送请求

原理: 服务器返回的html中插入Ajax 代码或 img 标记,img的src为需要执行的程序。

优点

1. 实现简单,服务端无需执行任何调用

缺点

1. 在执行期间,浏览器会一直处于loading状态,因为这种方法不算是真正的异步调用
2. 任务的划分对客户端不是透明的,对于一些需要将多任务结果聚合的场景,需要在浏览器端进行组合

例子

$.get("doRequest.php", { name: "fdipzone"} );
<img src="doRequest.php?name=fdipzone">
方案2 使用进程管道通信popen

原理: 创建新的进程来处理任务

优点

1. 用多进程实现了多任务异步处理

缺点

1. 只能本机执行
2. 不能传递复杂的参数
3. 在高并发场景创建过多的进程,开销太大
4. 无服务器部署扩展性

例子

pclose(popen('php /home/fdipzone/doRequest.php &', 'r'));
方案3 使用curl扩展

原理: 使用curl的多线程支持同时发送多个http请求,完成异步任务

优点

1. 效率较好,可以实现多任务并行执行

缺点

1. 代码编写不是特别简洁,要自己写监测多个任务执行状态
2. 任务的交互要走http应用层协议

例子

function getCurlObject($url,$postData=array(),$header=array()){
    $options = array();
    $url = trim($url);
    $options[CURLOPT_URL] = $url;
    $options[CURLOPT_TIMEOUT] = 10;
    $options[CURLOPT_USERAGENT] = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36';
    $options[CURLOPT_RETURNTRANSFER] = true;
    //    $options[CURLOPT_PROXY] = '127.0.0.1:8888';
    foreach($header as $key=>$value){
        $options[$key] =$value;
    }
    if(!empty($postData) && is_array($postData)){
        $options[CURLOPT_POST] = true;
        $options[CURLOPT_POSTFIELDS] = http_build_query($postData);
    }
    if(stripos($url,'https') === 0){
        $options[CURLOPT_SSL_VERIFYPEER] = false;
    }
    $ch = curl_init();
    curl_setopt_array($ch,$options);

    return $ch;
}

// 创建三个待请求的url对象
$chList = array();
$chList[] = getCurlObject('http://192.168.101.178/index.php?r=test2');
$chList[] = getCurlObject('http://192.168.101.178/index.php?r=test2');
$chList[] = getCurlObject('http://192.168.101.178/index.php?r=test2');

// 创建多请求执行对象
$downloader = curl_multi_init();

// 将三个待请求对象放入下载器中
foreach ($chList as $ch){
    curl_multi_add_handle($downloader,$ch);
}

// 轮询
do {
    while (($execrun = curl_multi_exec($downloader, $running)) == CURLM_CALL_MULTI_PERFORM) ;
    if ($execrun != CURLM_OK) {
        break;
    }

    // 一旦有一个请求完成,找出来,处理,因为curl底层是select,所以最大受限于1024
    while ($done = curl_multi_info_read($downloader))
    {
        // 从请求中获取信息、内容、错误
        $info = curl_getinfo($done['handle']);
        $output = curl_multi_getcontent($done['handle']);
        $error = curl_error($done['handle']);

        // 将请求结果保存,我这里是打印出来
        print $output;
        //        print "一个请求下载完成!\n";

        // 把请求已经完成了得 curl handle 删除
        curl_multi_remove_handle($downloader, $done['handle']);
    }

    // 当没有数据的时候进行堵塞,把 CPU 使用权交出来,避免上面 do 死循环空跑数据导致 CPU 100%
    if ($running) {
        $rel = curl_multi_select($downloader, 1);
        if($rel == -1){
            usleep(1000);
        }
    }

    if( $running == false){
        break;
    }
} while (true);


curl_multi_close($downloader);
echo "done";
方案4 使用socket扩展

原理: 使用fsockopen构造http请求,实现多任务的并行处理

优点

1. 效率较好

缺点

1. 对于php编写底层的通信代码不是最好的选择,要关注http协议和通信异常
2. 任务的交互要走http应用层协议

例子

$url = 'http://www.example.com/doRequest.php'; 
$param = array( 
  'name'=>'fdipzone', 
  'gender'=>'male', 
  'age'=>30 
); 
ignore_user_abort(true); // 忽略客户端断开 
set_time_limit(0);    // 设置执行不超时
doRequest($url, $param); 
    
function doRequest($url, $param=array())
{     
  $urlinfo = parse_url($url); 
    
  $host = $urlinfo['host']; 
  $path = $urlinfo['path']; 
  $query = isset($param)? http_build_query($param) : ''; 
    
  $port = 80; 
  $errno = 0; 
  $errstr = ''; 
  $timeout = 10; 
    
  $fp = fsockopen($host, $port, $errno, $errstr, $timeout); 
    
  $out = "POST ".$path." HTTP/1.1\r\n"; 
  $out .= "host:".$host."\r\n"; 
  $out .= "content-length:".strlen($query)."\r\n"; 
  $out .= "content-type:application/x-www-form-urlencoded\r\n"; 
  $out .= "connection:close\r\n\r\n"; 
  $out .= $query; 
    
  fputs($fp, $out); 
  fclose($fp); 
}
方案5 使用gearman分布式任务系统

原理: Gearman提供了一种通用的程序框架来将你的任务分发到不同的机器或者不同的进程当中。它提供了你进行并行工作的能力、负载均衡处理的能力,以及在不同程序语言之间沟通的能力。Gearman能够应用的领域非常广泛,从高可用的网站到数据库的复制任务。总之,Gearman就是负责分发处理的中枢系统.

缺点

1. 需要一个服务器跑守护进程
2. php必须打一个扩展用来支持该功能

优点

1. Gearman免费并且开源而且有一个非常活跃的开源社区
2. 多语言支持, 支持的语言种类非常丰富。让我们能够用一种语言来编写Worker程序,但是用另外一种语言编写Client程序。
3. 不必拘泥于固定的形式。您可以采用你希望的任何形式,例如 Map/Reduce。
4. Gearman的协议非常简单,并且有一个用C语言实现的,经过优化的服务器,保证应用的负载在非常低的水平。
5. 可植入, 因为Gearman非常小巧、灵活。因此您可以将他置入到现有的任何系统中。
6. 多点灾备,Gearman不仅可以帮助扩展系统,同样可以避免系统的失败。

例子

// worker
$worker= new GearmanWorker();    
$worker->addServer("127.0.0.1", 4730);     
$worker->addFunction("title", "title_function");    
while ($worker->work());    
           
function title_function($job){
	sleep(1);
	return time().'+'.strtoupper($job->workload());
}

// client
# Create our gearman client
$gmclient= new GearmanClient(); 
# add the default job server
$gmclient->addServer(); 
# set a function to be called when the work is complete
$gmclient->setCompleteCallback("complete"); 
# add a task to perform the "reverse" function on the string "Hello World!"
$gmclient->addTask("title", "Hello", null, "task1"); 
# add another task to perform the "reverse" function on the string "!dlroW olleH"
$gmclient->addTask("title", "World", null, "task2"); 
$gmclient->addTask("title", "World", null, "task3");
$gmclient->addTask("title", "World", null, "task4");
# run the tasks
$gmclient->runTasks(); 

function complete($task) 
{ 
  print "COMPLETE: " . $task->unique() . ", " . $task->data() . "\n"; 
  OXO::app('log')->info("data[".$task->unique()."]=".$task->data());
  OXO::app('log')->info("end");
}
方案6 使用Swoole

原理: PHP的异步、并行、高性能网络通信引擎,使用纯C语言编写,提供了PHP语言的异步多线程服务器,异步TCP/UDP网络客户端,异步MySQL,异步Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端 官网 http://www.swoole.com/

优点

1. 纯C编写性能极强
2. 简单易用开发效率高
3. 事件驱动异步非阻塞
4. 并发百万TCP连接
5. TCP/UDP/UnixSock
6. 服务器端/客户端
7. 全异步/半异步半同步
8. 支持多进程/多线程
9. CPU亲和性/守护进程
10. 支持IPv4/IPv6网络

缺点

功能太重了,涉及的范围太广,对现有框架的影响力太大, 不太适合用来写业务

例子

// webserver
$serv = new swoole_http_server("127.0.0.1", 9502);

$serv->on('Request', function($request, $response) {
    var_dump($request->get);
    var_dump($request->post);
    var_dump($request->cookie);
    var_dump($request->files);
    var_dump($request->header);
    var_dump($request->server);

    $response->cookie("User", "Swoole");
    $response->header("X-Server", "Swoole");
    $response->end("<h1>Hello Swoole!</h1>");
});

$serv->start();
其他方案