在過去的十年中,我們一直在為 財富 500 強公司 以及用戶人數不超過 500 人的企業開發應用程式。一直以來,我們的工程師主要使用 PHP 來開發後端。但兩年前,出現了一些問題不僅嚴重影響了我們的產品效能,還影響了它們的可擴展性 —— 因此我們將 Golang (Go) 引入了我們的技術堆疊。
幾乎同時,我們發現 Go 不僅允許我們創建更大的應用程序,並且能夠將效能提高多達 40 倍。有了它,我們能夠擴展使用 PHP 編寫的現有產品,並透過結合兩種語言的優勢來改進它們。
我們將透過大量的Go 和PHP 經驗告訴你,如何用它來解決實際的開發問題,以及我們如何把它變成一個工具,來消除與 PHP 死亡模型 相關的一些問題。
#在講述 Go 如何改善 PHP 死亡模型前,先了解常規 PHP 開發環境。
通常,應用程式運行於 nginx 和 PHP-FPM 上。 nginx 處理靜態請求,而動態請求則被重新導向給 PHP-FPM,並由其執行 PHP 程式碼。也許你用的是 Apache 和 mod_php,但是他們原理相同,運作起來只有細微的差別。
看看 PHP-FPM 是如何執行程式碼的。當收到請求,PHP-FPM 初始化 PHP 子程序,並將請求的詳細資訊轉送給它,作為其狀態的一部分(_GET, _POST, _SERVER 等)。
在 PHP 腳本執行期間,狀態將無法更改,因此只能透過一種方式取得一組新的輸入資料:清除進程記憶體並再次初始化它。
這種效能模型有許多優點。你不需要太擔心記憶體消耗,所有進程都是完全隔離的,如果其中一個進程「死亡」,它將自動重新創建,並且不會影響其他進程。但是,當你嘗試擴展應用程式時,這種方式會有缺點產生。
如果你從事PHP 的專業開發,那麼你就知道從哪裡開始創建一個新專案— 選擇框架。它是一個用於依賴注入、ORM、轉換和模板方法的函式庫。當然,所有使用者輸入的資料都可以方便地放在一個物件中(Symfony / HttpFoundation 或 PSR-7)。這些框架很棒!
但一切都有它的代價。在任何企業框架中,為了處理一個簡單的使用者請求或存取資料庫,您必須載入至少幾十個文件,建立許多類,並解析多個配置。但最糟糕的是,在每個任務完成後,您需要重置所有內容並重新啟動:您剛剛啟動的所有程式碼都將變得無用,在它的幫助下,您將無法處理另一個請求。把這件事告訴任何用其他語言寫的程式設計師 —— 你會看到他臉上的困惑。
多年來,PHP 工程師一直在尋找解決此問題的方法,他們使用了延遲載入技術、微幀、最佳化程式庫、快取等。但最終,您仍然必須放棄整個應用程序,重新開始*(譯者註:隨著PHP7.4 中預先加載的出現,這個問題將得到部分解決)
我們知道我們可以用純 PHP(PHP-PM)或 C 擴充(Swoole)來寫 web 伺服器。儘管每種方法都有其優點,但這兩種選擇都不適合我們 —— 我想要更多的東西。我們需要的不僅僅是一個 web 伺服器 —— 我們希望得到一個解決方案,可以使我們避免與 PHP 中的「重啟動」相關的問題,同時可以輕鬆地為特定的應用程式進行調整和擴展。也就是說,我們需要一個應用程式伺服器。
Go 可以幫助解決這個問題嗎?我們知道它可以,因為這種語言將應用程式編譯成單一的二進位檔案; 它是跨平台的; 使用自己的平行處理模型(並發)和用於處理HTTP 的函式庫; 最後,我們可以把更多的開源庫整合到我們的程式中。
首先,有必要確定兩個或多個應用程式之間如何相互溝通。
例如,使用 Alex Palaestras 的 go-php 函式庫,可以實作 PHP 和 Go 進程 (如 Apache 中的 mod_php) 之間的記憶體共用。但是這個函式庫的功能限制了我們使用它來解決問題。
我們決定使用另一種更常見的方法:透過使用 sockets /pipelines 來建立進程之間的互動。這種方法在過去十年中已經證明了其可靠性,並且在作業系統層級得到了很好的優化。
首先,我們創建了一個簡單的二進位協議,用於在進程之間交換資料和處理傳輸錯誤。在最簡單的形式中, 這種類型的協定類似於 一個具有固定大小的packet 頭 (在我們的範例中為17 個位元組) 的netstring ,其中包含的資訊有packet 的類型,其大小和二進位遮罩的信息,用來檢查資料的完整性。
在 PHP 端,我們使用了 pack 函數 ,在 Go 端,使用了 編碼 / 二進位 函式庫。
有一個協定對我們來說有點過時,我們加入了直接 從 PHP 呼叫 net /rpc Go 服務 的功能。這個功能在後面的開發中對我們有很大的幫助,因為我們可以輕鬆地將 Go 庫整合到 PHP 應用程式中。這項工作的結果可以在我們的另一個開源產品 Goridge# 中看到。
在互動機制實作之後,我們開始思考如何更好地將任務轉移到 PHP 進程中。當任務到達時,應用程式伺服器必須選擇一個空閒的 worker 來執行它。如果 worker 程序因錯誤而終止或“死亡”,我們將清除它並創建一個新的。如果 worker 程序成功執行,我們會將它返回到可用於執行任務的 worker 池中。
為了儲存活躍的worker 進程池,我們使用了一個 緩衝通道 ,為了從池中清除意外「死亡」的工作進程,我們新增了一種追蹤錯誤和worker 進程狀態的機制。
最終,我們得到了一個可以運行的 PHP 伺服器,它能夠處理任何以二進位形式呈現的請求。
為了讓我們的應用程式作為 web 伺服器開始運作,我們必須選擇一個可靠的 PHP 標準來處理任何傳入的 HTTP 請求。在我們的例子中,我們只需將簡單的net /http 請求從Go 轉換 為 PSR-7 格式,以便它可以與目前大多數可用的PHP 框架相容。
由於 PSR-7 被認為是不可變的(有人會說在技術上不是),開發人員必須編寫那些在原則上不將請求視為全域實體的應用程式。這完全符合 PHP 常駐進程的概念。我們的最終實作(尚未收到名稱)如下所示:
#為解決此問題,我們在 2018 年初部署了第一台 PHP / Go 應用伺服器。並立即取得了驚人的效果!我們不僅完全消除了 502 錯誤,並且還將伺服器的數量減少了三分之二,節省了大量資金並解決了令工程師和產品經理頭痛的問題。 在年中的時候,我們改進了我們的方案,在MIT 許可下將其發佈在GitHub 上,並命名為
RoadRunner, 從而強調了它驚人的速度和效率。 RoadRunner 是如何改進你的開發堆疊的
#RoadRunner的使用允許我們在Go 端使用中間件net/http ,甚至在請求進入PHP 之前進行JWT 驗證,以及在Prometheus 中處理WebSocket 和全域聚合狀態。
甚至將
gRPC新增至我們的應用程式。
同時使用PHP 和Go ,對解決方案有了穩定的提升,在一些測試中將應用程式效能提高了40 倍,改進了調試工具,實現了與Symfony 框架的集成,並添加了對HTTPS、HTTP/2、插件和PSR-17 的支援。
對於這些問題,我的回答是:再想想。我們相信只是你自己為 PHP 設定了一些限制。你可以用一輩子的時間從一種語言遷移到另一種語言,試圖找到與你的需求完美結合的語言,或者你可以將語言視為工具。像 PHP 這樣的語言,它的假想缺陷可能是其成功的真正原因。如果你將它與另一種語言(如 Go)結合,那麼你將創造出比只使用一種語言更強大的產品。結論
有些人仍然被過時的 PHP 概念所束縛,認為 PHP 是一種緩慢、繁瑣的語言,只適合在 WordPress 下編寫外掛。這些人甚至還說 PHP 有一個限制:當應用程式變得足夠大時,你必須選擇更「成熟」的語言,並重寫多年累積的程式碼庫。
在交替使用 Go 和 PHP 之後,我們可以說我們很喜歡它們。我們不打算犧牲其中一個來換取另一個,相反,我們會想辦法從這個雙重架構中獲得更多利益。
###英文原文網址:https://sudonull.com/post/6470-RoadRunner-PHP-is-not-created-to-die-or-Golang-to-the-rescue## #######翻譯網址:https://learnku.com/php/t/61733#########推薦學習:《###PHP影片教學###》###以上是為速度而生:PHP 與Golang 的合體 —— RoadRunner的詳細內容。更多資訊請關注PHP中文網其他相關文章!