PHP,作為網頁開發的主流語言之一,從誕生到現在已近三十個年頭。至今大部分執行 PHP 的方式大多都採用無狀態的方式執行。傳統的 PHP 執行模式雖然便利和成熟,但在某些高並行處理的場景下可能會產生效能上的問題。
每當我們提到 PHP 的執行方式時,一定會出現 Apache、Nginx 等網頁伺服器軟體。在傳統 PHP 運行模式下每一個 HTTP 請求都會建立一個新的 PHP 處理程序。當使用者發出一個請求時,伺服器軟體會立即通知 PHP 來處理這個請求,一旦請求完成,這個 PHP 程式就會終止。這種隨著請求啟動的執行模式雖然簡單直接,但在效能上卻有著一定的侷限性。在上述無狀態的執行方式下每個請求都是獨立的,伺服器不需保留任何使用者端的狀態訊息。這種設計簡化了開發,同時也意味著每次請求都需要重新載入相同的資源和設定。
而 PHP 是直譯式語言,即在執行時立刻進行原始碼的解釋與執行。因此在每個獨立的請求中,將重複地進行解釋程式碼、初始化資源,與載入系統組態等操作,在高並行請求的情況下,這樣子的資源消耗可能會成為效能瓶頸。
傳統的 PHP 執行模式雖然在大多數情況下都能夠提供穩定和可靠的服務,但在某些高效能或高並行處理的場景中,可能會顯得有些力不從心。隨著技術的演進,高流量的購物網站或票務網站等軟體需求日益複雜,這也促使了「常駐型 PHP」的架構出現在開發者的視野。
在傳統 PHP 架構中,隨著連線的開始與結束,總會重複地進行資料庫連線、系統組態,以及載入框架核心程式碼等工作;常駐型 PHP 開發架構則採用了不同的策略,在這種架構中 PHP 主程式在啟動後就被載入到記憶體中持續執行,並等待新的請求到來。因為不需要在每次請求中都經歷啟動和銷毀 PHP 程序的過程,這大大提高了應用程式的反應速度和整體效能。
目前,主流的代表性技術或開發架構如下:
Swoole 是使用 C 語言撰寫的 PHP 的外掛(extension),透過 Swoole 的相關類別的支援,你能夠輕鬆地以 PHP 開發網路應用程式,無論是短連結的 HTTP 或長連結 Websocket 都是開箱即用的;Swoole 也提供共常式(coroutine)的語法支援,你將能夠透過 PHP 的語法直接進行單一執行緒下的非阻塞應用程式開發。
值得一提的是,OpenSwoole 是從 Swoole 分叉出的新版本,它們有著相異的維護團隊,但卻有著幾乎相同的使用方式。在 OpenSwoole 來到 22.0.0
版本時,命名空間已從 Swoole 遷移至 OpenSwoole,這意味著兩者的程式碼將無法再互相支援。
RoadRunner 是一個使用 Golang 撰寫而成的高效能的 PHP 應用伺服器,它採用 PSR-7 規範處理 HTTP 請求與響應的溝通介面,這也讓它與現代 PHP 框架有著整合上的優勢。
PHP 應用程式在 RoadRunner 中作為常駐的應用程式執行,開發者能夠自由調整同一時間留存在記憶體的程序數量。透過常駐在記憶體中,RoadRunner 避免了 PHP 常見的開銷。
與 Swoole 不同的是,RoadRunner 不是一個 PHP 外掛,而是一個獨立的伺服器。
Workerman 是一款由原生 PHP 開發的事件循環(Event Loop)高效能應用容器,它不像 OpenSwoole 需要倚賴特別的外部 PHP Extension,或是像 RoadRunner 一樣需要一個外部的 Go Server ,它是個完全以 PHP 實作的事件循環高效能框架。
你幾乎可以在你的 PHP 環境中直接 composer require
相關的 Workerman 依賴程式庫,用 20 行以內的程式碼就可以啟動一個 PHP Web 伺服器。同時,它也支援 HTTP、Websocket、TCP、UDP 等多種協議。
上述高效能的 PHP 開發方案有一個同樣的特點,那就是當啟動伺服器時,會建立多個 PHP 子程序來聆聽和處理網路請求。這些子程序獨立執行互不干擾,當有新的請求到來時,主要的伺服器程序會選擇一個閒置的 PHP 子程序進行處理。
本單元「高效能 PHP」將採用 Workerman 作為我們的示範架構,你可以前往 Anser-Tutorial-Workerman 中下載本單元的範例專案,我們將透過這個專案中的環境作為示範基礎。
將上述檔案解壓縮至本地環境後,你應該能看到一個由 Filters、Logs、Orchestrators、Services、System 等資料夾組成的專案。這個專案結構即是 Anser 所推薦的組織模式,所有專案的檔案皆遵循 PSR-4 自動載入標準中關於檔案名稱與資料夾命名的規範。
使用 Command Line 介面定位至 Anser-Tutorial-Workerman 目錄下,以 docker compose up -d 啟動環境。初次啟動可能需要較長時間的建構,你可以參考專案根目錄下的 Dockerfile:
FROM webdevops/php:8.1-alpine
# php-event extension
RUN \
apk add autoconf openssl-dev && apk add build-base &&\
apk add linux-headers && apk add libevent-dev && apk add openldap-dev && apk add imagemagick-dev && \
docker-php-ext-install opcache && docker-php-ext-enable opcache && \
echo '' | pecl install event && \
docker-php-ext-enable --ini-name zz-event.ini event
# SWOW extension
RUN \
git clone https://github.com/swow/swow.git &&\
cd swow/ext && \
phpize && ./configure --enable-swow-ssl --enable-swow-curl && make && \
make install && \
docker-php-ext-enable swow
RUN \
apk add htop
WORKDIR "/"
映象檔基於 webdevops/php:8.1-alpine
進行建構,在預設的情況下這個環境已包含 Workermen 所需的基礎需求,再這個基礎需求之上我們再額外安裝了一些能夠加速 Workerman 的外掛,比如:event
外掛。雖然並不是必需的,但筆者還是在這個 Image 中一同包含。另外,SWOW
外掛是下一章才會使用到的,筆者建議你不用做額外的修改,直接建構即可。
另外,專案根目錄的 docker compose 檔案預設了基本的 port:8085:8080
,這在之後的存取會使用到,請不要忘記這兩個數字。
當環境執行起來後,你可以透過 docker-compose exec app bash
進入到容器內部,接著使用 composer install
將專案所需的依賴檔案給下載回來,此時你的資料夾應該會長的像是這個樣子:
讓我們直接建立一個 PHP 檔案吧?我們就叫它 server.php
:
<?php
require_once './init.php';
use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
// #### http worker ####
$httpWorker = new Worker('http://0.0.0.0:8080');
$httpWorker->onMessage = static function (TcpConnection $connection, Request $request) {
$start = microtime(true);
$connection->send('hello world');
$end = microtime(true);
$time = $end - $start;
print_r(sprintf(
"[%s] %s%s, %.2fms\n",
$request->method(),
$request->host(),
$request->uri(),
$time * 1000
));
};
// Run all workers
Worker::runAll();
當你完成 server.php
檔案的建立之後,你就擁有了一個基礎的 Workerman HTTP 伺服器。在這裡,我們定義了一個 HTTP Worker 並監聽 0.0.0.0:8080 這個位址。每當這個 Worker 收到消息時,它將回傳一個簡單的 'hello world' 字串給與 Client,同時也列印相關的連線資訊至 Terminal。
你可以在容器內透過 php server.php start
啟動伺服器:
當你看到上述畫面代表你已經啟動了你的 Workerman 伺服器,你現在可以打開瀏覽器,然後前往 http://localhost:8085
查看結果。你應該能夠看到 'hello world' 的字串在你的瀏覽器上。
你的 Terminal 應該也能看到相關的連線資訊同步出現:
一點都不困難吧?
當然這僅僅是 Workerman 的冰山一角,你可以依據需求閱讀文件後加入更多的功能。在這個範例中,我們已經完成了最基礎的環境建置以及入門的基本伺服器。
在很多時候,你可能會好奇一些 Method 的實作方式,或者是針對某些類別需要進行二次開發。採用 PHP 外掛的 Swoole 或是以外部伺服器執行的 RoadRunner 在開發的過程中不免讓人霧裡看花,很多時候你無法深探它們是如何完成某些重要的工作的。但 Workerman 不同,你甚至可以閱讀它是如何處理多程序管理、TCP 連線,或者是 Websocket 的交握(handshake)過程,這些基礎的機制都是以 PHP 程式碼完成。
筆者認為 Workerman 與其他高效能 PHP 解決方案相比,不僅提供了一種嶄新的開發模式,還是個建立於原生 PHP 的高效能方案,單就這點就非常值得我們學習。