后端服务开发中经常会有并发请求的需求,比如你需要获取10家供应商的带宽数据(每个都提供不同的url),然后返回一个整合后的数据,你会怎么做呢?

PHP中,最直观的做法foreach遍历urls,并保存每个请求的结果即可,那么如果供应商提供的接口平均耗时5s,你的这个接口请求耗时就达到了50s,这对于追求速度和性能的网站来说是不可接受的。

这个时候你就需要并发请求了。

PHP请求

PHP是单进程同步模型,一个请求对应一个进程,I/O是同步阻塞的。通过nginx/apache/php-fpm等服务的扩展,才使得PHP提供高并发的服务,原理就是维护一个进程池,每个请求服务时单独起一个新的进程,每个进程独立存在。

PHP不支持多线程模式和回调处理,因此PHP内部脚本都是同步阻塞式的,如果你发起一个5s的请求,那么程序就会I/O阻塞5s,直到请求返回结果,才会继续执行代码。因此做爬虫之类的高并发请求需求很吃力。

那怎么来解决并发请求的问题呢?除了内置的file_get_contentsfsockopen请求方式,PHP也支持cURL扩展来发起请求,它支持常规的单个请求:PHP cURL请求详解,也支持并发请求,其并发原理是cURL扩展使用多线程来管理多请求。

PHP并发请求

我们直接来看代码demo:

// 简单demo,默认支持为GET请求
public function multiRequest($urls) {
$mh = curl_multi_init();
$urlHandlers = [];
$urlData = [];
// 初始化多个请求句柄为一个
foreach($urls as $value) {
$ch = curl_init();
$url = $value['url'];
$url .= strpos($url, '?') ? '&' : '?';
$params = $value['params'];
$url .= is_array($params) ? http_build_query($params) : $params;
curl_setopt($ch, CURLOPT_URL, $url);
// 设置数据通过字符串返回,而不是直接输出
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$urlHandlers[] = $ch;
curl_multi_add_handle($mh, $ch);
}
$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($urlHandlers as $index => $ch) {
$urlData[$index] = curl_multi_getcontent($ch);
// 移除单个curl句柄
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
return $urlData;
}

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

curl_multi 相关函数

/** 函数作用:返回一个新cURL批处理句柄
@return resource 成功返回cURL批处理句柄,失败返回false
*/
resource curl_multi_init ( void ) /** 函数作用:向curl批处理会话中添加单独的curl句柄
@param $mh 由curl_multi_init返回的批处理句柄
@param $ch 由curl_init返回的cURL句柄
@return resource 成功返回cURL批处理句柄,失败返回false
*/
int curl_multi_add_handle ( resource $mh , resource $ch ) /** 函数作用:运行当前 cURL 句柄的子连接
@param $mh 由curl_multi_init返回的批处理句柄
@param $still_running 一个用来判断操作是否仍在执行的标识的引用
@return 一个定义于 cURL 预定义常量中的 cURL 代码
*/
int curl_multi_exec ( resource $mh , int &$still_running ) /** 函数作用:等待所有cURL批处理中的活动连接
@param $mh 由curl_multi_init返回的批处理句柄
@param $timeout 以秒为单位,等待响应的时间
@return 成功时返回描述符集合中描述符的数量。失败时,select失败时返回-1,否则返回超时(从底层的select系统调用).
*/
int curl_multi_select ( resource $mh [, float $timeout = 1.0 ] ) /** 函数作用:移除cURL批处理句柄资源中的某个句柄资源
说明:从给定的批处理句柄mh中移除ch句柄。当ch句柄被移除以后,仍然可以合法地用curl_exec()执行这个句柄。如果要移除的句柄正在被使用,则这个句柄涉及的所有传输任务会被中止。
@param $mh 由curl_multi_init返回的批处理句柄
@param $ch 由curl_init返回的cURL句柄
@return 成功时返回0,失败时返回CURLM_XXX中的一个
*/
int curl_multi_remove_handle ( resource $mh , resource $ch ) /** 函数作用:关闭一组cURL句柄
@param $mh 由curl_multi_init返回的批处理句柄
@return void
*/
void curl_multi_close ( resource $mh ) /** 函数作用:如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流
@param $ch 由curl_init返回的cURL句柄
@return string 如果设置了CURLOPT_RETURNTRANSFER,则返回获取的输出的文本流。
*/
string curl_multi_getcontent ( resource $ch )

本例中使用到的预定义常量
CURLM_CALL_MULTI_PERFORM: (int) -1
CURLM_OK: (int) 0

PHP并发请求耗时对比

  1. 第一次请求使用上面的curl_multi_init方法,并发请求105次。
  2. 第二次请求使用传统的foreach方法,遍历105次使用curl_init方法请求。

实际的请求耗时结果为:

刨除download的约765ms耗时,单纯的请求耗时优化达到了39.83/1.58达到了25倍,如果继续刨除建连相关的耗时,应该会更高。这其中的耗时:

  • 方案1:最慢的一个接口达到了1.58s
  • 方案2:105个接口的平均耗时是384ms

这个测试的请求是我的环境的内部接口,所以耗时很短,实际爬虫请求环境优化会更明显。

注意项

并发数限制

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

具体的测试结果我没有做,可以参考别人的文章:每次使用curl multi同时并发多少请求合适

超时时间

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

CPU负载打满

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

参考资料

  1. PHP手册 curl_multi_inithttp://php.net/manual/zh/func...
  2. PHP手册 curl预定义常量http://php.net/manual/zh/curl...
  3. PHP中foreach curl实现多线程http://www.111cn.net/phper/ph...
  4. Doing curl_multi_exec the right wayhttp://www.adrianworlddesign....
  5. Segmentfault PHP cURL请求详解https://segmentfault.com/a/11...
  6. CSDN 每次使用curl multi同时并发多少请求合适https://blog.csdn.net/loophom...
  7. 简书 Curl多线程及原理https://www.jianshu.com/p/f50...

原文地址:https://segmentfault.com/a/1190000016343861

PHP实现并发请求的更多相关文章

  1. 如何配置IIS处理多并发请求及存在的问题

    很多时候多线程能快速高效独立的计算数据,应用比较多. 但今天遇到的多进程下的问题更是让人觉得复杂 多进程下static变量都要失效,就目前的平台和产品static使用是很多的,各种session.ca ...

  2. 查看 Apache并发请求数及其TCP连接状态

    查看 Apache并发请求数及其TCP连接状态 (2011-06-27 15:08:36) 服务器上的一些统计数据: 1)统计80端口连接数 netstat -nat|grep -i "80 ...

  3. 查看 并发请求数及其TCP连接状态【转】

    服务器上的一些统计数据: 1)统计80端口连接数netstat -nat|grep -i "80"|wc -l 2)统计httpd协议连接数ps -ef|grep httpd|wc ...

  4. IIS处理并发请求时出现的问题及解决

    一个ASP.NET项目在部署到生产环境时,当用户并发量达到200左右时,IIS出现了明显的请求排队现象,发送的请求都进入等待,无法及时响 应,系统基本处于不可用状态.因经验不足,花了很多时间精力解决这 ...

  5. Web大规模高并发请求和抢购的解决方案

    电商的秒杀和抢购,对我们来说,都不是一个陌生的东西.然而,从技术的角度来说,这对于Web系统是一个巨大的考验.当一个Web系统,在一秒钟内收到数以万计甚至更多请求时,系统的优化和稳定至关重要.这次我们 ...

  6. 让Windows Server 2008 + IIS 7+ ASP.NET 支持10万并发请求

    原文:http://www.cnblogs.com/dudu/archive/2009/11/10/1600062.html 今天下午17点左右,博客园博客站点出现这样的错误信息: Error Sum ...

  7. [转]让Windows Server 2008 + IIS 7+ ASP.NET 支持10万并发请求

    本文转自:http://www.cnblogs.com/dudu/archive/2009/11/10/1600062.html 今天下午17点左右,博客园博客站点出现这样的错误信息: Error S ...

  8. 让Windows Server 2008 + IIS 7+ ASP.NET 支持10万并发请求(转载)

    转自:http://www.cnblogs.com/dudu/archive/2009/11/10/1600062.html 今天下午17点左右,博客园博客站点出现这样的错误信息: Error Sum ...

  9. 【性能诊断】四、单功能场景的性能分析(RedGate,找到同一个客户端的并发请求被串行化问题)

    问题描述: 客户端js连续发起两个异步http请求,请求地址相同,但参数不同:POST http://*.*.*.*/*****/webservice/RESTFulWebService/RESTFu ...

  10. Jexus 高并发请求的优化技巧 笔记

    Jexus web server 5.1 每个工作进程的最大并发数固定为1万,最多可以同时开启4个工作进程,因此,每台Jexus V5.1服务器最多可以到支持4万个并发连接.但是,按照linux系统的 ...

随机推荐

  1. 获取Class对象方式

    在java中,每个class都有一个相应的Class对象,当编写好一个类,编译完成后,在生成的.class文件中,就产生一个Class对象,用来表示这个类的类型信息.获得Class实例的三种方式: 1 ...

  2. 使用ZooKeeper实现配置同步(转)

    前言 应用项目中都会有一些配置信息,这些配置信息数据量少,一般会保存到内存.文件或者数据库,有时候需要动态更新.当需要在多个应用服务器中修改这些配置文件时,需要做到快速.简单.不停止应用服务器的方式修 ...

  3. 服务器session,Tomcat有自己的session维护机制,apache有自己的session维护机制

    1.SESSION一般不是你说的这种工作方式,你打开一个浏览器,再打开一个,请求同一个网址,然后其中一个登陆,另外一个绝对不会也登陆.SESSION和浏览器本身这个程序就是挂钩的,一般不通过IP和端口 ...

  4. HDU 2912

    直线关于球的多次反射,求最后一次反射点 #include <iostream> #include <cstdio> #include <cstring> #incl ...

  5. Swift学习——变量var和let常量的用法(一)

    Swift中的变量var和let常量 首先介绍一下Swift中的 var 和 let (1)var 是 variable的缩写形式,是变量的意思 ,是可改变的.并非数据类型 比如: 注意每一个语句后面 ...

  6. Aizu/Aoj 0033 Ball

    题目大意: 有编号1到10共10个球,从上方丢下去,入口处可以选择进入左边或者右边,最后10个球全部落下去后如果左右两侧都是从小到大的顺序,则输出YES:否则输出NO. 题目原本的标签枚举,复杂度是2 ...

  7. 19 个必须知道的 VS 快捷键

    本文将为大家列出在Visual Studio中常用的快捷键,正确熟练地使用快捷键,将大大提高你的编程工作效率. 项目相关的快捷键 Ctrl + Shift + B = 生成项目 Ctrl + Alt ...

  8. Empower Developers

     Empower Developers Timothy High THingS ARE uSuAlly EASiER SAid THAn donE, and software architects ...

  9. luogu2766 最长不下降子序列问题 DP 网络流

    题目大意:给定正整数序列x1,...,xn .(1)计算其最长不下降子序列的长度s.(不一定是否连续)(2)计算从给定的序列中最多可取出多少个长度为s的不下降子序列.(序列内每一个元素不可重复)(3) ...

  10. POJ3177 Redundant Paths 图的边双连通分量

    题目大意:问一个图至少加多少边能使该图的边双连通分量成为它本身. 图的边双连通分量为极大的不存在割边的子图.图的边双连通分量之间由割边连接.求法如下: 求出图的割边 在每个边双连通分量内Dfs,标记每 ...