你好,哈布爾! 我們在 Badoo 很活躍
近年來,PHP 取得了長足的進步:垃圾收集器得到了改進,穩定性水平也得到了提高 - 今天您可以在 PHP 中編寫守護進程和長期腳本而不會出現任何問題。 這使得 Spiral Scout 能夠走得更遠:RoadRunner 與 PHP-FPM 不同,不會在請求之間清理內存,這提供了額外的效能優勢(儘管這種方法使開發過程變得複雜)。 我們目前正在試驗這個工具,但還沒有任何結果可以分享。 為了讓等待他們變得更有趣, 我們正在發布 Spiral Scout 的 RoadRunner 公告的翻譯。
文章中的方法與我們很接近:在解決問題時,我們也最常使用 PHP 和 Go 的組合,獲得兩種語言的好處,而不是放棄一種語言而選擇另一種語言。
在過去的十年裡,我們為清單中的公司創建了應用程式
幾乎立刻,我們發現 Go 允許我們建立更大的應用程序,效能提高了 40 倍。 有了它,我們能夠擴展用 PHP 編寫的現有產品,透過結合兩種語言的優點來改進它們。
我們將告訴您 Go 和 PHP 的組合如何幫助解決實際的開發問題,以及它如何變成我們的工具,可以消除與
您的日常 PHP 開發環境
在我們討論如何使用 Go 來復興 PHP 垂死的模型之前,讓我們先來看看您的標準 PHP 開發環境。
在大多數情況下,您使用 nginx Web 伺服器和 PHP-FPM 伺服器的組合來執行應用程式。 第一個服務靜態檔案並將特定請求重定向到 PHP-FPM,PHP-FPM 本身執行 PHP 程式碼。 也許您正在使用 Apache 和 mod_php 中不太流行的組合。 但儘管其工作原理略有不同,但原理是相同的。
讓我們來看看 PHP-FPM 如何執行應用程式程式碼。 當請求到達時,PHP-FPM 會初始化 PHP 子程序,並將請求詳細資訊作為其狀態的一部分傳遞(_GET、_POST、_SERVER 等)。
PHP 腳本執行期間狀態無法更改,因此只有一種方法可以獲得一組新的輸入資料:清除進程記憶體並重新初始化。
這種執行模式有許多優點。 你不用太擔心記憶體消耗,所有進程都是完全隔離的,如果其中一個進程死掉了,它會自動重新創建,不會影響其餘進程。 但這種方法在嘗試擴展應用程式時也存在一些缺點。
常規 PHP 環境的缺點與低效率
如果您從事 PHP 專業開發,那麼您知道從哪裡開始新專案 - 透過選擇框架。 它由依賴注入庫、ORM、翻譯和模板組成。 當然,所有使用者輸入都可以方便地放入一個物件(Symfony/HttpFoundation 或 PSR-7)中。 框架很酷!
但凡事都有它的代價。 在任何企業級框架中,要處理簡單的使用者請求或存取資料庫,您將必須載入至少數十個文件,建立大量類別並解析多個配置。 但最糟糕的是,完成每個任務後,您將需要重置所有內容並重新開始:您剛剛啟動的所有程式碼都變得毫無用處,在它的幫助下您將不再處理另一個請求。 把這個告訴任何用其他語言寫的程式設計師,你會看到他臉上的困惑。
PHP 工程師花了數年時間尋找解決這個問題的方法,使用巧妙的延遲加載技術、微框架、優化庫、快取等。但最終,您仍然必須重置整個應用程式並一次又一次地重新開始。 (譯者註:這個問題將隨著
PHP 與 Go 能否承受多個請求?
可以編寫持續時間超過幾分鐘(長達數小時或數天)的 PHP 腳本:例如,cron 任務、CSV 解析器、佇列破壞程式。 它們都根據相同的場景工作:檢索任務,執行它,然後等待下一個任務。 程式碼駐留在記憶體中,節省了寶貴的毫秒時間,因為載入框架和應用程式需要許多額外的步驟。
但開發長期存在的腳本並不那麼容易。 任何錯誤都會完全殺死進程,診斷記憶體洩漏會讓你發瘋,而且你不能再使用 F5 偵錯。
隨著 PHP 7 的發布,這種情況得到了改善:出現了可靠的垃圾收集器,處理錯誤變得更加容易,現在可以保護核心擴充免受洩漏。 確實,工程師仍然需要小心記憶體並注意程式碼中的狀態問題(有一種語言我們不必擔心這些事情嗎?)。 然而,在 PHP 7 中,等待我們的驚喜就更少了。
是否有可能採用使用長期 PHP 腳本的模型,使其適應更瑣碎的任務,例如處理 HTTP 請求,從而消除為每個請求從頭開始加載所有內容的需要?
為了解決這個問題,我們首先需要實作一個伺服器應用程序,它可以接受 HTTP 請求並將它們一一轉發給 PHP Worker,而不必每次都殺死它。
我們知道我們可以用純 PHP (PHP-PM) 或使用 C 擴充 (Swoole) 來寫 Web 伺服器。 儘管每種方法都有自己的優點,但這兩種選擇都不適合我們——我們想要更多的東西。 我們需要的不僅僅是一個 Web 伺服器 - 我們希望得到一個解決方案,可以使我們擺脫與 PHP 的「硬啟動」相關的問題,同時可以輕鬆地針對特定應用程式進行調整和擴展。 也就是說,我們需要一個應用程式伺服器。
Go 可以幫忙解決這個問題嗎? 我們知道它可以,因為語言將應用程式編譯成單一二進位; 它是跨平台的; 使用自己的、非常優雅的平行處理模型(並發)和函式庫來處理 HTTP; 最後,我們將可以使用數千個開源程式庫和整合。
結合兩種程式語言的困難
第一步是確定兩個或多個應用程式如何相互通訊。
例如,使用
我們決定使用另一種更常見的方法:透過套接字/管道在進程之間建立互動。 這種方法在過去的幾十年中已經證明了其可靠性,並且在作業系統層面得到了很好的優化。
首先,我們創建了一個簡單的二進位協議,用於在進程之間交換資料並處理傳輸錯誤。 從最簡單的形式來看,這種類型的協議類似於
在 PHP 端我們使用
在我們看來,一個協議是不夠的 - 所以我們添加了調用的能力
將任務指派給多個 PHP Worker
實現了互動機制後,我們開始思考如何最有效率地將任務轉移到PHP進程。 當任務到來時,應用程式伺服器必須選擇一個空閒的worker來完成它。 如果一個工作進程/進程因錯誤而終止或“死亡”,我們會刪除它並建立一個新的來替換它。 如果工作人員/進程已成功完成,我們會將其返回到可用於執行任務的工作人員池中。
為了儲存我們使用的活躍工人池
結果,我們收到了一個能夠處理任何以二進位形式提出的請求的工作 PHP 伺服器。
為了讓我們的應用程式能夠充當 Web 伺服器,我們必須選擇可靠的 PHP 標準來表示任何傳入的 HTTP 請求。 在我們的例子中,我們只是
由於 PSR-7 被認為是不可變的(有些人會說技術上並非如此),因此開發人員必須編寫從根本上不將請求視為全域實體的應用程式。 這非常符合長壽命 PHP 進程的概念。 我們的最終實作(尚未命名)如下所示:
介紹 RoadRunner - 高效能PHP應用伺服器
我們的第一個測試任務是 API 後端,它會定期遇到意外的請求突發(比平常頻繁得多)。 儘管 nginx 在大多數情況下已經足夠,但我們經常遇到 502 錯誤,因為我們無法足夠快地平衡系統以滿足預期的負載增加。
為了取代此解決方案,我們於 2018 年初部署了第一個 PHP/Go 應用程式伺服器。 我們立刻就得到了令人難以置信的效果! 我們不僅徹底擺脫了 502 錯誤,而且還能夠將伺服器數量減少三分之二,為工程師和產品經理節省了大量資金和頭痛的事情。
到年中,我們已經完善了我們的解決方案,在 MIT 許可下將其發佈在 GitHub 上,並將其命名為
RoadRunner 如何改進您的開發堆疊
應用
由於內建的 RPC,您可以為 PHP 打開任何 Go 庫的 API,而無需編寫擴充包裝器。 更重要的是,RoadRunner 可用於部署新的非 HTTP 伺服器。 範例包括在 PHP 中啟動處理程序
在PHP 和Go 社群的幫助下,我們提高了解決方案的穩定性,在某些測試中將應用程式效能提高了多達40 倍,改進了調試工具,實現了與Symfony 框架的集成,並增加了對HTTPS、HTTP/ 2、插件和PSR-17。
結論
有些人仍然持有過時的觀點,認為 PHP 是一種緩慢、笨重的語言,只適合編寫 WordPress 外掛。 這些人甚至可能會說PHP有一個限制:當應用程式變得足夠大時,你必須選擇更「成熟」的語言並重寫多年來累積的程式碼庫。
對於這一切,我想回答:再想想。 我們相信只有您才能對 PHP 設定任何限制。 你可以花一輩子的時間從一種語言跳到另一種語言,試圖找到最適合你需求的語言,或者你可以開始將語言視為工具。 像 PHP 這樣的語言的明顯缺點實際上可能是其成功的原因。 如果你將它與另一種語言(例如 Go)結合起來,你可以創造比僅限於一種語言更強大的產品。
在使用 Go 和 PHP 的組合之後,我們可以說我們喜歡它們。 我們不打算犧牲其中之一,而是尋找從雙堆疊中獲得更多價值的方法。
UPD:我們歡迎 RoadRunner 的創作者和原始文章的合著者 -
來源: www.habr.com