后端服务开发中经常会有并发请求的需求,比如你需要获取10家供应商的带宽数据(每个都提供不同的url
),然后返回一个整合后的数据,你会怎么做呢?在PHP
中,最直观的做法foreach
遍历urls
,并保存每个请求的结果即可,那么如果供应商提供的接口平均耗时5s
,你的这个接口请求耗时就达到了50s
,这对于追求速度和性能的网站来说是不可接受的。这个时候你就需要并发请求了。为了提高性能,可以采用curl提供的curl_multi_*族函数实现简单的并发。
curl_multi 代码实现
以下简单demo,使用POST方式请求数据
<?php
//多并发数据请求
function multiRequest($urls){
$ch = array();
$res = array();
$conn = array();
//创建批处理cURL句柄
$mh = curl_multi_init();
//初始化多个请求句柄为一个
foreach ($urls as $i => $url) {
//创建一对cURL资源
$conn[$i] = curl_init();
//设置URL和相应的选项
curl_setopt($conn[$i], CURLOPT_URL, $url['url']);
curl_setopt($conn[$i], CURLOPT_HEADER, 0);//这里不要header,加块效率
// 设置数据通过字符串返回,而不是直接输出
curl_setopt($conn[$i], CURLOPT_RETURNTRANSFER, 1);
curl_setopt($conn[$i], CURLOPT_TIMEOUT, 5);//设置超时
//302跳转
curl_setopt($conn[$i], CURLOPT_FOLLOWLOCATION, 1);
//增加句柄
curl_multi_add_handle($mh, $conn[$i]);
curl_setopt($conn[$i], CURLOPT_POST, 1);//post提交方式
curl_setopt($conn[$i], CURLOPT_POSTFIELDS, json_encode($url['data'],JSON_UNESCAPED_UNICODE));
curl_multi_add_handle($mh, $conn[$i]);
}
//执行批处理句柄
$active = null;
//防卡死写法:执行批处理句柄
//检测操作的初始状态是否OK,CURLM_CALL_MULTI_PERFORM为常量值-1
do {
//返回的$active是活跃连接的数量,$mrc是返回值,正常为0,异常为-1
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
//如果还有活动的请求,同时操作状态OK,CURLM_OK为常量值0
while ($active && $mrc == CURLM_OK) {
// 持续查询状态并不利于处理任务,每50ms检查一次,此时释放CPU,降低机器负载
usleep(50000);
// 如果批处理句柄OK,重复检查操作状态直至OK。select返回值异常时为-1,正常为1(因为只有1个批处理句柄)
if (curl_multi_select($mh) != -1) {
do {
$mrc = curl_multi_exec($mh, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
}
}
//获取返回结果
foreach ($urls as $i => $url) {
//获取当前解析的cURL的相关传输信息
//$info = curl_multi_info_read($mh);
//获取请求头信息
$heards = curl_getinfo($conn[$i]);
//var_dump($heards);
//获取输出的文本流
$res[$i] = curl_multi_getcontent($conn[$i]);
// 移除curl批处理句柄资源中的某个句柄资源
curl_multi_remove_handle($mh, $conn[$i]);
//关闭cURL会话
curl_close($conn[$i]);
}
//关闭全部句柄
curl_multi_close($mh);
//var_dump($res);
return $res;
}
$urls = array(
array(
'url'=>'http://192.168.0.180/alterUser',
'data'=>array('lockerCode'=>'byk001','BindKeys'=>3)
),
array(
'url'=>'http://192.168.0.181/alterUser',
'data'=>array('lockerCode'=>'byk002','BindKey'=>3,5,6)
)
);
multiRequest($urls);
?>
在该并发请求中,先创建一个批处理句柄,然后将url
的cURL
句柄添加到批处理句柄中,并不断查询批处理句柄的执行状态,当执行完成后,获取返回的结果。
curl_multi 相关函数
curl_multi_init()
函数作用:返回一个新cURL批处理句柄
成功返回cURL批处理句柄,失败返回false
curl_multi_add_handle($mh, $ch)
curl_multi_exec($mh, $still_running)
函数作用:运行当前 cURL 句柄的子连接
$mh 由curl_multi_init返回的批处理句柄
$still_running 一个用来判断操作是否仍在执行的标识的引用
返回一个定义于 cURL 预定义常量中的 cURL 代码
curl_multi_select ($mh,$timeout = 1.0)
curl_multi_remove_handle ($mh, $ch)
函数作用:移除cURL批处理句柄资源中的某个句柄资源 说明:从给定的批处理句柄mh中移除ch句柄。当ch句柄被移除以后,仍然可以合法地用curl_exec()执行这个句柄。如果要移除的句柄正在被使用,则这个句柄涉及的所有传输任务会被中止。
$mh 由curl_multi_init返回的批处理句柄
$ch 由curl_init返回的cURL句柄
成功时返回0,失败时返回CURLM_XXX中的一个
curl_multi_close ($mh)
curl_multi_getcontent ($ch)
curl_multi 注意事项
并发数限制
curl_multi会消耗很多的系统资源,在并发请求时并发数有一定阈值,一般为
512,是由于
CURL`内部限制,超过最大并发会导致失败。
超时时间
为了防止慢请求影响整个服务,可以设置CURLOPT_TIMEOUT
来控制超时时间,防止部分假死的请求无限阻塞进程处理,最后打死机器服务。
CPU负载打满
在代码示例中,如果持续查询并发的执行状态,会导致cpu
的负载过高,所以,需要在代码里加上usleep(50000);
的语句。同时,curl_multi_select
也可以控制cpu
占用,在数据有回应前会一直处于等待状态,新数据一来就会被唤醒并继续执行,减少了CPU
的无谓消耗。