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 相关文章推荐
php socket方式提交的post详解
Jul 19 PHP
PHP操作MongoDB时的整数问题及对策说明
May 02 PHP
PHP生成Gif图片验证码
Oct 27 PHP
php生成扇形比例图实例
Nov 06 PHP
php实现快速排序的三种方法分享
Mar 12 PHP
PHP中的日期加减方法示例
Aug 21 PHP
PHP利用hash冲突漏洞进行DDoS攻击的方法分析
Mar 26 PHP
给PHP开发者的编程指南 第一部分降低复杂程度
Jan 18 PHP
深入浅析PHP无限极分类的案例教程
May 09 PHP
PHP实现大数(浮点数)取余的方法
Feb 18 PHP
php 广告点击统计代码(php+mysql)
Feb 21 PHP
PHP哈希表实现算法原理解析
Dec 11 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
摩卡咖啡
2021/03/03 咖啡文化
支持oicq头像的留言簿(一)
2006/10/09 PHP
利用static实现表格的颜色隔行显示
2006/10/09 PHP
php cli 方式 在crotab中运行解决
2010/02/08 PHP
探讨PHP删除文件夹的三种方法
2013/06/09 PHP
php打乱数组二维数组多维数组的简单实例
2016/06/17 PHP
关于php支持的协议与封装协议总结(推荐)
2017/11/17 PHP
轻轻松松学JS调试(不下载任何工具)
2010/04/14 Javascript
基于jQuery的js分页代码
2010/06/10 Javascript
JavaScript的类型简单说明
2010/09/03 Javascript
JavaScript+CSS控制打印格式示例介绍
2014/01/07 Javascript
jQuery+ajax中getJSON() 用法实例
2014/12/22 Javascript
jQuery使用hide方法隐藏指定元素class样式用法实例
2015/03/30 Javascript
js实现交换运动效果的方法
2015/04/10 Javascript
jquery插件jquery.dragscale.js实现拖拽改变元素大小的方法(附demo源码下载)
2016/02/25 Javascript
js实现文字滚动效果
2016/03/03 Javascript
Vue2 配置 Axios api 接口调用文件的方法
2017/11/13 Javascript
JS中touchstart事件与click事件冲突的解决方法
2018/03/12 Javascript
基于Vue和Element-Ui搭建项目的方法
2019/09/06 Javascript
微信小程序自定义底部弹出框功能
2020/11/18 Javascript
python实现应用程序在右键菜单中添加打开方式功能
2017/01/09 Python
Windows下安装Django框架的方法简明教程
2018/03/28 Python
Python爬虫框架scrapy实现downloader_middleware设置proxy代理功能示例
2018/08/04 Python
python获取地震信息 微信实时推送
2019/06/18 Python
python实现查找所有程序的安装信息
2020/02/18 Python
Python3 利用face_recognition实现人脸识别的方法
2020/03/13 Python
简单几步用纯CSS3实现3D翻转效果
2019/01/17 HTML / CSS
html5跳转小程序wx-open-launch-weapp踩坑
2020/12/02 HTML / CSS
应届生财务管理求职信
2013/11/06 职场文书
双十佳事迹材料
2014/01/29 职场文书
农业局学习党的群众路线教育实践活动心得体会
2014/03/07 职场文书
警校毕业生自我评价
2014/04/06 职场文书
干部竞争上岗演讲稿
2014/09/11 职场文书
董事长助理岗位职责
2015/02/11 职场文书
年度考核登记表个人总结
2015/03/06 职场文书
Win11 25163.1010更新补丁KB5016904推送,测试服务验证管道(附更新修复汇总)
2022/07/23 数码科技