首頁  >  文章  >  後端開發  >  為速度而生:PHP 與Golang 的合體 —— RoadRunner

為速度而生:PHP 與Golang 的合體 —— RoadRunner

青灯夜游
青灯夜游轉載
2022-09-23 19:40:455751瀏覽

為速度而生:PHP 與Golang 的合體 —— RoadRunner

在過去的十年中,我們一直在為 財富 500 強公司 以及用戶人數不超過 500 人的企業開發應用程式。一直以來,我們的工程師主要使用 PHP 來開發後端。但兩年前,出現了一些問題不僅嚴重影響了我們的產品效能,還影響了它們的可擴展性 —— 因此我們將 Golang (Go) 引入了我們的技術堆疊。

幾乎同時,我們發現 Go 不僅允許我們創建更大的應用程序,並且能夠將效能提高多達 40 倍。有了它,我們能夠擴展使用 PHP 編寫的現有產品,並透過結合兩種語言的優勢來改進它們。

我們將透過大量的Go 和PHP 經驗告訴你,如何用它來解決實際的開發問題,以及我們如何把它變成一個工具,來消除與 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 環境的缺點和低效性

如果你從事PHP 的專業開發,那麼你就知道從哪裡開始創建一個新專案— 選擇框架。它是一個用於依賴注入、ORM、轉換和模板方法的函式庫。當然,所有使用者輸入的資料都可以方便地放在一個物件中(Symfony / HttpFoundation 或 PSR-7)。這些框架很棒!

但一切都有它的代價。在任何企業框架中,為了處理一個簡單的使用者請求或存取資料庫,您必須載入至少幾十個文件,建立許多類,並解析多個配置。但最糟糕的是,在每個任務完成後,您需要重置所有內容並重新啟動:您剛剛啟動的所有程式碼都將變得無用,在它的幫助下,您將無法處理另一個請求。把這件事告訴任何用其他語言寫的程式設計師 —— 你會看到他臉上的困惑。

多年來,PHP 工程師一直在尋找解決此問題的方法,他們使用了延遲載入技術、微幀、最佳化程式庫、快取等。但最終,您仍然必須放棄整個應用程序,重新開始*(譯者註:隨著PHP7.4 中預先加載的出現,這個問題將得到部分解決)

##一個PHP 進程能處理多個請求嗎?

您可以編寫持續時間超過幾分鐘的 PHP 腳本(最多幾小時或幾天):例如 Cron 任務、CSV 解析器、佇列處理程序。所有這些工作遵循一個模式:他們取得一條任務,處理完它,然後取得下一個任務。程式碼常駐在記憶體中,因此避免了額外的操作來載入框架和應用程序,節約了寶貴時間。

但是開發長時間運行的腳本並不是那麼容易。任何錯誤都會殺死進程,記憶體溢位會導致崩潰,而且不能用 F5 來偵錯程式了。

自 PHP 7 後情況有所改善:可靠的垃圾收集器出現了,它變得更容易處理錯誤,核心的擴充可以避免記憶體洩漏。是的,工程師仍然需要仔細處理記憶體並記住程式碼中的狀態的問題(有哪種語言能讓你可以不關注這些事情?)當然,在 PHP 7 中,驚喜並不多。

是否可以採用一種 常駐 PHP 腳本的模型,將其用於處理 HTTP 請求等更瑣碎的任務,從而消除對每個請求都從頭開始下載所有內容的需要?

要解決這個問題,首先需要實作一個伺服器應用程序,該應用程式可以接收 HTTP 請求並將它們逐個重定向到 PHP worker,而不是每次都殺死它。

我們知道我們可以用純 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 之間分配任務

在互動機制實作之後,我們開始思考如何更好地將任務轉移到 PHP 進程中。當任務到達時,應用程式伺服器必須選擇一個空閒的 worker 來執行它。如果 worker 程序因錯誤而終止或“死亡”,我們將清除它並創建一個新的。如果 worker 程序成功執行,我們會將它返回到可用於執行任務的 worker 池中。

為速度而生:PHP 與Golang 的合體 —— RoadRunner

為了儲存活躍的worker 進程池,我們使用了一個 緩衝通道 ,為了從池中清除意外「死亡」的工作進程,我們新增了一種追蹤錯誤和worker 進程狀態的機制。

最終,我們得到了一個可以運行的 PHP 伺服器,它能夠處理任何以二進位形式呈現的請求。

為了讓我們的應用程式作為 web 伺服器開始運作,我們必須選擇一個可靠的 PHP 標準來處理任何傳入的 HTTP 請求。在我們的例子中,我們只需將簡單的net /http 請求從Go  轉換 為 PSR-7 格式,以便它可以與目前大多數可用的PHP 框架相容。

由於 PSR-7 被認為是不可變的(有人會說在技術上不是),開發人員必須編寫那些在原則上不將請求視為全域實體的應用程式。這完全符合 PHP 常駐進程的概念。我們的最終實作(尚未收到名稱)如下所示:

為速度而生:PHP 與Golang 的合體 —— RoadRunner

#

RoadRunner - # - 效能PHP 應用程式伺服器

我們的第一個測試任務是一個API 後端,在該後端上,會週期性地出現不可預測的突發請求(比平常更頻繁)。雖然在大多數情況下 nginx capabilities 是足夠的,但是我們經常因為無法在預期的負載增加下快速平衡系統而遇到 502 錯誤。

為解決此問題,我們在 2018 年初部署了第一台 PHP / Go 應用伺服器。並立即取得了驚人的效果!我們不僅完全消除了 502 錯誤,並且還將伺服器的數量減少了三分之二,節省了大量資金並解決了令工程師和產品經理頭痛的問題。 在年中的時候,我們改進了我們的方案,在MIT 許可下將其發佈在GitHub 上,並命名為 

RoadRunner

, 從而強調了它驚人的速度和效率。 RoadRunner 是如何改進你的開發堆疊的

#RoadRunner

 的使用允許我們在Go 端使用中間件net/http ,甚至在請求進入PHP 之前進行JWT 驗證,以及在Prometheus 中處理WebSocket 和全域聚合狀態。

由於內建的 RPC,你可以在不寫擴充的情況下,在 PHP 中開啟任何 Go 函式庫的 API。更重要的是,使用 RoadRunner,你可以部署不同於 HTTP 的新伺服器。範例包括在 PHP 中執行 AWS Lambda 處理器,以建立強大的佇列

選擇器,

 甚至將  

gRPC

 新增至我們的應用程式。

同時使用PHP 和Go ,對解決方案有了穩定的提升,在一些測試中將應用程式效能提高了40 倍,改進了調試工具,實現了與Symfony 框架的集成,並添加了對HTTPS、HTTP/2、插件和PSR-17 的支援。

結論

有些人仍然被過時的 PHP 概念所束縛,認為 PHP 是一種緩慢、繁瑣的語言,只適合在 WordPress 下編寫外掛。這些人甚至還說 PHP 有一個限制:當應用程式變得足夠大時,你必須選擇更「成熟」的語言,並重寫多年累積的程式碼庫。

對於這些問題,我的回答是:再想想。我們相信只是你自己為 PHP 設定了一些限制。你可以用一輩子的時間從一種語言遷移到另一種語言,試圖找到與你的需求完美結合的語言,或者你可以將語言視為工具。像 PHP 這樣的語言,它的假想缺陷可能是其成功的真正原因。如果你將它與另一種語言(如 Go)結合,那麼你將創造出比只使用一種語言更強大的產品。

在交替使用 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中文網其他相關文章!

陳述:
本文轉載於:learnku.com。如有侵權,請聯絡admin@php.cn刪除