一種語言的極限:PHP
Toby
Toby

在 2020 年 3月尾的時候,我向內部其他幫忙開發 ArOZ Online Beta 系統的工程師(Programmer / 程式員 / 開發者)宣布 ArOZ Online 系統將會停止支援 PHP 。你可能會問:為甚麼要終止支援 PHP? 這系統的核心不是一向也是由 php 開發的嗎?

Apache + PHP 是一個好用而且適合大型系統開發的程式語言

開始開發 ArOZ Online Beta 版時,在選擇到底應該選擇使用 PHP 或是 Nodejs 開發 ArOZ Online 系統。當時在網上也找到不少意見是說如果要開發大型系統, php 的 project structure (項目結構)的確是會比較適合。再之後,我亦在網上找到了一篇講述為甚麼 Nodejs 不適合開發大型系統的文章,讓這個想法得以確實。然而,這問題仍然比我想像中的複雜,我直接跳到結論來看:

PHP 是適合開發 靜態 大型系統

甚麼是靜態系統?一般常見的 CMS、BMS 如 warehouse management system (倉庫管理系統), Learning management system (學習管理系統)等。這些系統不需要太大量的即時互動性,資料只需要在頁面載入的一刻提供就可以了,不必要即時對頁面更新。

PHP 的設計本來就是這個用途,而一開始 ArOZ Online 系統也是只提供這個簡單的用途:

  1. 在頁面載入的時候提供一個音樂 / 影片列表
  2. 當用戶點擊媒體之後,JavaScript 把該媒體載入(用 Apache modXSendFile)
  3. 播放

這個設計在 ArOZ Online Alpha 跟 Beta 初階段也沒甚麼問題,畢竟就只是一個簡單的影片網頁而已。

當 PHP 遇上網頁桌面系統

到了 ArOZ Online Beta 開發的中期,Virtual Desktop Mode (虛擬 / 網頁桌面模式)被開發出來了。由於桌面的互動性設計(例如使用者拉動桌面上的圖示之類的),客戶端對伺服器的請求數目多了很多,由原本只是使用者有按按鈕才會做一次 request 變成了每 5 秒做一次請求,看看有沒有桌面圖示或是檔案變動。如果你在 ArOZ Online Beta 的桌面模式打開 Developer Console,你將會看到以下一大堆的 Ajax Request。當然,這種 request 方法完全說不同是 best practice。

桌面模式下超多次的 AJAX Request
在 Windows 的工作管理員明顯能看到每次 AJAX Request 時的 CPU 負載尖端(Load Spike)

然而,桌面的難題還是透過不斷 request 來算是暫時解決了。可是真正讓我發現 PHP 真的不夠用的部分是 File Explorer (檔案管理員)的部分。

PHP 與 檔案管理員 — 一種語言的極限

對,就是因為要開發一個基於 PHP 的檔案管理員,就把 PHP 這個語言推到了它的能力極限。

ArOZ Online Beta 下使用 PHP 開發的檔案管理員

這個工作管理員一開始是使用 100% 純 PHP 開發而成。但是如果你對 PHP + Apache 有一點的開發經驗,你就會知道一個 PHP 在處理的時候是會把另一個 PHP request 卡住。即是說,伺服器每次只能處理一個 request。當你把這個 request 用作處理 file operation 如 檔案移動、複制等等,如果檔案是比較小的還好,但是如影片檔案(1 – 2GB)一個的話,你整個伺服器就卡住了。

就是在這個時候我發現了 Golang

就是這個時候,我學會了基本的 Golang。這個時候我為 ArOZ Online 的 File Explorer 開發了三大系統:

fsexec / fszip / fsconv

這裡我只解釋 fsexec,fszip 跟 fsconv 的工作原理一樣所以就不作解釋了。簡單來說,系統由這樣

純 PHP 的方案

變成了這樣

PHP + Golang 的背景檔案工作方案

然而,由於 File Explorer 本身也已經有一個 timer 去處理和更新伺服器端的檔案列表,再加上它也有一個 worker (JavaScript)去比較檔案列表是否有不同,所以整套系統的完整圖看上去長這樣:

完整的 File Explorer 系統架構圖

就是因為這套系統的高複雜程度,Apache + PHP 的負擔非常的大,雖然說不是到了卡到不能用的地步,但是你看到每一次 File Operation CPU 就飄到 40 – 60% 怎樣說也不好受。

然而,這還沒有到我要正式放棄 PHP 轉去用 Golang 的地步,真正給這個結構最後一擊的是 aobws,ArOZ Online Beta WebSocket Communication Protocol

PHP 與 WebSocket

基本上當初說到要在 PHP 的系統上加 WebSocket 我的反應是這樣的:

老實說,這不可能吧?

不,這是可行的,你沒聽過 Ratchet 嗎?

(三小?)不,我放棄了。我用 Golang 寫

那,你怎樣跟 PHP SESSION 合拼做登入?

JWT 總算可以了吧?

就是這樣,ArOZ Online Beta 的 jwt 系統就是這樣被幹了出來。由於它是在背景運行的,我們把它暫名為 ShadowJWT,順便加點中二的感覺。

ArOZ Online Beta 的 JWT 設定界面

然而,Golang 要做 JWT authentication 一點都不容易,先不要說 Golang 本身處理 JSON 就已經夠麻煩了,現在還要用來處理登入,結果在一堆亂七八糟的設定和用 gorilla websocket 的 example 亂砌之後,寫成了現在附帶在系統裡面的 aobws 初版。

你以為問題這麼簡單的就解決了?不,因為 jwt 的問題每次用 websocket 的模組在 connect 之前都會彈這麼一個視窗

ArOZ Online JWT 權限請求界面

嘛,就結論而言,東西再拼右砌,到處都是膠水黏著的模塊,最後只會讓系統越來越爛。作為我第一個開發的大型系統,這是一開始完全沒想到的問題。

最後怎麼辦?是時候 Deprecate PHP 支援了

就是這樣,經過一些簡單的討論後,我還是決定把系統後台斬掉用 Golang 重練了。而這個故事的後續我還是留待下一篇文章再來說吧。