聊聊Flare應用前後端性能優化問題

兩周前,在給顏值在線的 flame 提交瞭幾個 PR 之後,我將它封裝成瞭容器,用於書簽和在線應用的管理。

但是在遷移個人書簽的過程中,我發覺 flame 在性能上的表現並不是特別好,於是我做瞭一個改良版:flare

寫在前面

在聊 flare 之前,我想先聊聊 flame。

flame 是一個顏值在線的導航頁項目,你可以將你常用的書簽和在線應用存儲在它上面。它由一個波蘭小哥創建,項目地址是 https://github.com/pawelmalak/flame

Flame 默認界面

Flame 默認界面

在試用之後,我覺得項目還不錯,於是稍作調整,封裝瞭一個新的鏡像:https://github.com/soulteary/docker-flame

新封裝的應用

新封裝的應用

在項目文檔中,記錄瞭我的修改:

  • 簡化程序功能和依賴(如K8S),減少軟件包體積,重構瞭一些細節邏輯,簡化應用啟動流程。
  • 重寫瞭天氣獲取邏輯,使用城市名稱替換經緯度來獲取天氣數據。
  • 對程序已有的一些小 BUG 進行瞭修復,支持中文搜索。
  • 對程序進行瞭簡單的漢化。

但是隨著深入使用,我發現頁面有著比較大的性能問題。

因為波蘭小哥采用瞭 SPA 方案,項目內又包含瞭 6700 多個 SVG 圖標、以及若幹網頁字體,並且在接口上有一些小問題,導致瞭頁面體積非常大,接口請求數量也非常多。

甚至當你上傳一些包含元素比較多的 SVG 作為你書簽圖標的時候,由 React 觸發的頁面渲染會造成瀏覽器卡死。

我大概有幾百個書簽需要處理,預估未來書簽數量還會增長,所以我使用程序批量創建瞭接近一千個書簽。然後我發現渲染如此多的書簽,頁面會出現卡頓,甚至在頁面內搜索包含關鍵詞的書簽也會感受到明顯的掉幀。

所以,我決定重新寫一個輕量一些的程序,來解決我的需求。新的項目地址在這裡,如果你好奇的話,可以試試看:https://github.com/soulteary/docker-flare

制作 flare 的過程,其實也是 flame 性能調優的過程。不過在解決問題之前,我們首先得能定位問題有哪些。

應用性能問題分析

關於這個應用的性能優化,其實並不復雜,和傳統應用優化差別不大:優先減少計算量,在實在減少不瞭的情況下使用計算效率更高的方式來解決問題。

不過結合使用場景來說,在分析技術問題之前,可以先從功能入手。

對於我不適用的功能

首先從功能上看,我不需要這個應用與 Docker 集成,提供“服務發現”功能。比如在我啟動容器後,這個應用會自動將新啟動的容器作為書簽或者應用進行添加。

其次,在擁有自己的 SSO 服務之後,我也不再需要使用簡單的賬號密碼登錄之類的功能,所以這個功能也可以去掉。

最後,關於書簽數據的存儲,我覺得在缺少用戶體驗非常棒的 Web 編輯器的前提下,可能不如配置聲明的方式更易於管理和維護。(你可以使用任何你喜歡的編輯器來更新和維護內容,你可以使用 Git 或者任何你喜歡的方式,以白盒形式保存你自己的數據)。

基於上面的變化,我大概可以少寫幾個部分的代碼:容器(Docker & K8S)集成、登錄鑒權、應用和書簽,以及書簽分類的 “CRUD”。

前端架構中的問題

Flame 項目中,作者使用都是 create-react-app 腳手架創建的項目,項目依賴為: React v17 + TypeScript + Redux,為瞭提供簡潔一致的圖標,作者在前端引入瞭 Templarian/MaterialDesign-JS,一個被精心處理過的 DOM 結構非常簡單的 SVG 圖標庫。

在使用構建工具打包、服務端 GZip 壓縮之後,需要傳輸接近 1MB 的資源,原始腳本程序體積接近 3MB。相對膨大的程序體積導致瞭頁面加載和執行時間都會比較長。比如頁面頁面首次渲染時間在 1s 上下浮動,多數情況下會超過 1s,完成時間一般都在 1.5s 以上。可能是作者對於服務端程序開發不夠熟悉,雖然在前端進行應用配置更新時會復用接口,但是在內容頁面展示時,會調用多達8個接口。此外,為瞭在頁面中展示和更新天氣信息,波蘭小哥還使用瞭 WebSocket 來進行數據交互。

Flame 應用性能概覽

Flame 應用性能概覽

其他的問題,在文章前面已經提到過瞭,就不贅述瞭。

後端架構中的問題

項目使用的技術棧為 Node.js,Web 框架為市占率非常高的 Express 的最新版本,ORM 框架選擇的則是 Sequelize,數據存儲落地為 SQLite3 。上面的選擇粗看問題不大,如果應用不需要公開提供瀏覽訪問,應該不會出現任何性能問題。

但是,如果我們仔細觀察服務響應,會發現有一些請求的響應的時間非常長,比如頁面資源、比如對於頁面至關重要的 JS 程序資源,它們的獲取都消耗瞭接近 400ms。

Flame 網絡請求記錄

Flame 網絡請求記錄

此外,前端發起瞭多次請求來獲取數據,結合數據存儲使用 SQLite,如果提供公開內容訪問,很容易遇到性能瓶頸。

針對應用進行改進

當我們清楚瞭解到上面的問題之後,比如容易采取的方案是:基於原程序進行重構調整,簡化前端請求、合理拆分模塊、處理資源加載和執行時機,調整數據存儲和處理方式,提高服務端響應能力等組合拳。然而,這會是最簡單和收益最高的方案嘛

調整前端實現

如果說在需要交互的頁面程序中使用 MVVM 框架會有較高的收益和性價比,那麼在缺少多端組件代碼復用、沒有服務端渲染需求的場景下,使用這類框架則是一個性價比不高的選擇。

或許有同學會問,如果不使用 React、Vue、Angular 這類框架,難道在 2022 年還要再拾起 jQuery 等老的工具嗎?雖然可以,但其實在近乎於純展示的場景下,我們可以脫離 JS 來實現業務功能和簡單的交互,比如自動獲取焦點、菜單按鈕的激活狀態變化、甚至是帶有動畫效果的天氣圖標。

所以,在調整實現的時候,其實還有一個選擇:不使用任何腳本

Flare 無腳本實現的渲染效率

Flare 無腳本實現的渲染效率

在完成程序之後,我們可以看到從服務器獲取整個頁面數據、結構解析、樣式計算、元素佈局、頁面繪制的完整時間在 33ms(包含瞭 idle 等待時間),其中關鍵流程的時間消耗加起來不到 10ms,而完成頁面渲染的時間更是縮短到瞭 1.65ms。

在得到瞭頁面快速渲染能力之後,即使不使用瀏覽器針對資源進行緩存,加速渲染,我們也能夠做到頁面切換的“無刷新”瀏覽(因為渲染速度足夠快)。

調整後端實現

雖然我非常喜歡使用 Node.js,以往也分享過你所未知的3種Node.js代碼優化方式,但是,為瞭能夠低成本提高高性能的資源響應,這裡進行技術棧切換是必要的:比如 Golang。

在使用 Golang 簡化程序功能後,程序對於每個請求的響應基本能夠保持在幾毫秒的水平(受限於網絡傳輸),相比較之前大概下降瞭2~3個量級。頁面關鍵的 DOM ContentLoad 時間更是縮短到原來的八分之一。

Flare 優化過後批量請求狀況

Flare 優化過後批量請求狀況

結合上面的前端優化提到的渲染時間來粗略估計,從資源下載到渲染加起來都不到 10ms,如果不是瀏覽器的一些限制,繪制幀率應該能夠遠超 60 幀,進一步滿足我們實現“即使刷新瞭也比沒一些沒刷新的實現還順滑”。

上面的實現中,我將頁面圖標請求和頁面文檔進行瞭拆分,在書簽數量和圖標種類不多的場景下,或許並不是最優的方案,但是一旦書簽數量級到幾百、上千之後,你會發現圖標拆分可以極大地提升性能。

當然,為瞭滿足數量比較少的場景,我也對合並輸出進行瞭實現,算上網站 favicon 獲取,一共隻有兩個請求。在書簽不是很多的時候,渲染性能甚至比文檔和資源拆分輸出效率更好。

Flare 請求合並模式下的網絡請求

Flare 請求合並模式下的網絡請求

圖標資源優化

Flame 使用的方案是讀取後端接口配置,從前端腳本中動態創建 SVG 圖標並插入文檔中,Flare 程序默認的方式則是將 SVG 和文檔拆分,以應對大量書簽狀況下的頁面性能問題。

雖然解決瞭頁面性能問題,但是服務端 IO 問題卻會伴隨而來,所以這裡還需要處理資源在服務端的釋放和讀取問題,盡量將資源的磁盤 IO 變為零。

聽起來比較玄乎,但其實結合代碼生成的方式,還是蠻好實現的。當然,因為 Go 存在自動 GC,所以在不同的資源被使用的時候,會出現大量內存的分配,影響效率,這裡可以考慮使用持久化方案來解決問題,處理起來挺有意思的,受限於篇幅和主題就不展開啦。有一部分我在前兩篇文章中提到瞭,關於 Golang 嵌入資源的使用和優化。

前段時間折騰 Go Emed 的記錄

前段時間折騰 Go Emed 的記錄

比如,在不針對 HTTP 服務實現做任何優化、限制運行資源為兩核心的前提下,僅優化資源 IO 後,能達到穩定 3ms 輸出資源,每秒提供2萬7千次以上的響應服務。

容器鏡像的優化

除瞭常規優化之外,容器時代的應用,鏡像優化也是非常關鍵的。容器優化方式,我在前面的文章反復提過多次,所以也不再展開瞭,感興趣可以自行翻閱之前的內容。

# docker images | grep fla                 
soulteary/flare       0.1.1      22b18ad73c66        12MB
soulteary/flaume      2.2.0      b39fffc0ca81        152MB
pawelmalak/flame      2.2.0      fa47c93c0af6        179MB
pawelmalak/flame      2.0.0      729b0fcea7f0        190MB

可以看到,相比較原版程序,優化後的程序在本地解壓後的尺寸大概是之前十五到十六分之一。

額外的優化

如果我們使用 lighthouse 針對 Flame 前端實現進行測試,能夠看到前端程序在實現上的一些小問題,得分雖然四個環綠三個,但是隻有一個環是綠色的。

Flame 應用 Lighthouse 得分

Flame 應用 Lighthouse 得分

在重新實現的過程中,除瞭簡化結構,調試實現之外,還順手將這四個圈都打到瞭滿分(Chrome 版本 v97+)。

Flare 應用 Lighthouse 得分

Flare 應用 Lighthouse 得分

最後

聊到這裡,相信你已經瞭解瞭我是怎麼做的啦,如果你對 Flare 感興趣,並且也需要一個簡單的導航程序,可以訪問項目 https://github.com/soulteary/docker-flare,來親自上手試試。

到此這篇關於Flare應用前後端性能優化的文章就介紹到這瞭,更多相關Flare性能優化內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: