前端體驗優化(3)——後端

2023-12-18 12:00:58

  前端很多時候是不會接觸到後端的工作,不過我們公司由於歷史原因,維護了大量的 Node.js 服務。

  所以也積累了一些後端優化的經驗,主要分兩塊 Node.js 和資料庫。

一、Node.js

  Node.js 的監控沒有從 0 開始,業務邏輯的紀錄檔直接記錄在阿里雲中,效能監控部署的是阿里雲提供的系統。

  業務邏輯包括自定義的埋點,以及資料庫的增刪改查操作,還有內部介面存取的紀錄檔資訊等。

  在效能監控中,可以匯出記憶體快照,方便排查記憶體漏失的問題。

1)服務

  Node.js 提供了定時任務、SSR、壓縮影象、訊息佇列等服務。

  定時任務可以將一些比較費時的計算按指定的間隔執行,以免直接存取造成介面的慢響應。

  SSR 就是伺服器端渲染,是一種常見的頁面優化手段,qwik 是一款語法接近 React 的前端 SSR 框架。

  其目標是延遲載入所有的程式碼,例如一個按鈕在沒有點選它之前,就不會去載入點選的邏輯,甚至不會去載入 React 相關的程式碼。

  理念很好,但是 qwik 還不夠穩定,應該會有比較多的坑。

  訊息佇列是一種限流削峰的有效手段,一般由於瞬時存取量過大,導致流量暴增,系統無法處理請求甚至發生崩潰。

  而在加入訊息佇列後,系統可以從訊息佇列中取資料,相當於訊息佇列做了一次緩衝,極大地減少了業務處理的壓力。

2)介面

  在業務迭代和人員流動的過程中,就會出現一些冗餘介面,而這些介面的存取量還可能比較大。

  在對業務進行分析後,可以將其合併到另一個介面中,或者就是將介面響應直接寫到前端指令碼中,直接減少一次通訊。

  比較影響介面查詢時間的是與資料庫的互動,可以減少互動次數,例如增加一層快取,無必要就不去查詢。

  或者要查詢多條記錄時,用一條 SQL 語句完成,可以在 SQL 使用 in 語法,例如 select * from user where id in (1,2,3)。

  對於提交到伺服器的資料,需要進行安全測試,即排除潛在隱患,防止刷介面,例如增加身份驗證。

  當程式碼出現異常時(例如調的內部介面沒有響應),得避免介面奔潰,一直載入中,可以給到統一的響應。

  在統一響應的 JSON 格式後(例如 { code:0, data: {}, msg: "xxx"}),就可以比較方便的統計介面異常和規範前端程式碼。

  規定 code 為 0 是正常響應,非 0 為異常後,就能統計各種異常響應的數量以及佔比,進行鍼對性的優化。

  諸如監控、埋點等非業務且量比較大的介面,推薦單獨分離出來(可根據請求路徑進行服務轉發),以免出現錯誤時,影響線上業務,

3)版本升級

  公司之前所有的 Node 專案,其環境都是 8.9.4 版本,釋出於 2018 年的一個比較古老的版本。

  老版本有兩個比較明顯的問題:

  1. Node 高版本的特性和方法都無法使用。
  2. 有些第三方新版本的包無法安裝和升級,該包可能依賴比較高的 Node 版本。

  之前在開發專案時就遇到第三方包自身的問題,必須升級或換個包才能解決,但因為 Node 版本的原因,無法替換,只能用其他方式來修補漏洞。

  2022 年的 7 月份,才有機會將版本升級到 16.15,總共有 4 個 Node 環境需要升級。

  在 4 個待升級的專案中,有 3 個是對外的專案,有 1 個是對內的專案。

  那麼先升級對內專案的 Node 版本,這樣有兩個好處:

  1. 即使出問題了,影響範圍也能最小。
  2. 響應也能最及時,因為有問題的話,在公司內部能馬上反饋到我們組。

  我們每個專案都會有 3 套執行環境:測試、預發和生產。

  首先將測試環境升級,測試環境都是開發人員使用,影響最小,反饋最快。

  觀察一段時間後,再升級預發,預發環境與生產環境最為接近,資料庫採用的也是一套。

  最後才是生產,給真實使用者使用,再獲取反饋。從開始升級到全部專案升級完成,前前後後操作了 20 多天。

4)框架

  Node.js 的框架眾多,公司使用的是 KOA2,它非常輕量,諸如路由、模板等功能預設都不提供,需要自己引入相關的中介軟體。

  洋蔥模型的中介軟體非常強大,我們將異常響應、身份驗證、傳送站內信等各類操作都交由中介軟體處理。

  各類框架都有其自身的特點和適用場景,選擇最合適自己團隊的框架才是正解。

  在公司的倉庫中,發現之前有用基於 next.js 建立的一個 SSR 專案,不過後面沒人維護,再之後就被運維釋放掉了。

  如果選擇了某一框架,那還是要儘量維護起來,投入到實際生產中,發揮其作用。

二、資料庫

  資料庫被分為關係型和非關係型資料庫,公司用的 MySQL 就屬於前者,而 Redis、MongoDB、ElasticSearch 等資料庫就屬於後者。

  線上資料庫也需要有個監控平臺執行著,瞭解資料庫的 CPU,定位慢查詢等。

1)MySQL

  在某張表的資料量上去後,效能瓶頸就會浮現出來。

  常見的優化包括建立索引、縮小查詢範圍等,利用執行計劃,可以觀察索引建立前後影響的行數。

  每次索引建立後,都能提高几百、甚至幾萬倍的查詢速度,當然,建立索引也是有講究的,也不是隨便建立的。

  之前就遇到建立太長的索引,被拒絕了。

  另一種優化手段是將資料歸檔,就是將比較舊或不用的資料遷移至別處,另一個資料庫或直接下載到本地。

  當然,將資料儲存在雲服務中,是會產生經濟成本的,所以還有種辦法就是直接刪除,這類通常是冗餘資料,不會用到。

  對於非業務的資料表,也是像介面那樣需要分離,以免出現容量上限、慢查詢等問題而拖垮線上服務。

2)MongoDB

  自研的前端監控系統中的表欄位有很多是 JSON 格式,這類資料其實很適合儲存在 MongoDB 中。

  因為 MySQL 需要先將物件進行字串序列化,然後再儲存,而此時若需要以物件中的某個屬性作為查詢條件,MySQL 就難以實現了。

  不過,最終並沒有將資料儲存在 MongoDB 中,因為雲服務只提供了 MySQL 的視覺化操作介面和監控平臺。

  為了便於自己排查異常,還是選擇了 MySQL 作為儲存媒介,但公司有許多遺留業務都儲存在 MongoDB 中,日常還需要維護。

  所以後期自研了 MongoDB 視覺化查詢工具,在後臺可以查詢線上的 MongoDB,省得每次都登入伺服器檢視。

3)ElasticSearch

  當需要全文檢索一張千萬級的表時,在 MySQL 中實現就比較困難,很容易將資料庫掛起,並且在 5.7.6 之前的 MySQL 還不支援中文檢索。

  此時可用 ElasticSearch 替代,這是一款基於 Lucene 的分散式、可延伸、RESTful 風格的全文檢索和資料分析引擎,擅長實時處理 PB 級別的資料。

  前端監控資料每日會增加 70W 條記錄,在 ES 中用關鍵字檢索這批資料會非常快。

  若資料中有比較大的欄位,那麼生成的倒排索引也會比較巨大,當時我儲存了介面的響應,結果整個容量直接膨脹了 3 倍。

  若要聯立好幾張表的 MySQL 資料,那麼也比較適合存到 ES 中。

  例如使用者釋出的紀錄檔,分成了 100 張表儲存在 MySQL 中,現在需要關鍵字檢索,就可以將這 100 張表合併到 ES 中。

4)Redis

  Redis 是一種支援 key-value 等多種資料結構的儲存系統,基於記憶體,可持久化,用於快取、訊息佇列、叢集等場景。

  Redis 之所以快,一方面是因為大部分操作在記憶體中完成,以及採用高效的資料結構,例如雜湊表、跳錶等。

  另一方面,就是 Redis 採用了多路複用機制,使其在網路 IO 操作中能並行處理大量的使用者端請求,實現高吞吐率。

  而 Redis 的並行處理能力(每秒處理請求數)能達到萬級別,甚至更高。

  前面也講到過,在介面中增加一層快取,可加速介面響應,這也是快取的常規用途,但是要注意,當快取被穿透時,資料要能自動恢復。

  也就是說,快取不保障資料的一致性,不能因為快取的異常,而影響線上業務,將錯誤資訊儲存到資料庫中。

  適當的將小部分特定的資料提前存入到 Redis 中,可起到預熱的作用,增加體驗。