原文:《使用Swoole協程一鍵代理PHP-FPM服務》php
原創做者:陳曹奇昊 [學而思網校技術團隊] 1周前 [2020.4.17]前端
在Swoole最新發布的v4.5(RC)版本中,咱們實現了一項很是有意思的新特性,那就是協程版本的FastCGI客戶端。nginx
那麼什麼是FastCGI呢?首先先來一個官方解釋:git
快速通用網關接口(Fast Common Gateway Interface/FastCGI)是一種讓交互程序與Web服務器通訊的協議。github
其實很簡單,你們使用PHP-FPM搭建服務的時候必然少不了前面架一個Nginx丶Apache或者IIS之類的東西做爲代理,咱們應用程序和代理通訊的時候,可能會使用各類各樣的協議(常見的好比瀏覽器使用的是HTTP/1.1,HTTP2,QUIC等),而代理的職責就是把各類協議的請求翻譯成FastCGI來和PHP-FPM通訊,這樣PHP服務就無需關心各類類型協議的解析,而能夠只關心處理請求自己的內容,且FastCGI是二進制協議,相較於HTTP1.x這樣的文本協議,FastCGI能夠說是很是高效。sql
實現了FastCGI客戶端,那麼咱們就能夠直接與PHP-FPM服務進行交互,但這有什麼用呢?瀏覽器
在一個Swoole的異步/協程服務中,咱們沒法容忍任何阻塞的存在,只要有一處調用阻塞,那麼整個服務程序都會退化爲阻塞程序,而此時若是咱們又沒有太多的資源去重構老項目,咱們一般會選擇使用Task進程來解決。安全
Task進程是Swoole異步服務器中專門設計用來執行同步阻塞程序的工做進程,咱們能夠很方便地調用$server->task方法去投遞一個同步阻塞任務給Task進程並當即返回,Task進程在完成後再通知Worker進程接收結果,這樣就構成了一個半異步半同步的服務器。服務器
咱們須要大量的task進程來處理少許的同步阻塞任務,但只須要少許的Worker就能夠處理大量的異步非阻塞任務,這就是多路IO複用技術帶來的好處。swoole
雖然這樣看起來已經很是方便了,但仍是有一些不足,如:不少項目不單是同步阻塞,還只能運行在PHP-FPM語境下;此外,若是是協程服務器或是本身用socket寫的服務器,就沒法使用task功能。那麼這時候協程版本的FastCGI就能夠一展身手了。
首先咱們本地得有一個正在運行的PHP-FPM,默認配置,知道它的地址便可。
而後咱們寫一個世界級的Hello程序,存檔爲/tmp/greeter.php,咱們只需在命令行中輸入:
echo "<?php echo 'Hello ' . (\$_POST['who'] ?? 'World');" > /tmp/greeter.php
而後咱們得確保咱們已經安裝了Swoole擴展,這時候咱們只須要在命令行輸入:
php -n -dextension=swoole -r \ "Co\run(function() { \ echo Co\FastCGI\Client::call('127.0.0.1:9000', '/tmp/greeter.php', ['who' => 'Swoole']); \ });"
就能獲得輸出:
Hello Swoole
這樣一個最簡單的調用就完成了,而且是協程非阻塞的,咱們甚至能夠經過多個客戶端併發調用多個PHP-FPM提供的接口再提供給前端以提升響應速度。
咱們能夠先寫一個sleep程序來模擬同步阻塞的PHP-FPM應用:
<?php #blocking.php sleep(1); echo $_POST['id'] . PHP_EOL;
協程FastCGI支持了PSR風格(並不是規範)的操做方法,咱們也能夠本身手動構造一個HTTP請求傳入,籍此咱們能夠靈活地構造任意FastCGI請求和PHP-FPM程序交互:
use Swoole\Coroutine; use Swoole\Coroutine\FastCGI\Client; use Swoole\FastCGI\HttpRequest; $s = microtime(true); Coroutine\run(function () { for ($n = 0; $n < 2; $n++) { Co::create(function () use ($n) { try { $client = new Client('127.0.0.1', 9000); $request = (new HttpRequest()) ->withScriptFilename('/path/to/blocking.php') ->withMethod('POST') ->withBody(['id' => $n]); $response = $client->execute($request); echo "Result: {$response->getBody()}\n"; } catch (Client\Exception $exception) { echo "Error: {$exception->getMessage()}\n"; } }); } }); $s = microtime(true) - $s; echo 'use ' . $s . ' s' . "\n";
最終程序輸出多是:
Result: 1
能夠看到咱們併發請求兩個阻塞1s的接口,而總耗時僅需1s(其實是MAX(...全部接口響應時間)),並且咱們能夠看到先請求不必定先返回,這一樣也證實了這是一個非阻塞的程序。
固然這裏要注意的是,你能併發的數量取決於你機器上PHP-FPM的工做進程數量,若是工做進程數量不足,那麼請求不得不進行排隊。
協程FastCGI客戶端的到來,至關於咱們的協程應用如今擁有了PHP-FPM這樣一個無比強大穩定的進程管理器做爲Task進程池來完成同步阻塞任務,藉此咱們能夠解決不少問題,如:
有一些協議暫未受到Swoole協程的支持,但卻有可用的同步阻塞的版本(MongoDB、sqlserver等),咱們就能夠經過它放心地投遞給PHP-FPM來完成。
或是你有一個很老的PHP-FPM項目飽受性能困擾又因積重難返而沒法快速重構,咱們仍是能夠藉助它來更平滑地將舊業務遷移到新的異步/協程服務器中。
最強大的是協程FastCGI客戶端還支持一鍵代理功能,能夠將其它HTTP請求對象轉化爲FastCGI請求(目前只支持了Swoole\Http,後續可能加入PSR支持),也能夠將FastCGI響應轉化爲HTTP響應,基於這個特性,咱們能夠作到代理世界上最好的博客程序:
declare(strict_types=1); use Swoole\Constant; use Swoole\Coroutine\FastCGI\Proxy; use Swoole\Http\Request; use Swoole\Http\Response; use Swoole\Http\Server; $documentRoot = '/path/to/wordpress'; // WordPress目錄的絕對路徑 $server = new Server('0.0.0.0', 80, SWOOLE_BASE); $server->set([ Constant::OPTION_WORKER_NUM => swoole_cpu_num() * 2, Constant::OPTION_HTTP_PARSE_COOKIE => false, Constant::OPTION_HTTP_PARSE_POST => false, Constant::OPTION_DOCUMENT_ROOT => $documentRoot, Constant::OPTION_ENABLE_STATIC_HANDLER => true, Constant::OPTION_STATIC_HANDLER_LOCATIONS => ['/'], ]); $proxy = new Proxy('127.0.0.1:9000', $documentRoot); $server->on('request', function (Request $request, Response $response) use ($proxy) { $proxy->pass($request, $response); }); $server->start();
撇開一些配置項的設置,整個代理的核心提取出來其實就只有這樣一句代碼
(new Proxy('127.0.0.1:9000', $documentRoot))->pass($request, $response);
而後咱們就能夠在瀏覽器中訪問localhost:
圖示爲本地已搭建好的WordPress站點
協程FastCGI客戶端,咱們能夠在 https://github.com/swoole/library 倉庫查看它的源碼,在README中能夠找到現成的Docker構建命令和配套演示程序讓咱們快速上手體驗它。
此外,經過查看源碼咱們不難發現,協程FastCGI客戶端是徹底使用PHP代碼編寫、基於協程Socket實現的,因爲FastCGI是高效的二進制協議,咱們使用PHP代碼來進行解析也不會有太大的開銷(而HTTP1.x這樣的文本協議就不行,越是人類友好的協議,對機器來講就越不友好)。
包括不少Swoole的其它組件如:WaitGroup、全自動鏈接池、協程Server等等,都是使用PHP編寫的,PHP編寫的組件具備內存安全、開發高效的特色,而且Swoole內核將這些PHP組件內嵌到了擴展中,開發者是無感知的,安裝擴展後就能當即使用這些組件而無需引入額外的包管理。
即便FastCGI客戶端是純PHP編寫的,壓測性能和nginx仍在一個量級,這也證實了PHP的性能瓶頸並不老是在於PHP代碼自己,不少時候是因爲同步阻塞的IO模型致使的。
目前PHP編寫的組件在Swoole中佔比還不高,將來咱們但願能引入更多PHP編寫的內部組件來解決功能性的需求,而只有PHP難以知足的一些高性能的需求(如各類複雜協議的處理)才考慮使用C++實現。