PHP curl 并发最佳实践代码分享


Posted in PHP onSeptember 05, 2012

本文将探讨两种具体的实现方法, 并对不同的方法做简单的性能对比.

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://item.taobao.com/item.htm?id=14392877692
http://item.taobao.com/item.htm?id=16231676302
http://item.taobao.com/item.htm?id=17037160462
http://item.taobao.com/item.htm?id=5522416710
http://item.taobao.com/item.htm?id=16551116403
http://item.taobao.com/item.htm?id=14088310973

简要说明下实验设计的原则和性能测试结果的格式: 为保证结果的可靠, 每组实验重复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略优, 但性能提升效果不明显.
数据处理回调延迟5毫秒: Rolling Curl完胜, 性能提升40%左右.
通过上面的性能对比, 在处理URL队列并发的应用场景中Rolling cURL应该是更加的选择, 并发量非常大(1000+)时, 可以控制并发队列的最大长度, 比如20, 每当1个URL返回并处理完毕之后立即加入1个尚未请求的URL到队列中, 这样写出来的代码会更加健壮, 不至于并发数太大而卡死或崩溃. 详细的实现请参考: http://code.google.com/p/rolling-curl/
PHP 相关文章推荐
建立动态的WML站点(三)
Oct 09 PHP
杏林同学录(四)
Oct 09 PHP
菜鸟学PHP之Smarty入门
Jan 04 PHP
PHP编码规范-php coding standard
Mar 16 PHP
PHP与MySQL开发中页面出现乱码的一种解决方法
Jul 29 PHP
PHP 类商品秒杀计时实现代码
May 05 PHP
使用PHP备份MySQL和网站发送到邮箱实例代码
Nov 28 PHP
PHP使用mysqldump命令导出数据库
Apr 14 PHP
YII Framework教程之异常处理详解
Mar 14 PHP
PHP实现用户登录的案例代码
May 10 PHP
Thinkphp5框架使用validate实现验证功能的方法
Aug 27 PHP
php进行md5加密简单实例方法
Sep 19 PHP
PHP输出数组中重名的元素的几种处理方法
Sep 05 #PHP
PHP中使用crypt()实现用户身份验证的代码
Sep 05 #PHP
通过缓存数据库结果提高PHP性能的原理介绍
Sep 05 #PHP
PHP中使用foreach和引用导致程序BUG的问题介绍
Sep 05 #PHP
php循环语句 for()与foreach()用法区别介绍
Sep 05 #PHP
PHP手机号码归属地查询代码(API接口/mysql)
Sep 04 #PHP
PHP获取用户的浏览器与操作系统信息的代码
Sep 04 #PHP
You might like
十天学会php之第四天
2006/10/09 PHP
PHP开发入门教程之面向对象
2006/12/05 PHP
PHP UTF8中文字符截断函数代码
2012/09/11 PHP
解析VS2010利用VS.PHP插件调试PHP的方法
2013/07/19 PHP
回帖脱衣服的图片实现代码
2014/02/15 PHP
PHP网站自动化配置的实现方法(必看)
2017/05/27 PHP
javascript实现图片切换的幻灯片效果源代码
2012/12/12 Javascript
解析js如何获取当前url中的参数值并复制给input
2013/06/23 Javascript
JavaScript设计模式之外观模式实例
2014/10/10 Javascript
jQuery代码实现表格中点击相应行变色功能
2016/05/09 Javascript
jQuery短信验证倒计时功能实现方法详解
2016/05/25 Javascript
AngularJS 如何在控制台进行错误调试
2016/06/07 Javascript
Node.js 异步异常的处理与domain模块解析
2017/05/10 Javascript
微信小程序实现下拉刷新和轮播图效果
2017/11/21 Javascript
微信小程序利用canvas 绘制幸运大转盘功能
2018/07/06 Javascript
JavaScript的Object.defineProperty详解
2018/07/09 Javascript
微信小程序倒计时功能实例代码
2018/07/17 Javascript
详解vue axios二次封装
2018/07/22 Javascript
[05:40]DOTA2荣耀之路6:Wings最后进攻
2018/05/30 DOTA
itchat-python搭建微信机器人(附示例)
2019/06/11 Python
django解决订单并发问题【推荐】
2019/07/31 Python
PyQt5中向单元格添加控件的方法示例
2020/03/24 Python
Python控制台实现交互式环境执行
2020/06/09 Python
HTML5 拖拽批量上传文件的示例代码
2018/03/28 HTML / CSS
详解html5 canvas常用api总结(二)--绘图API
2016/12/14 HTML / CSS
Html5 Canvas动画基础碰撞检测的实现
2018/12/06 HTML / CSS
html5 datalist 选中option选项后的触发事件
2020/03/05 HTML / CSS
Antonioli美国在线商店:时尚前卫奢华
2019/07/29 全球购物
Kappa英国官方在线商店:服装和运动器材
2020/11/22 全球购物
如何进行有效的自我评价
2013/09/27 职场文书
经贸专业毕业生求职信范文
2014/05/01 职场文书
安全施工标语
2014/06/07 职场文书
汉语专业毕业生自荐信
2014/07/06 职场文书
初中军训感想
2015/08/07 职场文书
openstack云计算keystone组件工作介绍
2022/04/20 Servers
vue组件vue-esign实现电子签名
2022/04/21 Vue.js