PHP異步的玩法
PHP是世界上最好的語言,但是總被“同行們”吐槽不支持異步。其實我們要實現異步也非常簡單,之前看到鳥哥的一篇寫PHP異步執行的博文 PHP實現異步調用方法研究,這篇文章還是08年的,到今天PHP發展快10年了,對於異步調用也有了更多新的玩法。
一. 先說說鳥哥文章中的幾種玩法:
一是通過渲染前端頁麵,使用js執行Ajax,這種方式現在還適用。隻是受限於業務場景,因為隻能在瀏覽器中調用,遇到接口請求就不行了。
二是通過popen()方法打開一個指向進程的管道,每個請求會多起一個進程。忽略進程來看最主要的原因是數據的傳輸特別不方便,使用場景有限。
三是使用CURL擴展,通過設置timeout超時參數,能實現離弦之箭的效果。不過這種方法會主動斷開連接。被調用的服務如果有做連接檢測,也會中斷服務端腳本的執行。比如我們請求 微信的某個費時接口(20s),我們調用1s就斷開連接,微信端是否會維持請求執行20S是不可控的。所以這種方法不推薦大家使用。
四方法與CURL類似,通過fsockopen創建socket連接訪問遠程服務,不循環獲取請求結果。一樣會有三中連接被斷開的問題。
二. PHP發展了這麼多年對異步支持方麵都有哪些改進?
CURL擴展已支持毫秒配置,將 CURLOPT_TIMEOUT 改為 CURLOPT_TIMEOUT_MS 即可生效(cURL 版本 >= libcurl/7.21.0,老服務器要檢查版本),但還是我前麵說的需要服務端配合,不然接口的調用結果不可控。
CURL擴展已支持並發,我們能一次訪問N個接口,執行時間取最長接口的時間。比如我們能一次訪問 京東支付(1s),微信支付(1.2s),支付寶(0.8s)不同服務的三個接口,總耗時才1.2s。詳細用法 curl_multi_init
類似Node.js的異步IO框架Swoole,能很好的實現異步調用;不過Swoole理論上不能算PHP框架,他算是PHP功能的擴展。所以除非項目都用Swoole寫,不然也是享受不到異步IO的福利。
對yield的支持,能實現調度器的功能,寫單進程的服務時能大展拳腳,特別是實現協程,異步更不在話下。不過在多進程的web服務上沒有太大的使用場景,看未來會不會有新的玩法吧。
當然還有很多新的特性,這裏不再細說,總之PHP越是被黑越是能快速發展。
三. 最好的異步實現方法
我們都知道PHP是支持多進程編程的,那完全可以新建一個進程去實現異步的調用。比如調用popen()方法,但是管道的方式傳參異常麻煩,不過多進程這個方法是絕對可行的。如果要實現多進程的功能,毫無疑問我們會選擇PHP官方提供的 pcntl 擴展,PHP默認會安裝pcntl擴展,如果代碼運行提示找不到pcntl擴展,可自行到php-src下載,選擇好版本通過phpize安裝即可。代碼如下
<?php
/**
* User: layne.xfl
* Date: 2017/5/12
* Time: 下午01:24
*/
class Arrow{
static $instance;
/**
* @return static
*/
public static function getInstance(){
if (null == Arrow::$instance)
Arrow::$instance = new Arrow();
return Arrow::$instance;
}
public function run($rb){
$pid = pcntl_fork();
if($pid > 0){
pcntl_wait($status);
}elseif($pid == 0){
$cid = pcntl_fork();
if($cid > 0){
//這裏放空
}elseif($cid == 0){
$rb();
}else{
exit();
}
}else
{
exit();
}
}
}
//離弦之箭---調用方法
$time_out = 30;
Arrow::getInstance()->run(function() use ($time_out){
//這裏寫我們要執行的代碼
sleep($time_out);
});
我給這個功能取了一個很生動的名字--離弦之箭。代表異步調用,我們的弓箭射出去後並不關心它的結果因為發送這個動作做了就行。比如打個10M的log,通知10個人(發10條短信)。
代碼說明:首先Arrow類是個單例類,減少多次調用的開銷。run()方法傳遞的是一個匿名函數,這樣我們能非常方便的傳遞參數,並且保留上下文。
這個類最難的地方在於多進程的處理。因為我們要盡可能快的將數據返回給用戶,所以主進程越快結束越好。但是我們又需要子進程來執行我們耗時的操作,執行完退出才行。如果不等子進程執行完就將父進程退出會出現什麼結果呢?結果就是子進程會常駐內存變成僵死進程。那我們有什麼辦法讓子進程執行完之後就自動結束呢?答案是很難……那麼兒子進程這麼不聽話,孫子進程會不會聽話一點呢??答案是孫子進程執行結束後會被係統進程回收並銷毀(還是孫子聽話)。所以我在代碼中使用了如下方法:當前請求進程fork出子進程,子進程fork出孫子進程,主進程和子進程都先行退出,最後由孫子進程來執行耗時操作,最後完美的解決了僵死進程問題。
當然這個方法的缺點就是調用的時候會多產生一個php-fpm的進程。關於php-fpm進程的管理和規劃又是另一個話題了。擴展閱讀 PHP進程間通信
我的博客-原文鏈接:PHP異步的的玩法
最後更新:2017-05-17 15:31:18