这里是文章模块栏目内容页
PHP实现Curl并发请求

后端服务开发中经常会有并发请求的需求,比如你需要获取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_HEADER0);//这里不要header,加块效率
           // 设置数据通过字符串返回,而不是直接输出
           curl_setopt($conn[$i], CURLOPT_RETURNTRANSFER1);
           curl_setopt($conn[$i], CURLOPT_TIMEOUT5);//设置超时

           //302跳转
           curl_setopt($conn[$i], CURLOPT_FOLLOWLOCATION1);
           //增加句柄
           curl_multi_add_handle($mh$conn[$i]);
       
           curl_setopt($conn[$i], CURLOPT_POST1);//post提交方式
           curl_setopt($conn[$i], CURLOPT_POSTFIELDSjson_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);

?>

在该并发请求中,先创建一个批处理句柄,然后将urlcURL句柄添加到批处理句柄中,并不断查询批处理句柄的执行状态,当执行完成后,获取返回的结果。

curl_multi 相关函数

curl_multi_init()

  • 函数作用:返回一个新cURL批处理句柄

  • 成功返回cURL批处理句柄,失败返回false

curl_multi_add_handle($mh, $ch)

  • 函数作用:向curl批处理会话中添加单独的curl句柄

  • $mh 由curl_multi_init返回的批处理句柄

  • $ch 由curl_init返回的cURL句柄

  • 成功返回cURL批处理句柄,失败返回false

curl_multi_exec($mh, $still_running)

  • 函数作用:运行当前 cURL 句柄的子连接

  • $mh 由curl_multi_init返回的批处理句柄

  • $still_running 一个用来判断操作是否仍在执行的标识的引用

  • 返回一个定义于 cURL 预定义常量中的 cURL 代码

curl_multi_select ($mh,$timeout = 1.0)

  • 函数作用:等待所有cURL批处理中的活动连接    

  • $mh 由curl_multi_init返回的批处理句柄    

  • $timeout 以秒为单位,等待响应的时间    

  • 成功时返回描述符集合中描述符的数量。失败时,select失败时返回-1,否则返回超时(从底层的select系统调用).

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句柄    

  • $mh 由curl_multi_init返回的批处理句柄

curl_multi_getcontent ($ch)

  • 函数作用:如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流    

  • $ch 由curl_init返回的cURL句柄    

  • 如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流。

curl_multi 注意事项

并发数限制

curl_multi会消耗很多的系统资源,在并发请求时并发数有一定阈值,一般为512,是由于CURL`内部限制,超过最大并发会导致失败。

超时时间

为了防止慢请求影响整个服务,可以设置CURLOPT_TIMEOUT来控制超时时间,防止部分假死的请求无限阻塞进程处理,最后打死机器服务。

CPU负载打满

在代码示例中,如果持续查询并发的执行状态,会导致cpu的负载过高,所以,需要在代码里加上usleep(50000);的语句。同时,curl_multi_select也可以控制cpu占用,在数据有回应前会一直处于等待状态,新数据一来就会被唤醒并继续执行,减少了CPU的无谓消耗。