h3. 背景
在網校最初時,接入的網校就幾家,並且課程數據量較小,爲快速開發上線,就採起逐個遍歷課程數據源,進行抓取,而後解析數據並處理,存入到數據庫中。可是隨着網校的接入, 且爲展現更多課程數據信息,字段內容又不斷調整,致使請求的數據變大,抓取的時間變得延長,順序的處理流程,又容易形成若是某個數據源出錯,致使剩下的網校課程數據沒法抓取。
恰逢網校要開發詳情頁,須要調整字段,引入更多數據,抓取的當前框架有些耦合,特別是網絡處理這些,不能支持多併發,擔憂隨着數據量的增大,處理完後,會嚴重致使不斷調整cron中的其餘業務腳本,畢竟依賴最新的數據,如刪除過 期的數據,向引擎推送數據等,將來是打算整合這些腳本,但提升數據抓取與處理的性能,仍是必須作的。
h3. 數據依賴與併發
網校目前的各個數據源都是來自於各個教育機構,於是沒有依賴性,這就在抓取數據時,方便併發處理;若是網校的數據源存在依賴,特別是某條數據源的請求依賴上次某條數據源的處理結果,則將不得不採用串行處理。即便併發,也可併發 請求完全部數據,而後再對數據進行處理,如圖
可是會發現,這種方式並不能利用客戶端等待服務端數據到達的空隙,更好的處理是,當某個數據源的數據到達時,就能當即處理,而不是等待全部的數據源的數據到達時,再逐個處理數據。在目前的網校課程數據源中,不一樣教育機構給出的課程 數差別很大,有些數據可能要持續兩三分鐘,才能請求完,而有些數據幾秒內就可到達,於是誰處處理誰,能更好利用CPU。
h3. 併發的實現
在當前PHP併發的方法中,可使用curl_multi_*系列的方法,也可利用其它人開發的專用併發框架,如鳥哥寫的yar,考慮到學習成本與資源,就直接採用curl_multi_*方法,並且自己也有封裝好的專門用於「誰處處理誰」的框架RollingCurl,
提供更好的網絡控制處理,並提供相關的DEMO,但核心代碼依然是curl_multi_*系列方法的處理:
{code:java}
do {
while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ;
if ($execrun != CURLM_OK)
break;
// a request was just completed -- find out which one
while ($done = curl_multi_info_read($master)) {
// get the info and content returned on the request
...
// send the return values to the callback function.
...
// start a new request (it's important to do this before removing the old one)
...
}
// remove the curl handle that just completed
curl_multi_remove_handle($master, $done['handle']);
}
// Block for data in / output; error handling is done by curl_multi_exec
if ($running)
curl_multi_select($master, $this->timeout);
} while ($running);
{code}
當調用curl_multi_add_handle添加curl句柄時,就會進入do-while大循環代碼,首先執行curl_multi_exec,可是此方法,在do-while循環中被屢次調用,PHP文檔給此方法的解釋是「處理在棧中的每個句柄。
不管該句柄須要讀取或寫入數據均可調用此方法。」,也就是說第一次調用此方法是用於發出HTTP請求數據,當棧中的句柄還有數據須要傳送時,就會返回 CURLM_CALL_MULTI_PERFORM,當返回CURLM_OK只是意味着數據傳送完畢或者沒有數據 可傳送。此方法是不阻塞的,也就是說一旦棧中某個或多個curl句柄有數據讀取或者寫入,就能夠調用此方法傳送數據,並當即返回棧中句柄的活動狀態。
url_multi_info_read方法是檢查批處理句柄中某次傳送數據結束的狀態(當curl_multi_exec調用返回CURLM_OK),可能上次並無數據可傳送,則隊列中並無任何消息,則就會返回false;可能傳送數
據出現錯誤,消息隊列就會有某個批處理句柄出錯的信息,當數據成功完成時,則返回的消息體中的msg字段值會爲CURLMSG_DONE,其餘值都不可用,代表出錯。
若是沒有後面的curl_multi_select方法上述程序也能正常運行,可是就會發現整個程序處於不停空轉中,畢竟網絡傳輸數據佔用時間仍是蠻長的,每次調用curl_multi_exec可能都是當即返回CURLM_OK,而後執行
curl_multi_info_read,又當即返回false,如此繼續。而調用curl_multi_select,就會等待直到多個SOCKET中的某個處於活躍狀態,否則就處於阻塞狀態,直到超時爲止,就不會出現程序忙空轉狀態。
在上述程序代碼中,能夠看到,當某次傳送數據完成後,就會當即處理返回來的數據,而不是待全部句柄的請求數據都完成後,才繼續處理數據,其實這種等待全部請求數據都完成時再處理的情形,也有不少應用場景,如某個頁面的數據, 來自於多個源,只有組裝在一塊兒,才能渲染頁面。到時只須要將調用curl_multi_info_read方法的那塊代碼移除,在do-while循環後添加數據處理的過程便可。
h5. 參考文獻
[php curl document|http://www.php.net/manual/zh/ref.curl.php] [rolling-curl|https://github.com/takinbo/rolling-curl] [php-src|https://github.com/php/php-src] [libcutl-multi API document|http://curl.haxx.se/libcurl/c/libcurl-multi.html] [curl-src|https://github.com/bagder/curl]