聊聊Flare應(yīng)用前后端性能優(yōu)化問(wèn)題
兩周前,在給顏值在線的 flame
提交了幾個(gè) PR 之后,我將它封裝成了容器,用于書(shū)簽和在線應(yīng)用的管理。
但是在遷移個(gè)人書(shū)簽的過(guò)程中,我發(fā)覺(jué) flame
在性能上的表現(xiàn)并不是特別好,于是我做了一個(gè)改良版:flare
。
寫(xiě)在前面
在聊 flare 之前,我想先聊聊 flame。
flame 是一個(gè)顏值在線的導(dǎo)航頁(yè)項(xiàng)目,你可以將你常用的書(shū)簽和在線應(yīng)用存儲(chǔ)在它上面。它由一個(gè)波蘭小哥創(chuàng)建,項(xiàng)目地址是 https://github.com/pawelmalak/flame
Flame 默認(rèn)界面
在試用之后,我覺(jué)得項(xiàng)目還不錯(cuò),于是稍作調(diào)整,封裝了一個(gè)新的鏡像:https://github.com/soulteary/docker-flame
新封裝的應(yīng)用
在項(xiàng)目文檔中,記錄了我的修改:
- 簡(jiǎn)化程序功能和依賴(如K8S),減少軟件包體積,重構(gòu)了一些細(xì)節(jié)邏輯,簡(jiǎn)化應(yīng)用啟動(dòng)流程。
- 重寫(xiě)了天氣獲取邏輯,使用城市名稱替換經(jīng)緯度來(lái)獲取天氣數(shù)據(jù)。
- 對(duì)程序已有的一些小 BUG 進(jìn)行了修復(fù),支持中文搜索。
- 對(duì)程序進(jìn)行了簡(jiǎn)單的漢化。
但是隨著深入使用,我發(fā)現(xiàn)頁(yè)面有著比較大的性能問(wèn)題。
因?yàn)椴ㄌm小哥采用了 SPA 方案,項(xiàng)目?jī)?nèi)又包含了 6700 多個(gè) SVG 圖標(biāo)、以及若干網(wǎng)頁(yè)字體,并且在接口上有一些小問(wèn)題,導(dǎo)致了頁(yè)面體積非常大,接口請(qǐng)求數(shù)量也非常多。
甚至當(dāng)你上傳一些包含元素比較多的 SVG 作為你書(shū)簽圖標(biāo)的時(shí)候,由 React 觸發(fā)的頁(yè)面渲染會(huì)造成瀏覽器卡死。
我大概有幾百個(gè)書(shū)簽需要處理,預(yù)估未來(lái)書(shū)簽數(shù)量還會(huì)增長(zhǎng),所以我使用程序批量創(chuàng)建了接近一千個(gè)書(shū)簽。然后我發(fā)現(xiàn)渲染如此多的書(shū)簽,頁(yè)面會(huì)出現(xiàn)卡頓,甚至在頁(yè)面內(nèi)搜索包含關(guān)鍵詞的書(shū)簽也會(huì)感受到明顯的掉幀。
所以,我決定重新寫(xiě)一個(gè)輕量一些的程序,來(lái)解決我的需求。新的項(xiàng)目地址在這里,如果你好奇的話,可以試試看:https://github.com/soulteary/docker-flare
制作 flare 的過(guò)程,其實(shí)也是 flame 性能調(diào)優(yōu)的過(guò)程。不過(guò)在解決問(wèn)題之前,我們首先得能定位問(wèn)題有哪些。
應(yīng)用性能問(wèn)題分析
關(guān)于這個(gè)應(yīng)用的性能優(yōu)化,其實(shí)并不復(fù)雜,和傳統(tǒng)應(yīng)用優(yōu)化差別不大:優(yōu)先減少計(jì)算量,在實(shí)在減少不了的情況下使用計(jì)算效率更高的方式來(lái)解決問(wèn)題。
不過(guò)結(jié)合使用場(chǎng)景來(lái)說(shuō),在分析技術(shù)問(wèn)題之前,可以先從功能入手。
對(duì)于我不適用的功能
首先從功能上看,我不需要這個(gè)應(yīng)用與 Docker 集成,提供“服務(wù)發(fā)現(xiàn)”功能。比如在我啟動(dòng)容器后,這個(gè)應(yīng)用會(huì)自動(dòng)將新啟動(dòng)的容器作為書(shū)簽或者應(yīng)用進(jìn)行添加。
其次,在擁有自己的 SSO 服務(wù)之后,我也不再需要使用簡(jiǎn)單的賬號(hào)密碼登錄之類的功能,所以這個(gè)功能也可以去掉。
最后,關(guān)于書(shū)簽數(shù)據(jù)的存儲(chǔ),我覺(jué)得在缺少用戶體驗(yàn)非常棒的 Web 編輯器的前提下,可能不如配置聲明的方式更易于管理和維護(hù)。(你可以使用任何你喜歡的編輯器來(lái)更新和維護(hù)內(nèi)容,你可以使用 Git 或者任何你喜歡的方式,以白盒形式保存你自己的數(shù)據(jù))。
基于上面的變化,我大概可以少寫(xiě)幾個(gè)部分的代碼:容器(Docker & K8S)集成、登錄鑒權(quán)、應(yīng)用和書(shū)簽,以及書(shū)簽分類的 “CRUD”。
前端架構(gòu)中的問(wèn)題
Flame 項(xiàng)目中,作者使用都是 create-react-app 腳手架創(chuàng)建的項(xiàng)目,項(xiàng)目依賴為: React v17 + TypeScript + Redux,為了提供簡(jiǎn)潔一致的圖標(biāo),作者在前端引入了 Templarian/MaterialDesign-JS,一個(gè)被精心處理過(guò)的 DOM 結(jié)構(gòu)非常簡(jiǎn)單的 SVG 圖標(biāo)庫(kù)。
在使用構(gòu)建工具打包、服務(wù)端 GZip 壓縮之后,需要傳輸接近 1MB 的資源,原始腳本程序體積接近 3MB。相對(duì)膨大的程序體積導(dǎo)致了頁(yè)面加載和執(zhí)行時(shí)間都會(huì)比較長(zhǎng)。比如頁(yè)面頁(yè)面首次渲染時(shí)間在 1s 上下浮動(dòng),多數(shù)情況下會(huì)超過(guò) 1s,完成時(shí)間一般都在 1.5s 以上??赡苁亲髡邔?duì)于服務(wù)端程序開(kāi)發(fā)不夠熟悉,雖然在前端進(jìn)行應(yīng)用配置更新時(shí)會(huì)復(fù)用接口,但是在內(nèi)容頁(yè)面展示時(shí),會(huì)調(diào)用多達(dá)8個(gè)接口。此外,為了在頁(yè)面中展示和更新天氣信息,波蘭小哥還使用了 WebSocket 來(lái)進(jìn)行數(shù)據(jù)交互。
Flame 應(yīng)用性能概覽
其他的問(wèn)題,在文章前面已經(jīng)提到過(guò)了,就不贅述了。
后端架構(gòu)中的問(wèn)題
項(xiàng)目使用的技術(shù)棧為 Node.js,Web 框架為市占率非常高的 Express 的最新版本,ORM 框架選擇的則是 Sequelize,數(shù)據(jù)存儲(chǔ)落地為 SQLite3 。上面的選擇粗看問(wèn)題不大,如果應(yīng)用不需要公開(kāi)提供瀏覽訪問(wèn),應(yīng)該不會(huì)出現(xiàn)任何性能問(wèn)題。
但是,如果我們仔細(xì)觀察服務(wù)響應(yīng),會(huì)發(fā)現(xiàn)有一些請(qǐng)求的響應(yīng)的時(shí)間非常長(zhǎng),比如頁(yè)面資源、比如對(duì)于頁(yè)面至關(guān)重要的 JS 程序資源,它們的獲取都消耗了接近 400ms。
Flame 網(wǎng)絡(luò)請(qǐng)求記錄
此外,前端發(fā)起了多次請(qǐng)求來(lái)獲取數(shù)據(jù),結(jié)合數(shù)據(jù)存儲(chǔ)使用 SQLite,如果提供公開(kāi)內(nèi)容訪問(wèn),很容易遇到性能瓶頸。
針對(duì)應(yīng)用進(jìn)行改進(jìn)
當(dāng)我們清楚了解到上面的問(wèn)題之后,比如容易采取的方案是:基于原程序進(jìn)行重構(gòu)調(diào)整,簡(jiǎn)化前端請(qǐng)求、合理拆分模塊、處理資源加載和執(zhí)行時(shí)機(jī),調(diào)整數(shù)據(jù)存儲(chǔ)和處理方式,提高服務(wù)端響應(yīng)能力等組合拳。然而,這會(huì)是最簡(jiǎn)單和收益最高的方案嘛?
調(diào)整前端實(shí)現(xiàn)
如果說(shuō)在需要交互的頁(yè)面程序中使用 MVVM 框架會(huì)有較高的收益和性價(jià)比,那么在缺少多端組件代碼復(fù)用、沒(méi)有服務(wù)端渲染需求的場(chǎng)景下,使用這類框架則是一個(gè)性價(jià)比不高的選擇。
或許有同學(xué)會(huì)問(wèn),如果不使用 React、Vue、Angular 這類框架,難道在 2022 年還要再拾起 jQuery 等老的工具嗎?雖然可以,但其實(shí)在近乎于純展示的場(chǎng)景下,我們可以脫離 JS 來(lái)實(shí)現(xiàn)業(yè)務(wù)功能和簡(jiǎn)單的交互,比如自動(dòng)獲取焦點(diǎn)、菜單按鈕的激活狀態(tài)變化、甚至是帶有動(dòng)畫(huà)效果的天氣圖標(biāo)。
所以,在調(diào)整實(shí)現(xiàn)的時(shí)候,其實(shí)還有一個(gè)選擇:不使用任何腳本。
Flare 無(wú)腳本實(shí)現(xiàn)的渲染效率
在完成程序之后,我們可以看到從服務(wù)器獲取整個(gè)頁(yè)面數(shù)據(jù)、結(jié)構(gòu)解析、樣式計(jì)算、元素布局、頁(yè)面繪制的完整時(shí)間在 33ms(包含了 idle 等待時(shí)間),其中關(guān)鍵流程的時(shí)間消耗加起來(lái)不到 10ms,而完成頁(yè)面渲染的時(shí)間更是縮短到了 1.65ms。
在得到了頁(yè)面快速渲染能力之后,即使不使用瀏覽器針對(duì)資源進(jìn)行緩存,加速渲染,我們也能夠做到頁(yè)面切換的“無(wú)刷新”瀏覽(因?yàn)殇秩舅俣茸銐蚩欤?/p>
調(diào)整后端實(shí)現(xiàn)
雖然我非常喜歡使用 Node.js,以往也分享過(guò)你所未知的3種Node.js代碼優(yōu)化方式,但是,為了能夠低成本提高高性能的資源響應(yīng),這里進(jìn)行技術(shù)棧切換是必要的:比如 Golang。
在使用 Golang 簡(jiǎn)化程序功能后,程序?qū)τ诿總€(gè)請(qǐng)求的響應(yīng)基本能夠保持在幾毫秒的水平(受限于網(wǎng)絡(luò)傳輸),相比較之前大概下降了2~3個(gè)量級(jí)。頁(yè)面關(guān)鍵的 DOM ContentLoad 時(shí)間更是縮短到原來(lái)的八分之一。
Flare 優(yōu)化過(guò)后批量請(qǐng)求狀況
結(jié)合上面的前端優(yōu)化提到的渲染時(shí)間來(lái)粗略估計(jì),從資源下載到渲染加起來(lái)都不到 10ms,如果不是瀏覽器的一些限制,繪制幀率應(yīng)該能夠遠(yuǎn)超 60 幀,進(jìn)一步滿足我們實(shí)現(xiàn)“即使刷新了也比沒(méi)一些沒(méi)刷新的實(shí)現(xiàn)還順滑”。
上面的實(shí)現(xiàn)中,我將頁(yè)面圖標(biāo)請(qǐng)求和頁(yè)面文檔進(jìn)行了拆分,在書(shū)簽數(shù)量和圖標(biāo)種類不多的場(chǎng)景下,或許并不是最優(yōu)的方案,但是一旦書(shū)簽數(shù)量級(jí)到幾百、上千之后,你會(huì)發(fā)現(xiàn)圖標(biāo)拆分可以極大地提升性能。
當(dāng)然,為了滿足數(shù)量比較少的場(chǎng)景,我也對(duì)合并輸出進(jìn)行了實(shí)現(xiàn),算上網(wǎng)站 favicon
獲取,一共只有兩個(gè)請(qǐng)求。在書(shū)簽不是很多的時(shí)候,渲染性能甚至比文檔和資源拆分輸出效率更好。
Flare 請(qǐng)求合并模式下的網(wǎng)絡(luò)請(qǐng)求
圖標(biāo)資源優(yōu)化
Flame 使用的方案是讀取后端接口配置,從前端腳本中動(dòng)態(tài)創(chuàng)建 SVG 圖標(biāo)并插入文檔中,F(xiàn)lare 程序默認(rèn)的方式則是將 SVG 和文檔拆分,以應(yīng)對(duì)大量書(shū)簽狀況下的頁(yè)面性能問(wèn)題。
雖然解決了頁(yè)面性能問(wèn)題,但是服務(wù)端 IO 問(wèn)題卻會(huì)伴隨而來(lái),所以這里還需要處理資源在服務(wù)端的釋放和讀取問(wèn)題,盡量將資源的磁盤 IO 變?yōu)榱恪?/p>
聽(tīng)起來(lái)比較玄乎,但其實(shí)結(jié)合代碼生成的方式,還是蠻好實(shí)現(xiàn)的。當(dāng)然,因?yàn)?Go 存在自動(dòng) GC,所以在不同的資源被使用的時(shí)候,會(huì)出現(xiàn)大量?jī)?nèi)存的分配,影響效率,這里可以考慮使用持久化方案來(lái)解決問(wèn)題,處理起來(lái)挺有意思的,受限于篇幅和主題就不展開(kāi)啦。有一部分我在前兩篇文章中提到了,關(guān)于 Golang 嵌入資源的使用和優(yōu)化。
前段時(shí)間折騰 Go Emed 的記錄
比如,在不針對(duì) HTTP 服務(wù)實(shí)現(xiàn)做任何優(yōu)化、限制運(yùn)行資源為兩核心的前提下,僅優(yōu)化資源 IO 后,能達(dá)到穩(wěn)定 3ms 輸出資源,每秒提供2萬(wàn)7千次以上的響應(yīng)服務(wù)。
容器鏡像的優(yōu)化
除了常規(guī)優(yōu)化之外,容器時(shí)代的應(yīng)用,鏡像優(yōu)化也是非常關(guān)鍵的。容器優(yōu)化方式,我在前面的文章反復(fù)提過(guò)多次,所以也不再展開(kāi)了,感興趣可以自行翻閱之前的內(nèi)容。
# 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
可以看到,相比較原版程序,優(yōu)化后的程序在本地解壓后的尺寸大概是之前十五到十六分之一。
額外的優(yōu)化
如果我們使用 lighthouse 針對(duì) Flame 前端實(shí)現(xiàn)進(jìn)行測(cè)試,能夠看到前端程序在實(shí)現(xiàn)上的一些小問(wèn)題,得分雖然四個(gè)環(huán)綠三個(gè),但是只有一個(gè)環(huán)是綠色的。
Flame 應(yīng)用 Lighthouse 得分
在重新實(shí)現(xiàn)的過(guò)程中,除了簡(jiǎn)化結(jié)構(gòu),調(diào)試實(shí)現(xiàn)之外,還順手將這四個(gè)圈都打到了滿分(Chrome 版本 v97+)。
Flare 應(yīng)用 Lighthouse 得分
最后
聊到這里,相信你已經(jīng)了解了我是怎么做的啦,如果你對(duì) Flare 感興趣,并且也需要一個(gè)簡(jiǎn)單的導(dǎo)航程序,可以訪問(wèn)項(xiàng)目 https://github.com/soulteary/docker-flare,來(lái)親自上手試試。
到此這篇關(guān)于Flare應(yīng)用前后端性能優(yōu)化的文章就介紹到這了,更多相關(guān)Flare性能優(yōu)化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
ffmpeg安裝及音頻轉(zhuǎn)換指令應(yīng)用
ffmpeg是一套可以用來(lái)記錄、轉(zhuǎn)換數(shù)字音頻、視頻,本文主要介紹了ffmpeg安裝及音頻轉(zhuǎn)換指令應(yīng)用,具有一定的參考價(jià)值,感興趣的可以了解一下2024-02-02科學(xué)知識(shí):二進(jìn)制、八進(jìn)制、十進(jìn)制、十六進(jìn)制轉(zhuǎn)換
這篇文章主要介紹了科學(xué)知識(shí):二進(jìn)制、八進(jìn)制、十進(jìn)制、十六進(jìn)制轉(zhuǎn)換,本文只介紹一些理論知識(shí),需要的朋友可以參考下2015-05-05最新idea2021注冊(cè)碼永久激活(激活到2100年)
這篇文章主要介紹了idea2021注冊(cè)碼永久激活(激活到2100年),文中給大家提到了2020年最新JetBrains授權(quán)服務(wù)器-IntelliJ IDEA激活,需要的朋友可以參考下2020-01-01AI經(jīng)典書(shū)單 人工智能入門該讀哪些書(shū)?
學(xué)習(xí)人工智能該讀哪些書(shū)可以快速入門呢?我的答案是多讀經(jīng)典書(shū)。方向?qū)α思词孤c(diǎn),總會(huì)走向成功的終點(diǎn)。而該讀哪些書(shū),小編推薦五份經(jīng)典書(shū)單2017-11-11阿里巴巴開(kāi)源 Dragonwell JDK 最新版本 8.1.1-GA 發(fā)布
距離 Dragonwell JDK 第一個(gè)正式版本 8.0.0-GA 發(fā)布已經(jīng)過(guò)去 3 個(gè)月了,項(xiàng)目在 Github 上的 stars 繼續(xù)攀升達(dá)到了 1900。今天我們帶來(lái)了最新版本 8.1.1-GA 的發(fā)布,包含了全新的特性和更新,需要的朋友可以參考下2019-10-10