php cURL和Rolling cURL并发方式比较


Posted in PHP onOctober 30, 2013

在实际项目或者自己编写小工具(比如新闻聚合,商品价格监控,比价)的过程中, 通常需要从第3方网站或者API接口获取数据, 在需要处理1个URL队列时, 为了提高性能, 可以采用cURL提供的curl_multi_*族函数实现简单的并发。
本文将探讨两种具体的实现方法, 并对不同的方法做简单的性能对比.
1. 经典cURL并发机制及其存在的问题
经典的cURL实现机制在网上很容易找到, 比如参考PHP在线手册的如下实现方式:

function
classic_curl($urls,
$delay)
 {
    $queue
= curl_multi_init();
    $map
= array();
 
    foreach
($urls
as 
$url)
 {
        //
 create cURL resources
        $ch
= curl_init();
 
        //
 set URL and other appropriate options
        curl_setopt($ch,
 CURLOPT_URL, $url);
 
        curl_setopt($ch,
 CURLOPT_TIMEOUT, 1);
        curl_setopt($ch,
 CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch,
 CURLOPT_HEADER, 0);
        curl_setopt($ch,
 CURLOPT_NOSIGNAL, true);
 
        //
 add handle
        curl_multi_add_handle($queue,
$ch);
        $map[$url]
 = $ch;
    }
 
    $active
= null;
 
    //
 execute the handles
    do
{
        $mrc
= curl_multi_exec($queue,
$active);
    }
while
($mrc
== CURLM_CALL_MULTI_PERFORM);
 
    while
($active
> 0 && $mrc
== CURLM_OK) {
        if
(curl_multi_select($queue,
 0.5) != -1) {
            do
{
                $mrc
= curl_multi_exec($queue,
$active);
            }
while
($mrc
== CURLM_CALL_MULTI_PERFORM);
        }
    }
 
    $responses
= array();
    foreach
($map
as 
$url=>$ch)
 {
        $responses[$url]
 = callback(curl_multi_getcontent($ch),
$delay);
        curl_multi_remove_handle($queue,
$ch);
        curl_close($ch);
    }
 
    curl_multi_close($queue);
    return
$responses;
}

首先将所有的URL压入并发队列, 然后执行并发过程, 等待所有请求接收完之后进行数据的解析等后续处理. 在实际的处理过程中, 受网络传输的影响, 部分URL的内容会优先于其他URL返回, 但是经典cURL并发必须等待最慢的那个URL返回之后才开始处理, 等待也就意味着CPU的空闲和浪费. 如果URL队列很短, 这种空闲和浪费还处在可接受的范围, 但如果队列很长, 这种等待和浪费将变得不可接受.
2. 改进的Rolling cURL并发方式
仔细分析不难发现经典cURL并发还存在优化的空间, 优化的方式时当某个URL请求完毕之后尽可能快的去处理它, 边处理边等待其他的URL返回, 而不是等待那个最慢的接口返回之后才开始处理等工作, 从而避免CPU的空闲和浪费. 闲话不多说, 下面贴上具体的实现:
function
rolling_curl($urls,
$delay)
 {
    $queue
= curl_multi_init();
    $map
= array();
 
    foreach
($urls
as 
$url)
 {
        $ch
= curl_init();
 
        curl_setopt($ch,
 CURLOPT_URL, $url);
        curl_setopt($ch,
 CURLOPT_TIMEOUT, 1);
        curl_setopt($ch,
 CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch,
 CURLOPT_HEADER, 0);
        curl_setopt($ch,
 CURLOPT_NOSIGNAL, true);
 
        curl_multi_add_handle($queue,
$ch);
        $map[(string)
$ch]
 = $url;
    }
 
    $responses
= array();
    do
{
        while
(($code
= curl_multi_exec($queue,
$active))
 == CURLM_CALL_MULTI_PERFORM) ;
 
        if
($code
!= CURLM_OK) { break;
 }
 
        //
 a request was just completed -- find out which one
        while
($done
= curl_multi_info_read($queue))
 {
 
            //
 get the info and content returned on the request
            $info
= curl_getinfo($done['handle']);
            $error
= curl_error($done['handle']);
            $results
= callback(curl_multi_getcontent($done['handle']),
$delay);
            $responses[$map[(string)
$done['handle']]]
 = compact('info',
'error',
'results');
 
            //
 remove the curl handle that just completed
            curl_multi_remove_handle($queue,
$done['handle']);
            curl_close($done['handle']);
        }
 
        //
 Block for data in / output; error handling is done by curl_multi_exec
        if
($active
> 0) {
            curl_multi_select($queue,
 0.5);
        }
 
    }
while
($active);
 
    curl_multi_close($queue);
    return
$responses;
}

3. 两种并发实现的性能对比
改进前后的性能对比试验在LINUX主机上进行, 测试时使用的并发队列如下:

http://a.com/item.htm?id=14392877692
http:/a.com/item.htm?id=16231676302
http://a.com/item.htm?id=5522416710
http://a.com/item.htm?id=16551116403
简要说明下实验设计的原则和性能测试结果的格式: 为保证结果的可靠, 每组实验重复20次, 在单次实验中, 给定相同的接口URL集合, 分别测量Classic(指经典的并发机制)和Rolling(指改进后的并发机制)两种并发机制的耗时(秒为单位), 耗时短者胜出(Winner), 并计算节省的时间(Excellence, 秒为单位)以及性能提升比例(Excel. %). 为了尽量贴近真实的请求而又保持实验的简单, 在对返回结果的处理上只是做了简单的正则表达式匹配, 而没有进行其他复杂的操作. 另外, 为了确定结果处理回调对性能对比测试结果的影响, 可以使用usleep模拟现实中比较负责的数据处理逻辑(如提取, 分词, 写入文件或数据库等).
性能测试中用到的回调函数为:

function
callback($data,
$delay)
 {
    preg_match_all('/<h3>(.+)<\/h3>/iU',
$data,
$matches);
    usleep($delay);
    return
compact('data',
'matches');
}

数据处理回调无延迟时: Rolling Curl略优, 但性能提升效果不明显。
php cURL和Rolling cURL并发方式比较
PHP 相关文章推荐
PHP中的正规表达式(二)
Oct 09 PHP
利用PHP生成静态HTML文档的原理
Oct 29 PHP
php class中self,parent,this的区别以及实例介绍
Apr 24 PHP
PHP按行读取、处理较大CSV文件的代码实例
Apr 09 PHP
php获取中文拼音首字母类和函数分享
Apr 24 PHP
php查看网页源代码的方法
Mar 13 PHP
PHP strip_tags() 去字符串中的 HTML、XML 以及 PHP 标签的函数
May 22 PHP
thinkPHP简单实现多个子查询语句的方法
Dec 05 PHP
PHP ajax+jQuery 实现批量删除功能实例代码小结
Dec 06 PHP
php 使用expat方式解析xml文件操作示例
Nov 26 PHP
php反序列化长度变化尾部字符串逃逸(0CTF-2016-piapiapia)
Feb 15 PHP
浅谈PHP之ThinkPHP框架使用详解
Jul 21 PHP
使用PHP Socket写的POP3类
Oct 30 #PHP
腾讯QQ微博API接口获取微博内容
Oct 30 #PHP
FireFox浏览器使用Javascript上传大文件
Oct 30 #PHP
php使用ICQ网关发送手机短信
Oct 30 #PHP
PHP分页详细讲解(有实例)
Oct 30 #PHP
php预定义变量使用帮助(带实例)
Oct 30 #PHP
调整PHP的性能
Oct 30 #PHP
You might like
PHP中动态HTML的输出技术
2006/10/09 PHP
php数组函数序列之end() - 移动数组内部指针到最后一个元素,并返回该元素的值
2011/10/31 PHP
php 模拟GMAIL,HOTMAIL(MSN),YAHOO,163,126邮箱登录的详细介绍
2013/06/18 PHP
laravel框架中表单请求类型和CSRF防护实例分析
2019/11/23 PHP
二级域名转向类
2006/11/09 Javascript
比较简单的一个符合web标准的JS调用flash方法
2007/11/29 Javascript
JavaScript 继承机制的实现(待续)
2010/05/18 Javascript
Javascript 八进制转义字符(8进制)
2011/04/08 Javascript
JavaScript中prototype为对象添加属性的误区介绍
2013/10/15 Javascript
SeaJS入门教程系列之SeaJS介绍(一)
2014/03/03 Javascript
JS实现跟随鼠标立体翻转图片的方法
2015/05/04 Javascript
JS实现简单的图书馆享元模式实例
2015/06/30 Javascript
JS+CSS实现大气清新的滑动菜单效果代码
2015/10/22 Javascript
实例详解ECMAScript5中新增的Array方法
2016/04/05 Javascript
Js自动截取字符串长度,添加省略号(……)的实现方法
2017/03/06 Javascript
canvas绘制一个常用的emoji表情
2017/03/30 Javascript
JS判断两个对象内容是否相等的方法示例
2017/04/10 Javascript
vue-cli webpack 开发环境跨域详解
2017/05/18 Javascript
Vue隐藏显示、只读实例代码
2018/07/18 Javascript
Vuex模块化应用实践示例
2020/02/03 Javascript
[03:22]DAC最前线(第二期)—DOTA2亚洲邀请赛主赛场周边及线路探访
2015/01/24 DOTA
Python 字符串定义
2009/09/25 Python
Python中SOAP项目的介绍及其在web开发中的应用
2015/04/14 Python
实例解析Python的Twisted框架中Deferred对象的用法
2016/05/25 Python
python 创建弹出式菜单的实现代码
2017/07/11 Python
python3 kmp 字符串匹配的方法
2018/07/07 Python
Python使用logging模块实现打印log到指定文件的方法
2018/09/05 Python
Django 视图层(view)的使用
2018/11/09 Python
使用Python+selenium实现第一个自动化测试脚本
2020/03/17 Python
美国名牌香水折扣网站:Hottperfume
2021/02/10 全球购物
中文系学生自荐信范文
2013/11/13 职场文书
优秀干部获奖感言
2014/01/31 职场文书
个人安全生产责任书
2014/07/28 职场文书
2014光棍节单身联谊活动策划书
2014/10/10 职场文书
2015年卫生局工作总结
2015/07/24 职场文书
go语言中http超时引发的事故解决
2021/06/02 Golang