[轉]使用Swoole協程一鍵代理PHP-FPM服務

原文:《使用Swoole協程一鍵代理PHP-FPM服務php

原創做者:陳曹奇昊 [學而思網校技術團隊] 1周前 [2020.4.17]前端

1、什麼是FastCGI

在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服務進行交互,但這有什麼用呢?瀏覽器

2、Swoole中的Task進程

在一個Swoole的異步/協程服務中,咱們沒法容忍任何阻塞的存在,只要有一處調用阻塞,那麼整個服務程序都會退化爲阻塞程序,而此時若是咱們又沒有太多的資源去重構老項目,咱們一般會選擇使用Task進程來解決。安全

Task進程是Swoole異步服務器中專門設計用來執行同步阻塞程序的工做進程,咱們能夠很方便地調用$server->task方法去投遞一個同步阻塞任務給Task進程並當即返回,Task進程在完成後再通知Worker進程接收結果,這樣就構成了一個半異步半同步的服務器。服務器

咱們須要大量的task進程來處理少許的同步阻塞任務,但只須要少許的Worker就能夠處理大量的異步非阻塞任務,這就是多路IO複用技術帶來的好處。swoole

雖然這樣看起來已經很是方便了,但仍是有一些不足,如:不少項目不單是同步阻塞,還只能運行在PHP-FPM語境下;此外,若是是協程服務器或是本身用socket寫的服務器,就沒法使用task功能。那麼這時候協程版本的FastCGI就能夠一展身手了。

3、使用協程FastCGI客戶端調用PHP-FPM程序

首先咱們本地得有一個正在運行的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項目飽受性能困擾又因積重難返而沒法快速重構,咱們仍是能夠藉助它來更平滑地將舊業務遷移到新的異步/協程服務器中。

4、使用協程FastCGI一鍵代理WordPress

最強大的是協程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站點

5、協程FastCGI客戶端的背後

協程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++實現。

相關文章
相關標籤/搜索