PHP純異步非阻塞框架:swPromise
swPromise 是基于 swoole 的 PHP promise 框架。
在日常的使用場景中,PHP一般用作接口聚合層。一個業務請求可能會串行的請求多個接口A->B->C,此時如果接口B的響應時間較慢(關鍵性業務,需要有較長的timeout等待時間),則會導致請求整體的時間過長,嚴重降低系統的響應能力。考慮到這個業務場景下,進程的主要時間用在等待網絡io返回。如果能夠使用異步編程的方式,則會極大的提升服務的吞吐量(NodeJS的優勢)。
如果某接口響應時間超過往常,會導致php-fpm進程數急劇升高,從而導致大量cpu資源浪費在進程調度上面,甚至導致服務崩潰。swPromise框架是為了解決該問題而開發的。
異步非阻塞模式是實現高性能網絡編程的一種方法。傳統上,為進行異步調用,會在代碼中實現大量的回調函數,導致代碼可讀性與可維護性的急劇下降。為了解決這個問題,主流方案有以下幾種:
-
自定義事件式方案
-
Promise/Deferred
-
高階函數篡改回調函數
-
協程(Generator)
Swoole 是PHP語言的高性能網絡通信框架,提供了PHP語言的異步多線程服務器。 swoole采用自定義事件式方案,為我們提供網絡層基本封裝。基于swoole,可以擴展出業務層的異步開發框架。
tsf (Tencent Server Framework) 是騰訊公司推出的 PHP 協程方案,基于 Swoole+PHP Generator 實現的 Coroutine。該框架使用協程模式,基于swoole與swoole framework開發。實現了真正的異步非阻塞開發模式,同時具有極高的性能。其核心代碼來源于該文章Cooperative multitasking using coroutines (in PHP!) 。 tsf使用了較為復雜的用戶態任務調度邏輯,暫時沒有看到生產環境的使用案例。另外由于使用了swoole framework,也使其略顯重量級。
swPromise的主要處理流程在Core\Async\Promise類中。該類實現了基本的then方法,并通過對promise流程的延遲計算,保證了異步流程的動態控制能力。該框架是一個非常基礎的web框架,目前僅實現通用Future(通用延遲計算)、HttpClientFuture、ResponseFuture三個延遲計算類。
該框架需要配合Swoole、php-http-parser擴展使用,第二個擴展用于解析http協議。
演示代碼
class Handler_Index extends \Core\Handler{ public function run($request, $response){ Promise::create ( Model::getUserInfo ( 'user1', 'haha' ) ) ->then (function(&$promise){ $user1 = $promise->get('user1'); if($user1){ return Model::getUserInfo ( 'user2', 'haha2' ) ->then(function(&$promise){ $user2 = $promise->get('user2'); $promise->accept(['user3'=>$user2['body']]); }); } else $promise->accept(); }) ->then ( Model::getUserInfo ( 'user4', 'haha4' ) ) ->then ( Model::getUserInfo ( 'user5', 'haha5' ) ) ->then ( new ResponseFuture ($response) ) ->start ( new PromiseContext () ); } }
這段流程表明了,先獲取haha這個用戶的信息,寫入上下文的user1字段中。如果獲取到了數據,再獲取haha2這個用戶的信息,寫入上下文user2字段中。并將user2的body字段放入user3字段中。然后獲取haha4和haha5的信息。最后將所有數據輸出到網頁。
可以看到,在第一個then中,通過if條件返回promise對象,實現了對異步流程的動態控制。同樣的,整個流程通過then串聯起來,已經較為接近同步代碼的書寫了。而使用回調的方式,代碼會變得極為恐怖。
并行請求
class Handler_Index extends \Core\Handler{ public function run($request, $response){ Promise::create([ Model::getUserInfo ( 'user1', 'haha' ), Model::getUserInfo ( 'user2', 'haha2' ), ])->then( new ResponseFuture ($response) )->start(new PromiseContext ()); } }
這個請求并行獲得haha與hah2兩個用戶的數據,分布放到user1和user2兩個字段中。
存在問題
其中Handler_Sync實現的就是該框架同步的使用方式。另外,目前reject方法以及異常處理流程均沒有實現,有興趣的朋友可以自行擴展。
目前有一個比較嚴重的bug,如果大量http request沒有完成就自行中斷的話,會導致swoole http server發生錯誤,從而退出。在swoole前面放一個nginx就可以解決問題。
測試方法
啟動
php run.php
測試:
ab -n 10000 -c 100 "http://localhost:9502/async" ab -n 10000 -c 100 "http://localhost:9502/sync"
經過測試,在后端接口響應性能有問題的情況下,swPromise 可以同時處理大量連接,用很低的 cpu 負載等待接口數據返回。