前後端分離,目前對於一個前端開發者來說,或者將其放在軟體開發領域的歷史背景下,都是再平常不過的一件事情了。17 年時進行了一個前後端徹底分離方案的實踐,每次想起來這個方案時,總覺得它不夠完美。現在將其寫下來和大家交流,期望可以促進方案的後續完善。
邂逅
自從接觸前端開發,一共接觸到了三種前端專案釋出的方式:
- 通過 FileZilla 直接將前端資原始檔上傳到業務伺服器
- SSH 到業務伺服器,通過 Git 更新前端專案程式碼,然後進行新版本資源的構建
- 將構建好的前端資原始檔推送到 cdn
剛加入丁香園前端團隊時,前後端分離的 SPA 專案的實現方式是:前端和服務端專案是兩個程式碼倉庫,HTML 模板由服務端語言(JSP/PHP)輸出,HTML 引用的前端資源是通過前端資源釋出系統釋出到 cdn 上。涉及到 cdn,就會面臨 cdn 快取的問題。解決 cdn 快取問題的方案是傳統的時間戳機制。具體操作流程是服務端提供一個更新時間戳的介面,當前端每次釋出新版本後,去呼叫更新時間戳的介面。
基於時間戳機制的前端專案釋出流程大致為:
- 在釋出系統進行專案釋出
- 通過相關介面更新時間戳
看上去這個流程很簡單高效,對不對?讓我們來根據實際情況來細化一下這個流程:
- 登入釋出系統
- 在釋出系統點選對應專案的釋出按鈕
- 等待前端資源構建併發布成功
- 資源釋出成功後,呼叫更新時間戳的介面
- 釋出完成
流程細化之後,我們可以把流程分為5步,平均算下來每次前端同學進行一次專案釋出,大概需要3分鐘。在等待發布系統構建資源的時候,前端同學可以並行的去做一些放鬆的事情,比如:去餐吧喝杯咖啡,上個廁所,隨手修個 bug。放鬆之後,更新一下時間戳,新版本釋出便大功告成。接下來便可以進入愉悅的新需求開發之旅了,一切都顯得如此愜意。
去年的我,也是這樣認為的。因為當時在負責的新版調查問卷(SPA)和 Insight(可以看做 n 個 SPA)等專案,大概平均每天釋出一次,所以用來發布的時間成本還是能接受的。
16年年底時,加入了大眾醫學部,開始和夥伴們一起負責來問丁香醫生等專案。專案依舊是前後端分離的 SPA,構建好的前端資源依舊是釋出到 cdn,解決 cdn 快取問題同樣採用的是時間戳機制。一切都是熟悉的配方,熟悉的味道。當參與了一段時間的需求迭代後,我發現事情似乎沒有想象的那麼簡單。
讓我有這種感受的原因,主要有兩點:
- 更新時間戳的介面即使在內網,也是需要鑑權的。並不像調查問卷等專案,直接在瀏覽器訪問一下更新時間戳的介面就可以了。鑑權的方式是需要微信掃碼。
- 需求迭代速度謙虛一點說,很快。僅生產環境,幾乎每天都要釋出至少一次。
此時細化的一次釋出流程變為:
- 登入釋出系統
- 在釋出系統點選對應專案的釋出按鈕
- 等待前端資源構建併發布成功
- 資源釋出成功後,呼叫更新時間戳的介面(需要鑑權)
- 訪問更新時間戳的頁面,頁面彈出鑑權二維碼
- 釋出者拿起手機
- 開啟微信,點選掃一掃,掃描二維碼
- 微信接收到同意授權的模板訊息
- 點選模板訊息進行授權,授權成功後進入更新時間戳頁面
- 點選更新時間戳按鈕
- 等待生效(服務端有快取機制)
- 釋出完成
一次前端釋出的時間,平均在7分鐘左右。在實際工作中,需要在測試環境、(預發環境)、生產環境進行釋出。從團隊的角度來看,管理後臺等專案也在採用這種釋出方式。毛估一下,每週團隊在專案釋出上就需要花費 2 ~ 3 個小時((7min * 2 * 2 * 5)/ 60)。
此外,這種前後端分離的方式還有以下幾個問題:
- HTML 模板開發效率較低。專案所使用的 HTML 檔案需要前端同學寫好後發給後端,後端再進行“套模板”,這種做法本身就有一個流程的複雜度。後端同學如果對 HTML 掌握的不熟練,那麼需要前端同學去跟後端同學結對程式設計,來確保 HTML 的正確,此處有一個溝通成本。專案上線後,如果需要對 HTML 進行改動,需要前端先修改好 HTML,然後把 HTML 發給後端,後端再次進行“套模板”。後端同學修改好 HTML 模板檔案後,可能不會因為這一個改動進行發版,需要跟隨著後端專案的其他改動程式碼一起發版,此時對於前端、測試同學有一個等待的成本。
- cdn 資源有一點點浪費。頁面中所有前端資源是使用同一個時間戳,這意味著每次更新時間戳都會更新頁面中的全部資源引用地址,從而當使用者再次使用應用時,需要重新下載頁面引用的所有資源。而前端的某個新版本,可能僅僅是需要更新部分資原始檔即可。
- 加長了應用的響應時間。這一點同樣是由上一點描述的時間戳機制導致的,重新下載“新”的資源而不是利用瀏覽器的快取,必然會導致使用者需要等待更長的時間。
念念不忘,必有迴響
為了解決上述問題,我設計了一個的方案:
方案說明:
- 前端專案生產環境構建時,將檔名中加入 hash 值。
- 前端資源釋出系統在資源構建成功後,將 index.html 同步到業務伺服器(本著線上專案不做寫操作的原則:會把 HTML 檔案放到後端專案和Node專案目錄之外的地方)。
- 在業務伺服器上新增一個 Node 服務。該服務的作用之一為:收到瀏覽器端首頁的請求時,將 index.html 返回。
- 當使用者訪問應用時,由運維將請求首頁 HTML 的請求轉給 Node 服務,其餘業務介面保持原有方式不變。
方案確定後,在團隊小夥伴的配合下,該方案在一個流量較小的專案上線了。
新的火花
上述方案存在一個問題的:在原有的技術體系中,引入了 Node.js。本質上是在穩定的技術體系下,增加了技術複雜度。因此,在不增加技術複雜度的前提下,需要開始探索新的解決方案。在後端同學的配合下,新方案相比於引入 Node.js 方案改動如下:
- 業務伺服器給前端釋出系統提供同步 HTML 模板檔案的介面,前端釋出系統每次成功構建前端資源後,呼叫該介面將模板檔案同步給業務伺服器。
- 業務伺服器獲取模板檔案後,將檔案內容存入資料庫,持久化儲存模板。在服務重啟和使用者請求時,服務端從資料庫中獲取模板。
- 服務端可以將從資料獲取的模板放到快取中,這樣可以避免高頻的讀取資料庫操作。對應的業務伺服器需要監控資料庫中的資料變化情況,以便於及時更新快取中的模板資源。
方案上線後,前端專案的釋出流程變為:
- 登入釋出系統
- 在釋出系統點選對應專案的釋出按鈕
- 等待前端資源構建併發布成功
前端專案釋出這件事,終於變成了點選一下發布按鈕,整件事情就做好了。整個釋出流程耗時變為不到一分鐘。此外,當前端需要改變 HTML 模板時,也不再需要將檔案發給後端同學,苦苦等待後端專案的發版。
該方案上線後,組內同學在週報中提及到的使用新方案後的感受為:
為了方便前端同學獲取同步模板的進展,在釋出系統中增加了同步過程的提醒:
沒有銀彈
上述方案相比於 JSP/PHP 提供 HTML 模板存在一個問題,就是在前端在構建 HTML 時,(暫時)不能將應用初始化的資料放入 HTML 中。解決方案是服務端提供一個介面,在應用啟動時去呼叫該介面獲取初始化資料。
對於前端載入優化,整體上思路上是需要減少網路請求的,而此方案卻在增加網路請求,這意味著頁面載入時間會變長。但是綜合利弊之後,還是決定採用這個方案。
最終方案上線後,其實心中已經做好了頁面平均載入時間會變長的心理準備,但是有些意外的是,幾天後去 mta 看資料發現全國範圍內平均響應時間縮短了 0.2s 左右。為什麼不升反降,目前我還不能得出一個準確的答案。猜測的一個原因是: hash 值的方案避免了使用者進行不必要的資源更新。
待完善
上述方案雖然做到了前端專案的一鍵釋出,但是還不夠稱作為一個完善的解決方案。因為該方案只是解決了 SPA 型別專案的釋出問題,對於之前“套模板”重 SEO 的專案而言,並不是很適用。(提到 2018 年的技術浪潮下,如何開發一個重 SEO 的網站這個話題,又可以寫一篇文章了,其中的心路歷程還是蠻坎坷的)
言歸正傳,在前端資源釋出系統層面,該方案可以考慮去增加檔案歷史和釋出回滾功能,以備不時只需。
這個方案是和業務線的服務端同學配合實現的,從公司層面來看,可以考慮的點是是否可以將這個方案做成一個通用的服務。
在和團隊的交流時,相學長提出可以將 HTML 引用的資源抽象成 JSON Tree 進行儲存。之前看過一些類似的解決方案,不過目前自己還是更傾向於分開的“更徹底”,這樣可以讓服務端同學更安心的提供介面。
寫在最後
由於水平有限,歡迎大家對此方案提出建議。非常期待。
本文作者:丁香園前端工程師 @志遙