摘要:不支持多線程模式和回調處理,因此內部腳本都是同步阻塞式的,如果你發起一個的請求,那么程序就會阻塞,直到請求返回結果,才會繼續執行代碼。參考資料手冊手冊預定義常量中實現多線程請求詳解每次使用同時并發多少請求合適簡書多線程及原理
后端服務開發中經常會有并發請求的需求,比如你需要獲取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 )
本例中使用到的預定義常量:PHP并發請求耗時對比
CURLM_CALL_MULTI_PERFORM: (int) -1
CURLM_OK: (int) 0
第一次請求使用上面的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內部限制,超過最大并發會導致失敗。
在我做的測試中,發起2000個相同的請求,并輸出每一個請求的響應結果。測試結果2000個請求共有366個成功,前331個均成功,在331-410次序之間共有35個成功的,第410個請求之后全部失敗。因此我們一定要注意并發數的限制,不要超過300個,或者你可以自己在自己的機器上做一下測試,來制定你的閾值。
使用之前,請一定要注意并發數限制!!超時時間
為了防止慢請求影響整個服務,可以設置CURLOPT_TIMEOUT來控制超時時間,防止部分假死的請求無限阻塞進程處理,最后打死機器服務。
CPU負載打滿在代碼示例中,如果持續查詢并發的執行狀態,會導致cpu的負載過高,所以,需要在代碼里加上usleep(50000);的語句。
同時,curl_multi_select也可以控制cpu占用,在數據有回應前會一直處于等待狀態,新數據一來就會被喚醒并繼續執行,減少了CPU的無謂消耗。
PHP手冊 curl_multi_init:http://php.net/manual/zh/func...
PHP手冊 curl預定義常量:http://php.net/manual/zh/curl...
PHP中foreach curl實現多線程:http://www.111cn.net/phper/ph...
Doing curl_multi_exec the right way:http://www.adrianworlddesign....
Segmentfault PHP cURL請求詳解:https://segmentfault.com/a/11...
CSDN 每次使用curl multi同時并發多少請求合適:https://blog.csdn.net/loophom...
簡書 Curl多線程及原理:https://www.jianshu.com/p/f50...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/30837.html
摘要:官方對的解釋是進程管理器。對并發訪問的處理進程和線程從代碼級別來講不支持多線程操作,不能像等語言一樣可以編寫多線程代碼。 關于本篇文章的部分糾正,請參考這篇文章:http://www.cppblog.com/woaido... 首先搞清楚php-fpm與cgi的關系 CGI CGI是一個web server與cgi程序(這里可以理解為是php解釋器)之間進行數據傳輸的協議,保證了傳遞的...
摘要:下文如無特殊聲明將使用進程同時表示進程線程。收到數據后服務器程序進行處理然后使用向客戶端發送響應。現在各種高并發異步的服務器程序都是基于實現的,比如。 并發 IO 問題一直是服務器端編程中的技術難題,從最早的同步阻塞直接 Fork 進程,到 Worker 進程池/線程池,到現在的異步IO、協程。PHP 程序員因為有強大的 LAMP 框架,對這類底層方面的知識知之甚少,本文目的就是詳細介...
并發大家都知道是什么情況,這里說的是并發多個請求搶占同一個資源,直接上實例吧 請求:index.php?mod=a&action=b&taskid=6處理: $key = a_b::.$uid._.$taskid; $v = $redis->get($key); if($v == 1){ $redis->setex($key,10,1); //處理邏輯省略 } 邏輯看來還可以,結果...
摘要:這中情況聽過很多,在開發過程中也沒刻意去模擬實驗過。這就讓兩個以上的并發請求得到控制必須成功獲取鎖才能繼續。 1.并發問題并發大家都知道是什么情況,這里說的是并發多個請求搶占同一個資源,直接上實例吧 請求:index.php?mod=a&action=b&taskid=6處理: $key = a_b::.$uid._.$taskid; $v = $redis->get($key); i...
閱讀 2732·2021-11-22 13:54
閱讀 1068·2021-10-14 09:48
閱讀 2295·2021-09-08 09:35
閱讀 1558·2019-08-30 15:53
閱讀 1170·2019-08-30 13:14
閱讀 609·2019-08-30 13:09
閱讀 2526·2019-08-30 10:57
閱讀 3341·2019-08-29 13:18