這篇文章帶給大家的內容是關於php如何實現並發請求(程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。
後端服務開發中經常會有並發請求的需求,例如你需要取得10家供應商的頻寬資料(每個都提供不同的url
),然後回傳一個整合後的數據,你會怎麼做呢?
在PHP
中,最直觀的做法foreach
遍歷urls
,並保存每個請求的結果即可,那麼如果供應商提供的介面平均耗時5s
,你的這個介面請求耗時就達到了50s
,這對於追求速度和效能的網站來說是不可接受的。
這個時候你就需要並發請求了。
PHP
請求PHP
是單一進程同步模型,一個請求對應一個進程,I/O
是同步阻塞的。透過nginx/apache/php-fpm
等服務的擴展,才使得PHP提供高並發的服務,原理就是維護一個進程池,每個請求服務時單獨起一個新的進程,每個進程獨立存在。
PHP
不支援多執行緒模式和回呼處理,因此PHP
內部腳本都是同步阻塞式的,如果你發起一個5s
的請求,那麼程式就會I/O
阻塞5s
,直到請求回傳結果,才會繼續執行程式碼。因此做爬蟲之類的高並發請求需求很吃力。
那怎麼來解決並發請求的問題呢?除了內建的file_get_contents
和fsockopen
請求方式,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; }
在該並發請求中,先建立一個批次句柄,然後將url
的cURL
句柄加入到批次句柄中,並不斷查詢批次句柄的執行狀態,執行完成後,取得傳回的結果。
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
並發請求耗時對比第一次請求使用上面的curl_multi_init
方法,並發請求105
次。
第二次請求使用傳統的foreach
方法,遍歷105
次使用curl_init
方法請求。
實際的請求耗時結果為:
刨除download
的約765ms
耗時,單純的請求耗時優化達到了39.83/1.58
達到了25
倍,如果繼續刨除建連相關的耗時,應該會更高。這其中的耗時:
方案1:最慢的一個介面達到了1.58s
2 :105
個介面的平均耗時是384ms
curl_multi
會消耗很多的系統資源,在並發請求時並發數有一定閾值,一般為512
,是由於CURL
內部限制,超過最大並發會導致失敗。
為了防止慢請求影響整個服務,可以設定CURLOPT_TIMEOUT
來控制逾時時間,防止部分假死的請求無限阻塞進程處理,最後打死機器服務。
CPU
負載打滿在程式碼範例中,如果持續查詢並發的執行狀態,會導致cpu
的負載過高,所以,需要在程式碼裡加上usleep(50000);
的語句。
同時,curl_multi_select
也可以控制cpu
佔用,在資料有回應前會一直處於等待狀態,新資料一來就會被喚醒並繼續執行,減少了CPU
的無謂消耗。
相關推薦:
PHP使用curl_multi實作並發請求的方法範例php技巧
#以上是php如何實現並發請求(程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!