為什麼Swoole可以加速php

daryl發表於2019-02-16

前言

最近在研究Swoole,原來一直聽別人在說Swoole可以加速,一直都是懵逼的。在研究了Swoole之後,我有了一些自己的理解。

PHP-CGI 的黑歷史

對於 PHP 處理網路請求,大家基本上也都是再用 CGI 的方式來做的。那麼,什麼是 CGI 呢。

CGI

CGI,全稱 Common Gateway Interface,中文稱作“公共閘道器介面”。也許有很多人認為 CGI 是一個程式,沒錯,曾經的我也是這麼認為的。直到我從《圖解HTTP》開始細細地研究HTTP協議之後,我才知道,原來 CGI 是一種協議。任何程式語言,都可以實現 CGI,所以任何語言都可以作為網站的後臺語言(扯遠了)。

PHP-CGI

上面說了,CGI 是一個協議,所以,PHP 有自己對 CGI 的實現,那就是 PHP-CGI。可是呢,隨著技術的發展,人們開始意識到,PHP-CGI 的效能不是那麼盡如人意。我們知道,PHP 在執行的時候,是依賴配置檔案 php.ini的。所以,每當 PHP-CGI 開始工作的時候,它是完完全全的一個新程式,它需要重新載入配置檔案並初始化,這就造成了很大的資源和時間的浪費。

FastCGI

那麼,怎麼才能避免這種浪費呢,聰明的程式設計師們想出了另外一種方法:我們為什麼不預先載入好配置,然後,每一個執行的任務只需要複製當前的程式,不就能避免上面的浪費了麼。於是, FastCGI 便橫空出世。

FastCGI,全稱 Fast Common Gateway Interface,中文譯作“快速公共網管介面”。沒錯,這又是個協議。當然,這個協議並不是因為 PHP 才有的。

Apache (httpd)

幾乎所有的 Web 容器都實現了 FastCGI 的功能。首先是 httpd。對於 PHP 來說,httpd 是通過自身來實現一個 FastCGI 的模組的。它會預先載入好 php.ini 檔案中的配置。待到有請求進入需要 PHP 處理時,PHP 就不需要再對 php.ini 重新載入了。這也就是每改動過 php.ini 後都要重啟 httpd 服務的原因。

Nginx 與 php-fpm

php-fpm 也是 FastCGI 的一種實現。通常我們是將 NginxPHP 處理部分代理到 php-fpm 的埠上,交給 php-fpm 來處理。而 php-fpm 同樣是通過預先載入配置,然後給到子程式的方式的,它會對程式做一些管理。

Swoole

辣麼問題來了,php-fpm 雖然實現了 FastCGI,但是,它在處理請求的時候,依然要重新執行一個指令碼,像 Laravel 一樣的框架,一開始就要載入辣麼多依賴和檔案,依然是一個不小的開銷。我們看一下 Laravelpublic/index.php 的原始碼。

require __DIR__.`/../bootstrap/autoload.php`;
$app = require_once __DIR__.`/../bootstrap/app.php`;
$kernel = $app->make(IlluminateContractsHttpKernel::class);
$response = $kernel->handle(
    $request = IlluminateHttpRequest::capture()
);
$response->send();
$kernel->terminate($request, $response);

看看前面兩條語句,這需要載入多少個依賴啊,這都是大把大把的時間和資源啊,每一次請求都需要載入一邊,真是心疼啊。

那麼,我們為什麼不能像之前一樣,能夠不重新載入配置檔案的 FastCGI ,來一個不用載入這麼多的依賴的方式呢?

當然可以啦,這時候 Swoole 就派上用場了。既然是通過 $app->make 的方式來生成一個新的 Kernel 物件,那麼 Application 的物件 $app 自然是不會有什麼改變的了。所以,我們可以在收到請求之前,就把 $app 給生成好,這樣就會快了,不是麼?我們可以對它進行一個簡單的改造。

require __DIR__.`/../bootstrap/autoload.php`;
$app = require_once __DIR__.`/../bootstrap/app.php`;
$serv = new SwooleServerHttp(`127.0.0.1`, 9501);
$serv->on(`request`, function ($req, $res) use ($app) {
    $kernel = $app->make(IlluminateContractsHttpKernel::class);
    $response = $kernel->handle(
        $request = IlluminateHttpRequest::capture()
    );
    $res->end($response);
    $kernel->terminate($request, $response);
});
$serv->start();

好了,我們現在就可以通過執行這個指令碼來監聽9501埠了。然後就像 Nginx 配置 php-fpm 一樣來配置它就可以了。這樣我們可以看到,在收到請求之前,就已經把依賴載入乾淨了,剩下的就是處理請求了。

當然我的這個改動很簡陋,根本無法用於生產環境的,只是提供一個例子。

後記

以上只是我自己的理解和對我自己的理解進行的總結。對於 Swoole 我還在探索當中,因為它需要的只是實在是太多了,需要一點一點積累。本文可能有不對的地方,歡迎各位大神來拍磚!

相關文章